Repair
Four changes. Two are settings on the Cloudflare zone, which answer before any code runs; two are code.
1. The trailing slash: 301, at the zone
A Redirect Rule on the zone, beside the existing www → apex rule:
- When: the request path ends with
/and is not/itself. - Then: a dynamic redirect to
https://flowingkhaos.com+ the path with its trailing slashes removed, status 301, query string preserved.
Check: curl -sI https://flowingkhaos.com/column/ → HTTP/2 301, location: https://flowingkhaos.com/column. / stays 200.
2. http → https, and HSTS
Zone settings: Always Use HTTPS on; HSTS on, six months, includeSubDomains off (the API host and others are not all on this zone’s TLS), preload off. Belt and braces in code: a public/_headers file that sets Strict-Transport-Security: max-age=15552000 on every path. The adapter copies it into the built assets the same way it copies _redirects, and appends its own cache rule for hashed assets.
Check: curl -sI http://flowingkhaos.com/ → 301 to https; the https response carries the HSTS header.
3. A sitemap with a true lastmod
The integration is removed. The site now writes sitemap-0.xml itself from the four content collections, and keeps sitemap-index.xml as a second endpoint listing the one file, so robots.txt, the /sitemap.xml 301 and the sitemap registered in Search Console all stay valid.
The date per URL: the CMS row’s updatedAt for a page with a row; the parent row’s for a fix’s stage pages and a workshop’s steps; the newest row’s for an index page (a list changes when its newest entry does); none for the three markdown pages that live in the repo. Every URL is the apex host with no trailing slash. The rules are one function, tested against the fixtures: every URL slashless, every URL dated except the three, nothing from /account, and one entry per node the site’s page tree declares.
One honest caveat, written beside the code so nobody “fixes” it later: Payload’s updatedAt moves on any save, including a save with no text change. Acceptable today, with few saves and all of them editorial. The trigger to revisit: lastmod moving on more than two pages a week without a text change. Then a contentUpdatedAt field set by a CMS hook that compares the body, and the sitemap reads that instead.
4. One schema.org graph on every page
The base layout prints one <script type="application/ld+json"> with a graph: the WebSite, the Brand at https://flowingkhaos.com/#brand (an id the clevrstart home page has pointed at for two weeks, which resolved to nothing until now), the Person at https://lukesidney.me/#person with the same profile list clevrstart publishes, the WebPage with its dateModified, and then the page’s own node:
| Page | Node |
|---|---|
| a fix, and each of its stages | TechArticle — headline, datePublished = opened, dateModified, author, about = the measured impact; a stage is isPartOf the fix |
| a workshop | Course with hasPart = its steps as LearningResource |
| a workshop step | LearningResource, isPartOf the course, dates from the parent |
| a column piece | BlogPosting |
| a listing | Product + Offer with the price Polar lists and the licence terms already on the page |
| everything else | the WebPage alone |
Every content node names the author and both dates. The same dates are printed as text in a <time> on fix, workshop and column pages, because a visible date is on the checklist for being cited.
In the same pass
- A description of its own on every page: fix stages and workshop steps take the first paragraph of their body, cut at a word under 160 characters; the index pages one sentence each; the legal pages their lede.
- The four stage pages of a fix get their own H1: the stage name, then the fix title.
- The sign-in links on a step page carry
rel="nofollow", so the crawl budget stays on the lesson.
Gates
astro check 0 errors. 265 tests pass. A build against the live CMS: 26 URLs in the sitemap, 23 dated. Under a local wrangler preview: the HSTS header on /, the sitemap index 200, /sitemap.xml 301, a fix stage with its canonical, its own description, its own H1 and a dated TechArticle with an author. /column/ still answers 307 locally, as expected: the 301 is the zone rule, and the zone is not in the preview.