{"node":{"id":"00c82fa2-90c7-4fe7-b145-9dd5238363b6","handle":"00c8","type":"note","authorName":"Vincent Yeung","intent":"recipe","title":"Pattern: putting a site up on Cloudflare","aiSummary":null,"content":"**When:** you're putting a site up on Cloudflare Pages, attaching a domain to it, or wondering why a change you made isn't showing on the live site.\n\n**Built from:** align before you give it more, where it runs and what it may do.\n\n**The worry:** the quick way to get a site up is one powerful token that can do everything. It works, so nobody goes back to it. And a site that looks fine in the dashboard can be quietly broken: deploys that stopped, analytics that never counted a visit, pages you deleted still being served.\n\n**What's really going on:** deploying a page and setting up a domain are two different jobs. Deploying happens all the time and needs very little access. Setting up a domain happens once and needs a lot. Mixing them gives you a standing key with far more power than anything uses. Most of what goes wrong after that is silent, so you only find it by checking.\n\n**Tokens: two, with different lives**\n- *Rare and powerful goes in the dashboard or a throwaway token. Frequent and weak gets a standing token.*\n- *The standing token* is called pages-deploy. It has Cloudflare Pages Edit on the account and nothing else. It covers every project, including ones that don't exist yet, so a new site needs no token work. It's the one that lives in GitHub Actions. If it leaks, the worst case is bad content you fix by redeploying.\n- *The throwaway token* is called domain-setup. It's made only when a domain is first attached, scoped to that one domain, never all of them: Zone Read, DNS Edit, SSL and Certificates Edit, Zone Settings Edit, Cache Purge. Delete it as soon as the domain and SSL are working.\n- Cache purges and one-off DNS records (a Search Console TXT, DMARC) go through the dashboard. Never add DNS Edit to the standing token.\n- Make tokens under the account, not under your own profile. They outlive your login and show on the one screen people check.\n- When you audit, read both lists, the account's and your profile's. An audit of one list is wrong. Every token is named for its job. One with a made-up name like \"sparkling-fire\" gets traced or deleted.\n- A token is shown once, when it's made. If you didn't save it, roll it. Don't go hunting.\n- Save a secret with printf, not echo, because echo adds a newline and Cloudflare then calls the token malformed (error 9106). Error 10000 means the token arrived and was refused: check which account it's scoped to.\n\n**Deploying**\n- Keep the source repo private. Don't connect Pages to GitHub. GitHub Actions builds the site and uploads only the built folder with Wrangler, so Cloudflare never sees the source.\n- In a pnpm workspace, call `npx --yes wrangler@3 pages deploy` directly. The official wrangler action tries to install at the repo root and fails. Don't pin a pnpm version in the setup step if package.json already says which one.\n- The order for a new domain: make the Pages project, point the bare domain and www at it (Cloudflare allows that at the bare domain when proxied), attach both domains, set SSL to strict with HTTPS always on and TLS 1.2 minimum, put the deploy token in GitHub, delete the throwaway, push, watch the run.\n\n**Defaults every site gets**\n- A real 404 page. Without one, Pages serves the home page with a 200 for any missing address.\n- HTML set to `Cache-Control: public, max-age=0, must-revalidate`. Without it, a deleted or changed page can stay live for up to a week.\n- Security headers in a `_headers` file: HSTS, nosniff, no framing, a strict referrer policy, permissions locked off, and a content security policy.\n- HSTS can't be taken back once a domain is on the preload list. Start at six months with no preload. Go to two years with preload only after two to four weeks of the site running cleanly.\n- If the domain sends no email, add the null SPF and DMARC records so nobody can send mail pretending to be it.\n\n**Analytics: decide per site, and prove it fires**\n- Whether a site measures at all is a choice. Some sites deliberately carry no tracking.\n- If it measures, use Cloudflare Web Analytics, not Google Analytics: no cookies, no consent banner.\n- Never use the dashboard's automatic setup. It injects the script at the edge, the site's content security policy blocks it, and the dashboard still says it's set up. That happened twice: one site recorded nothing for a week, lattris.com for three months.\n- Use the snippet setup instead, and add Cloudflare's two addresses to the policy.\n- A tag in the page proves nothing. Load the live page in a real browser and check that the request to `cloudflareinsights.com/cdn-cgi/rum` went out, with no policy errors. Then watch the dashboard count go up.\n- There's no API permission for Web Analytics. The dashboard is the only way in.\n\n**Deploys fail silently**\n- A failed GitHub Action tells nobody. Once the pipeline was dead for four weeks and a fix sat unshipped for a week.\n- So when the live site doesn't show your change, first check the deploy actually ran. That separates \"my change is wrong\" from \"my change never shipped\".\n- Headers are cached at the edge too. Just after a deploy, the bare address can show the old headers. Check with a cache-buster (`?cb=` plus something random) before deciding the deploy failed.\n- If a deleted page still answers: open the deployment's own pages.dev address first. If that shows a 404, the build is right and only the cache is behind. Purge, wait, or push a no-op change.\n- `_headers` rules add up, they don't replace each other. A content security policy on `/*` also applies to every more specific path, and two policies on one address make the browser enforce only what both allow. So you can't loosen the policy for one path. Use one site-wide policy that allows what every page needs.\n- The same goes for `Cache-Control`. One on `/*` lands on `/assets/*` too, and a year-long immutable rule never takes effect. Put no `Cache-Control` on `/*`. Give each group of paths its own: HTML at max-age 0, assets at a year, immutable.\n- A file cached as immutable for a year keeps its old copy under the same name. When you replace an image, give it a new file name.\n- A new path can take minutes to show on the production address after a deploy. The deployment's own address is the one to trust.\n- Only one machine deploys. A deploy from an out-of-date copy on a second machine quietly put an older version back live. Pull first, and deploy only by pushing.\n\n**After it's up: make it findable and shareable**\n- Link previews. Every page needs Open Graph and Twitter card tags in its `<head>`, or LinkedIn, Facebook, Discord, Slack, iMessage, X and WhatsApp show a bare link:\n  - `og:title`, `og:type`, `og:url` (the page's canonical address), `og:site_name`, `og:description`\n  - `twitter:card` set to `summary`, plus `twitter:site` and `twitter:creator` with the handle\n  - `og:type` is `website` on the home page and `article` on content pages.\n  - `og:image` is optional but makes previews much better. Use one default image at 1200×630. Without it, platforms show a text-only card, which still works.\n- A `robots.txt` at the site's root, allowing everything and pointing to the sitemap: `User-agent: *`, `Allow: /`, `Sitemap: https://DOMAIN/sitemap.xml`.\n- Google Search Console:\n  1. Add the site as a URL-prefix property, `https://DOMAIN`.\n  2. Verify it with the HTML tag: a `google-site-verification` meta tag in the layout's `<head>`. Or use a DNS TXT record, added through the dashboard or a throwaway token.\n  3. Deploy, so the tag is live, then click Verify.\n  4. Under Sitemaps, submit `https://DOMAIN/sitemap.xml`.\n- Put the site's address in your social bios.\n\n\n\n**As a skill:** ask the AI to turn this page into a skill for any new site setup.","capturedAt":"2026-10-29T05:44:41.508Z","createdAt":"2026-10-29T05:44:41.510Z","updatedAt":"2026-11-06T04:43:50.332Z","spanId":null,"contributor":null,"audiences":[{"kind":"public","createdAt":"2026-11-06T04:27:07.200Z"}],"fingerprint":"5e5270fb44f8acc1d0cf09a9185b752672803b9c8d462e248595c34c32ca282c","revisionNo":"4","structureHash":"69d402a179a28c7f173da5f223acc012de8e96105b9da11c58afd2619c9c8ada","page":"https://lattris.com/by/b/memories/00c82fa2-90c7-4fe7-b145-9dd5238363b6"},"links":[{"direction":"out","verb":"references","reason":"only if you want the general habit behind the token split: agree what access is needed before handing it over","hash":"3eaf33ba0153d7374b639736f282d7e5f56a2ead7fd9d0940fca491be5e55db8","node":{"id":"a97b3a18-fa1f-4edf-8edb-82f41fd3aedd","handle":"a97b","type":"note","title":"Pattern: align before you give it more","aiSummary":null,"erased":false,"href":"https://lattris.com/api/lattris/by/b/memories/a97b3a18-fa1f-4edf-8edb-82f41fd3aedd?links=1","fingerprint":"74f20963d5ea1705a3aef44bbf792cedc82d2c85cf6aa8a1e8171a3c43c6c58b","revisionNo":"2","structureHash":"6c5ab9aa89dc3fc8c9e5585072b5cc09c525fd192edb43323b9638474970a4fe","intent":"recipe","authorName":"Vincent Yeung","page":"https://lattris.com/by/b/memories/a97b3a18-fa1f-4edf-8edb-82f41fd3aedd"}},{"direction":"in","verb":"references","reason":"only if you're putting a site up on Cloudflare, attaching a domain, or a live site isn't showing your change","hash":"9da2113146b1a2e253e58ce3ac195ff339644f718185451eda06d0f5701e8e49","node":{"id":"331896d2-2e9d-46b1-b56c-048a18ee23eb","handle":"3318","type":"note","title":"Patterns: a library","aiSummary":"Index of the author's patterns, one page each, built from the basics. Any pattern can be turned into a skill by asking the AI. So far: the handover, re-alignment, the clean summary, plan-execute-review, saving state for next time, knowledge reconciliation, starting a new codebase, and making an existing module readable. Grows as pages are added.","erased":false,"href":"https://lattris.com/api/lattris/by/b/memories/331896d2-2e9d-46b1-b56c-048a18ee23eb?links=1","fingerprint":"7916c915219b006b717edc7b297d1a807abe38eb305c7baa17ff1e7f0add9bbb","revisionNo":"10","structureHash":"687289243eac53d59127886c143c7b0384c691d49d47431094cb081316c95e03","intent":"recipe","authorName":"Vincent Yeung","page":"https://lattris.com/by/b/memories/331896d2-2e9d-46b1-b56c-048a18ee23eb"}},{"direction":"in","verb":"references","reason":"Follow when the site is ready to host.","hash":"db98eee4152e85e44b45ffceaa825f163a455bb73858639e536cc3d01b04a017","node":{"id":"5f8b457d-b87a-4e61-9af2-3008deaf37a9","handle":"5f8b","type":"note","title":"Front-end design: the toolbox","aiSummary":null,"erased":false,"href":"https://lattris.com/api/lattris/by/b/memories/5f8b457d-b87a-4e61-9af2-3008deaf37a9?links=1","fingerprint":"49e1c7f41f22c466c7ef1a993cab80b6dcbedab4552d2ac684133d60dea7e60b","revisionNo":"2","structureHash":"64df670c7bd82572e0ad9b2e06b8829d3b2e509be134591c04ea1fa281fca3ef","intent":"learning","authorName":"Vincent Yeung","page":"https://lattris.com/by/b/memories/5f8b457d-b87a-4e61-9af2-3008deaf37a9"}},{"direction":"in","verb":"references","reason":"Follow for hosting traps: headers that add up, caching, checking the right address.","hash":"9002a9c8cc9fde2f9b4638d738aba3165f56579d4894910b145e5fb61e5b85c5","node":{"id":"c89881f9-196f-47e1-98ed-d75a4571258d","handle":"c898","type":"note","title":"Drawer 7: Shipping","aiSummary":null,"erased":false,"href":"https://lattris.com/api/lattris/by/b/memories/c89881f9-196f-47e1-98ed-d75a4571258d?links=1","fingerprint":"84a05c0f98df15e746ff5d66c0d77e2eda6e2b5fdafc026907c323e7bf6b9777","revisionNo":"1","structureHash":"cc128b64ee43e01d855adc5862ee568da80d0e14712743a7e5c1cbf4594437fc","intent":"learning","authorName":"Vincent Yeung","page":"https://lattris.com/by/b/memories/c89881f9-196f-47e1-98ed-d75a4571258d"}},{"direction":"in","verb":"references","reason":"Read this when the site goes on Cloudflare Pages: tokens, headers, caching and silent deploy failures.","hash":"8d77a9e87427dada071ab68ea4f2e0b10a2623713a2ad423eaf4214ad3884156","node":{"id":"ec50a07f-f1ee-4008-b3ce-b7e1a847d706","handle":"ec50","type":"note","title":"Pattern: building a website with AI","aiSummary":null,"erased":false,"href":"https://lattris.com/api/lattris/by/b/memories/ec50a07f-f1ee-4008-b3ce-b7e1a847d706?links=1","fingerprint":"62d9975e4cd521f59b3f3e71719dbcae5835d307e28597ea0104247bb6721bb2","revisionNo":"1","structureHash":"e63b54c083aa93ec272c5d03a5b587824c3c5a78373b8839600f59268a5f2083","intent":"learning","authorName":"Vincent Yeung","page":"https://lattris.com/by/b/memories/ec50a07f-f1ee-4008-b3ce-b7e1a847d706"}}],"dictionary":{"lattris":"A personal knowledge base — how a person captures what they know as linked blocks they own and control. To understand how this knowledge base works: https://lattris.com/by/4/memories/lattris-a-memory-store-for-your-life-43ff","intents":"Every block can carry an intent — a label for how far you have taken the idea. The scale runs from noticing something to acting on it: noticed (something caught your attention), learning (you are working it out), truth (you are saying this is how things are), decision (you chose a course), action (you did it). Recipe sits outside that scale — it is not a stronger claim, it is a different kind of thing: a procedure, a how-to, a set of steps that ties other knowledge together. Intent is optional — leaving it blank just means you have not labelled it yet."},"instructions_to_ai":"This is a recipe from a personal knowledge base. Read it and apply it to what the person you're talking to is working on. Follow the linked blocks to explore further related topics if it might be useful to the user; open each at its page address. Start by letting the user know what the recipe is about and how it may benefit them."}