PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m15s
PR Checks / Backend Smoke (pull_request) Successful in 9s
PR Checks / Build Backend (no push) (pull_request) Successful in 20s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m21s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 12s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 1m39s
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
144 lines
5.3 KiB
TypeScript
144 lines
5.3 KiB
TypeScript
import type { Metadata } from 'next';
|
|
import Image from 'next/image';
|
|
import { notFound } from 'next/navigation';
|
|
import { absoluteUrl } from '@/lib/site';
|
|
import { getFlags } from '@/lib/flags';
|
|
import { personJsonLd, organizationJsonLd } from '@/lib/jsonld';
|
|
import styles from './About.module.css';
|
|
|
|
export const metadata: Metadata = {
|
|
title: 'About',
|
|
description:
|
|
'Who builds schoolcompare, why it exists, and where its numbers come from.',
|
|
alternates: { canonical: absoluteUrl('/about') },
|
|
};
|
|
|
|
/*
|
|
* Gated on about_page. The default 300s read is the right floor here: this
|
|
* page declares no revalidate of its own, so nothing is lost by it, and a flip
|
|
* lands within five minutes.
|
|
*
|
|
* notFound(), not a redirect: while the flag is dark this URL does not exist,
|
|
* and a 404 is what tells a crawler not to keep it.
|
|
*/
|
|
export default async function AboutPage() {
|
|
const flags = await getFlags();
|
|
if (flags.about_page !== true) notFound();
|
|
|
|
const jsonLd = {
|
|
'@context': 'https://schema.org',
|
|
'@graph': [personJsonLd(), organizationJsonLd()],
|
|
};
|
|
|
|
return (
|
|
<div className={styles.page}>
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
|
/>
|
|
|
|
<header className={styles.header}>
|
|
<Image
|
|
src="/brand/tudor.jpg"
|
|
alt="Tudor, who builds schoolcompare"
|
|
width={96}
|
|
height={96}
|
|
className={styles.portrait}
|
|
priority
|
|
/>
|
|
<div>
|
|
<p className={styles.kicker}>Who's behind this</p>
|
|
<h1 className={styles.heading}>I'm Tudor. I built this site.</h1>
|
|
</div>
|
|
</header>
|
|
|
|
<div className={styles.prose}>
|
|
<p className={styles.lede}>
|
|
I'm a parent in south-west London. When we started looking at
|
|
primary schools, I found the information I needed was all published,
|
|
and almost impossible to hold in one place.
|
|
</p>
|
|
|
|
<p>
|
|
SATs results were in one government table. Ofsted judgements were in a
|
|
separate service, in a format that had just changed. Admissions
|
|
distances were buried in council PDFs, a different one per borough,
|
|
each with its own layout. I ended up building a spreadsheet, and then
|
|
I got tired of the spreadsheet.
|
|
</p>
|
|
|
|
<p>
|
|
So I built this instead. It pulls the official figures into one place
|
|
and puts them side by side, which is what I wanted and could not find.
|
|
</p>
|
|
|
|
<h2 className={styles.subheading}>I'm not an education expert</h2>
|
|
|
|
<p>
|
|
I want to be straightforward about that. I'm not a teacher, a
|
|
governor, or an education researcher. I have no qualification that
|
|
makes my opinion about a school worth more than yours.
|
|
</p>
|
|
|
|
<p>
|
|
What I do have is the problem itself. I'm going through primary
|
|
admissions right now, and I work with data for a living. That
|
|
combination is enough to take published figures and present them
|
|
honestly. It is not enough to tell you which school is right for your
|
|
child, and this site never tries to.
|
|
</p>
|
|
|
|
<h2 className={styles.subheading}>Where the numbers come from</h2>
|
|
|
|
<p>
|
|
Everything here is official published data: Key Stage 2 and Key Stage
|
|
4 results and school characteristics from the Department for
|
|
Education, inspection outcomes from Ofsted, and admissions data from
|
|
local authorities. Nothing is estimated, modelled or filled in. Where
|
|
a figure is missing, the page says so rather than showing a guess.
|
|
</p>
|
|
|
|
<p>
|
|
This is an independent site. It is not affiliated with the Department
|
|
for Education or with Ofsted, and nobody pays to appear on it or to
|
|
rank higher.
|
|
</p>
|
|
|
|
<h2 className={styles.subheading}>What the data can't tell you</h2>
|
|
|
|
<p>
|
|
A school is not its results. The figures here describe one year group,
|
|
on a handful of days, measured in a way that suits national statistics
|
|
rather than your child. A small cohort makes percentages swing wildly.
|
|
In a class of thirty, one pupil is worth more than three points.
|
|
Results say nothing at all about whether a child will be happy
|
|
somewhere.
|
|
</p>
|
|
|
|
<p>
|
|
I try to build that honesty into the site rather than just say it
|
|
here. Special schools and pupil referral units are never compared
|
|
against a mainstream national average, because that comparison is
|
|
meaningless and makes good schools look like failing ones. Where a
|
|
number is unreliable, the aim is for the page to tell you before you
|
|
draw a conclusion from it.
|
|
</p>
|
|
|
|
<h2 className={styles.subheading}>If something's wrong</h2>
|
|
|
|
<p>
|
|
Tell me and I'll fix it. If a figure looks wrong, or a page gives
|
|
a misleading impression of a school, I genuinely want to know.
|
|
It's the fastest way this gets better.
|
|
</p>
|
|
|
|
<p>
|
|
<a href="mailto:contact@schoolcompare.co.uk" className={styles.link}>
|
|
contact@schoolcompare.co.uk
|
|
</a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|