2026-09-02 16:29:41 +01:00
# Publishing to the blog
The blog is Payload CMS, running inside the Next.js app. There is no separate
2026-09-02 17:14:20 +01:00
service and no second deploy. Writing a post is done in the browser and takes
2026-09-02 16:29:41 +01:00
effect on the live site within seconds.
2026-09-08 17:12:25 +01:00
## Before any of this: the flags
`/blog` and `/about` are behind feature flags (`blog` and `about_page` ), and
every flag in this system starts off. While `blog` is dark, `/blog` , every post
page and the RSS feed return 404, and neither appears in the sitemap or the
footer. Posts still save normally, because `/admin` is deliberately **not**
flagged: you have to be able to write a post before there is anything worth
switching on.
So a new post published to a dark blog is invisible, and that is working as
intended, not a bug. Flip the flag in Unleash when the content is ready.
Staging and production hold their own values (`development` and `production`
environments), so you can light it on staging first. The page follows a flip
within five minutes; the footer link takes up to a week, because it renders in
the root layout and is cached at the same weekly floor as the school corpus.
Both flags are temporary scaffolding, like every flag here: a test starts
failing once one is older than 90 days, at which point either the feature is
permanent and the flag comes out, or it was never going to ship.
2026-09-02 16:29:41 +01:00
## Signing in
2026-09-02 17:14:20 +01:00
`https://www.schoolcompare.co.uk/admin` , one account, no registration. If you
2026-09-02 16:29:41 +01:00
need the account seeded on a fresh environment, run against the container:
```bash
npx payload create-first-user
```
Staging has its own admin panel, its own database and its own credentials at
`https://stx.schoolcompare.co.uk/admin` . Never reuse production's secret or
password there.
## Writing a post
**Posts → Create New.** The fields:
| Field | Notes |
|---|---|
| **Title** | The `<h1>` and the browser tab. |
2026-09-02 17:14:20 +01:00
| **Slug** | The URL segment, in the sidebar. **Never change it after publishing.** It is the canonical URL, and changing it breaks every existing link and discards the page's accumulated search signal. |
2026-09-02 16:29:41 +01:00
| **Published at** | The date shown on the post and in the feed. |
| **Excerpt** | Max 200 characters. Shown on the index *and* used as the meta description, so write it as a standalone sentence rather than a teaser. |
| **Hero image** | Optional. Becomes the social share image; without one, the site's generated card is used. |
| **Content** | Rich text. `/` inserts a block. |
2026-09-02 17:14:20 +01:00
**Save as draft** while you're working; drafts are not public. **Publish** when
2026-09-02 16:29:41 +01:00
it's ready.
### The callout block
One custom block, `Callout` , with two tones:
2026-09-02 17:14:20 +01:00
- **Caveat**: what a number does *not* show. This is the one that matters. It
2026-09-02 16:29:41 +01:00
is how a post states a limitation in context rather than burying it in a
closing paragraph.
2026-09-02 17:14:20 +01:00
- **Note**: a useful aside.
2026-09-02 16:29:41 +01:00
### Images
Every image requires alt text; the editor will not let you save without it.
Uploads go to a Docker volume on the host, which is backed up separately from
2026-09-02 17:14:20 +01:00
Postgres. An image is not reproducible from the pipeline the way school data
2026-09-02 16:29:41 +01:00
is.
## How publishing reaches the live site
- `/blog` , `/blog/rss.xml` and `/content-sitemap.xml` are rendered per request,
so a new post appears immediately.
- `/blog/[slug]` is cached after its first request. Publishing or editing fires
a `revalidatePath` from the collection's `afterChange` hook, which drops that
2026-09-02 17:14:20 +01:00
cached copy, so edits appear immediately too.
2026-09-02 16:29:41 +01:00
If a change doesn't show, it is far more likely the post is still a draft than
that the cache is stale.
## House style
These rules are why the blog exists. A post that ignores them makes the site
read more machine-generated, not less.
2026-09-02 17:14:20 +01:00
- **First person singular.** "I built", "I found", never "we provide".
2026-09-02 16:29:41 +01:00
- **Concrete over general.** "When we were looking at schools in Wandsworth"
beats any amount of stated warmth.
- **State limits before someone else finds them.** Every post that presents a
metric says what it does not show. This is the single strongest signal that a
human wrote it: generated content does not volunteer its own weaknesses.
- **No mission statements, no "passionate about", no invented team.** There is
one person here.
2026-09-02 17:14:20 +01:00
- **No em dashes.** They are one of the clearest tells of machine-written
prose, which is the whole problem this blog exists to fix. A full stop, a
colon, a semicolon or a pair of commas does the job and reads as though a
person chose it.
2026-09-02 16:29:41 +01:00
- **Short sentences.**
- **Never publish a surname, an employer, or a child's name.** The site's author
is "Tudor". See `/about` .
- **Never invent a figure**, even illustratively. On a site whose whole
proposition is official data, a made-up number attached to a real school is
2026-09-02 17:14:20 +01:00
the one thing it cannot do, and no illustrative intent survives being
2026-09-02 16:29:41 +01:00
screenshotted.