Both features ship dark. Every flag in this system starts off, so deploying this makes /about and /blog disappear until someone turns them on deliberately in Unleash.
Two flags, not one
about_page and blog are independent, which makes blog-on-about-off a reachable state. That state is why this is bigger than four notFound() calls: the blog leans on the About page for its author identity.
The Person entity is anchored at /about#tudor, and that URL 404s while about_page is dark, so a post published in that state would claim an author resolving to nothing — a worse credibility signal than having no named author at all. So when About is dark:
both bylines render "Tudor" as plain text rather than a link into a 404
blogPostingJsonLd attributes to the publisher instead of the Person
the Person node is dropped from the post's @graph
Every combination of the two flags now renders something correct.
What's gated
/about, /blog, /blog/[slug], the RSS feed, both footer links, and the matching content-sitemap.xml entries. A sitemap must never advertise a URL that 404s. With both dark it emits a valid empty <urlset> rather than a 404, because robots.txt names it unconditionally.
The footer's whole "About" section is dropped when both are dark, rather than left as a heading with nothing under it.
Not gated: /admin. Posts have to be writable before the blog is worth switching on; flagging the panel would make the flag unflippable.
The revalidate problem
Reading a flag pins the calling route to the lowest revalidate among its fetches, and the footer links render in the root layout — so a naive gate there would have dropped every school and place page from a 7-day cache to a 5-minute one. That is a large origin-load regression to pay for two footer links.
getFlags therefore takes a revalidate instead of always using the 300s constant, and each call site passes its own route's floor:
Route
Passes
Effect
root layout
604800
matches what the SEO routes already declare, so nothing regresses
/blog/[slug]
3600
its own declared floor
/about, /blog, RSS, sitemap
default 300
no floor of their own to protect
The build confirms all four SSG route families (/school/[slug], /schools/[place], /schools/authority/[la], /schools/near/[outcode]) still prerender as ● (SSG), and /about is now ISR at 5 minutes.
The cost is one-way latency: pages follow a flip within minutes, footer links within a week. Turning a feature on early shows the page before its footer link, which is harmless. Turning one off leaves a footer link to a 404 until the cache turns over, so a rollback that matters wants a purge.
Tests
The e2e journeys follow the paired shape the admission_distance flag already established: a lit journey and a dark one per flag, reading state from whether /about and /blog respond rather than from /api/flags, which another journey asserts is not publicly reachable. The dark journeys assert clean absence — 404, no footer link, no sitemap entry — rather than an empty page.
New Jest coverage for the footer's four link states, the JSON-LD author fallback, and the revalidate parameter. The backend's existing registry and 90-day staleness tests pick up both new flags without changes.
Verification
tsc --noEmit clean.
412 Jest tests across 49 suites pass.
11 backend flag tests pass.
npx playwright test --list compiles: 117 journeys, both new ones present.
next build succeeds with DATABASE_URL unset, as CI builds it, with the SSG route families intact.
Both flags are temporary scaffolding like every flag here: the 90-day tripwire starts failing on 2026-12-07, at which point either the feature is permanent and the flag comes out, or it was never going to ship.
docs/PUBLISHING.md gains a section up front, since a writer publishing to a dark blog would otherwise reasonably conclude that saving is broken.
Both features ship dark. Every flag in this system starts off, so deploying this makes `/about` and `/blog` disappear until someone turns them on deliberately in Unleash.
## Two flags, not one
`about_page` and `blog` are independent, which makes blog-on-about-off a reachable state. That state is why this is bigger than four `notFound()` calls: **the blog leans on the About page for its author identity.**
The `Person` entity is anchored at `/about#tudor`, and that URL 404s while `about_page` is dark, so a post published in that state would claim an author resolving to nothing — a worse credibility signal than having no named author at all. So when About is dark:
- both bylines render "Tudor" as plain text rather than a link into a 404
- `blogPostingJsonLd` attributes to the publisher instead of the Person
- the `Person` node is dropped from the post's `@graph`
Every combination of the two flags now renders something correct.
## What's gated
`/about`, `/blog`, `/blog/[slug]`, the RSS feed, both footer links, and the matching `content-sitemap.xml` entries. A sitemap must never advertise a URL that 404s. With both dark it emits a valid empty `<urlset>` rather than a 404, because `robots.txt` names it unconditionally.
The footer's whole "About" section is dropped when both are dark, rather than left as a heading with nothing under it.
**Not gated: `/admin`.** Posts have to be writable before the blog is worth switching on; flagging the panel would make the flag unflippable.
## The revalidate problem
Reading a flag pins the calling route to the *lowest* revalidate among its fetches, and the footer links render in the root layout — so a naive gate there would have dropped every school and place page from a 7-day cache to a 5-minute one. That is a large origin-load regression to pay for two footer links.
`getFlags` therefore takes a revalidate instead of always using the 300s constant, and each call site passes its own route's floor:
| Route | Passes | Effect |
|---|---|---|
| root layout | `604800` | matches what the SEO routes already declare, so nothing regresses |
| `/blog/[slug]` | `3600` | its own declared floor |
| `/about`, `/blog`, RSS, sitemap | default `300` | no floor of their own to protect |
The build confirms all four SSG route families (`/school/[slug]`, `/schools/[place]`, `/schools/authority/[la]`, `/schools/near/[outcode]`) still prerender as `● (SSG)`, and `/about` is now ISR at 5 minutes.
The cost is one-way latency: pages follow a flip within minutes, footer links within a week. Turning a feature on early shows the page before its footer link, which is harmless. Turning one off leaves a footer link to a 404 until the cache turns over, so a rollback that matters wants a purge.
## Tests
The e2e journeys follow the paired shape the `admission_distance` flag already established: a lit journey and a dark one per flag, reading state from whether `/about` and `/blog` respond rather than from `/api/flags`, which another journey asserts is not publicly reachable. The dark journeys assert clean absence — 404, no footer link, no sitemap entry — rather than an empty page.
New Jest coverage for the footer's four link states, the JSON-LD author fallback, and the revalidate parameter. The backend's existing registry and 90-day staleness tests pick up both new flags without changes.
## Verification
- `tsc --noEmit` clean.
- 412 Jest tests across 49 suites pass.
- 11 backend flag tests pass.
- `npx playwright test --list` compiles: 117 journeys, both new ones present.
- `next build` succeeds with `DATABASE_URL` unset, as CI builds it, with the SSG route families intact.
Both flags are temporary scaffolding like every flag here: the 90-day tripwire starts failing on 2026-12-07, at which point either the feature is permanent and the flag comes out, or it was never going to ship.
`docs/PUBLISHING.md` gains a section up front, since a writer publishing to a dark blog would otherwise reasonably conclude that saving is broken.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01DXnXQKnPpZBBP61fBQiFkq
Both features ship dark. Neither is reachable in an environment where
its flag is off, and every flag in this system starts off, so a deploy
of this commit makes both disappear until someone turns them on
deliberately.
Two independent flags rather than one, which makes blog-on-about-off a
reachable state. That state is the whole reason the change is larger
than four notFound() calls: the blog leans on the About page for its
author identity. The Person entity is anchored at /about#tudor, and
that URL 404s while about_page is dark, so a post published in that
state would claim an author resolving to nothing. Worse than having no
named author. Both bylines fall back to unlinked text and the
BlogPosting attributes to the publisher instead, so every combination
of the two flags renders something correct.
Gated: /about, /blog, /blog/[slug], the RSS feed, both footer links,
and the matching content-sitemap entries. A sitemap must never
advertise a URL that 404s. With both dark it emits a valid empty
urlset rather than a 404, because robots.txt names it unconditionally.
Not gated: /admin. Posts have to be writable before the blog is worth
switching on, so flagging the panel would make the flag unflippable.
getFlags takes a revalidate rather than always using the 300s
constant. Reading a flag pins the calling route to the lowest
revalidate among its fetches, and the footer links live in the root
layout, so a naive gate there would have dropped every school and
place page from a weekly cache to a 5-minute one. The layout passes
604800, the floor those routes already declare, and the build confirms
all four SSG route families still prerender. The cost is one-way
latency: pages follow a flip in minutes, footer links within a week.
The e2e journeys follow the existing paired shape from the
admission_distance flag: a lit journey and a dark one for each flag,
reading state from whether /about and /blog respond rather than from
/api/flags, which another journey asserts is not publicly reachable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DXnXQKnPpZBBP61fBQiFkq
This PR gates the new /about and /blog features behind two Unleash flags (about_page, blog), making both pages, the RSS feed, sitemap entries, footer links, and JSON-LD author attribution independently conditional and defaulting to dark/off. It's a clean, well-tested change: the ISR revalidate floors passed to getFlags() at each call site match each route's actual declared revalidate (verified against about/page.tsx, blog/[slug]/page.tsx's revalidate=3600, and layout.tsx's 604800), the sole call site of blogPostingJsonLd was updated for its new required namedAuthor param, and the /api/flags endpoint is generic over the registry so the two new flags need no extra backend wiring.
✅ No issues found.
## 🤖 AI Code Review (Claude Code)
This PR gates the new /about and /blog features behind two Unleash flags (about_page, blog), making both pages, the RSS feed, sitemap entries, footer links, and JSON-LD author attribution independently conditional and defaulting to dark/off. It's a clean, well-tested change: the ISR revalidate floors passed to getFlags() at each call site match each route's actual declared revalidate (verified against about/page.tsx, blog/[slug]/page.tsx's revalidate=3600, and layout.tsx's 604800), the sole call site of blogPostingJsonLd was updated for its new required namedAuthor param, and the /api/flags endpoint is generic over the registry so the two new flags need no extra backend wiring.
✅ No issues found.
tudor
merged commit 47f3591ed8 into main2026-09-08 16:47:18 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Both features ship dark. Every flag in this system starts off, so deploying this makes
/aboutand/blogdisappear until someone turns them on deliberately in Unleash.Two flags, not one
about_pageandblogare independent, which makes blog-on-about-off a reachable state. That state is why this is bigger than fournotFound()calls: the blog leans on the About page for its author identity.The
Personentity is anchored at/about#tudor, and that URL 404s whileabout_pageis dark, so a post published in that state would claim an author resolving to nothing — a worse credibility signal than having no named author at all. So when About is dark:blogPostingJsonLdattributes to the publisher instead of the PersonPersonnode is dropped from the post's@graphEvery combination of the two flags now renders something correct.
What's gated
/about,/blog,/blog/[slug], the RSS feed, both footer links, and the matchingcontent-sitemap.xmlentries. A sitemap must never advertise a URL that 404s. With both dark it emits a valid empty<urlset>rather than a 404, becauserobots.txtnames it unconditionally.The footer's whole "About" section is dropped when both are dark, rather than left as a heading with nothing under it.
Not gated:
/admin. Posts have to be writable before the blog is worth switching on; flagging the panel would make the flag unflippable.The revalidate problem
Reading a flag pins the calling route to the lowest revalidate among its fetches, and the footer links render in the root layout — so a naive gate there would have dropped every school and place page from a 7-day cache to a 5-minute one. That is a large origin-load regression to pay for two footer links.
getFlagstherefore takes a revalidate instead of always using the 300s constant, and each call site passes its own route's floor:604800/blog/[slug]3600/about,/blog, RSS, sitemap300The build confirms all four SSG route families (
/school/[slug],/schools/[place],/schools/authority/[la],/schools/near/[outcode]) still prerender as● (SSG), and/aboutis now ISR at 5 minutes.The cost is one-way latency: pages follow a flip within minutes, footer links within a week. Turning a feature on early shows the page before its footer link, which is harmless. Turning one off leaves a footer link to a 404 until the cache turns over, so a rollback that matters wants a purge.
Tests
The e2e journeys follow the paired shape the
admission_distanceflag already established: a lit journey and a dark one per flag, reading state from whether/aboutand/blogrespond rather than from/api/flags, which another journey asserts is not publicly reachable. The dark journeys assert clean absence — 404, no footer link, no sitemap entry — rather than an empty page.New Jest coverage for the footer's four link states, the JSON-LD author fallback, and the revalidate parameter. The backend's existing registry and 90-day staleness tests pick up both new flags without changes.
Verification
tsc --noEmitclean.npx playwright test --listcompiles: 117 journeys, both new ones present.next buildsucceeds withDATABASE_URLunset, as CI builds it, with the SSG route families intact.Both flags are temporary scaffolding like every flag here: the 90-day tripwire starts failing on 2026-12-07, at which point either the feature is permanent and the flag comes out, or it was never going to ship.
docs/PUBLISHING.mdgains a section up front, since a writer publishing to a dark blog would otherwise reasonably conclude that saving is broken.🤖 Generated with Claude Code
https://claude.ai/code/session_01DXnXQKnPpZBBP61fBQiFkq
🤖 AI Code Review (Claude Code)
This PR gates the new /about and /blog features behind two Unleash flags (about_page, blog), making both pages, the RSS feed, sitemap entries, footer links, and JSON-LD author attribution independently conditional and defaulting to dark/off. It's a clean, well-tested change: the ISR revalidate floors passed to getFlags() at each call site match each route's actual declared revalidate (verified against about/page.tsx, blog/[slug]/page.tsx's revalidate=3600, and layout.tsx's 604800), the sole call site of blogPostingJsonLd was updated for its new required namedAuthor param, and the /api/flags endpoint is generic over the registry so the two new flags need no extra backend wiring.
✅ No issues found.