PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m2s
PR Checks / Backend Smoke (pull_request) Successful in 8s
PR Checks / Build Backend (no push) (pull_request) Successful in 11s
PR Checks / Build Frontend (no push) (pull_request) Successful in 44s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 27s
Every place page shipped as 'Schools in Brentwood - Compare 27 Schools | schoolcompare | schoolcompare'. The root layout's title template appends '| schoolcompare' to any plain-string title, and all four place routes already carried the brand. W8 opted the other routes out with an absolute title; the place routes were written afterwards and did not inherit the lesson. ~2,600 titles affected, and the repetition pushed them past Google's truncation point, so the doubled brand displaced real words in the result. An e2e journey now asserts no title repeats the brand, across the static routes and a place page, so this cannot come back on a route added later. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015mWQnpye9F299NVRCCSRvj
68 lines
2.5 KiB
TypeScript
68 lines
2.5 KiB
TypeScript
/**
|
|
* Local authority pages.
|
|
*
|
|
* A separate namespace from /schools/[place] because 67 town names collide
|
|
* with an authority name and neither set contains the other — Bedford the
|
|
* town holds 104 schools, Bedford the authority 86, because postal towns
|
|
* cross authority boundaries. The title says "Local Authority" so a reader
|
|
* landing on both knows which set each covers.
|
|
*/
|
|
import { notFound } from 'next/navigation';
|
|
import type { Metadata } from 'next';
|
|
import { fetchPlace, fetchPlaces } from '@/lib/places';
|
|
import { fetchNationalAverages } from '@/lib/api';
|
|
import { PlaceView } from '@/components/places/PlaceView';
|
|
import { absoluteUrl } from '@/lib/site';
|
|
|
|
interface Props { params: Promise<{ la: string }> }
|
|
|
|
export const revalidate = 604800;
|
|
export const dynamicParams = true;
|
|
|
|
export async function generateStaticParams(): Promise<Array<{ la: string }>> {
|
|
// Gated like every other prerender in this app. There are only ~154
|
|
// authorities, but "few enough to always build" still means the API must be
|
|
// reachable at build time, and in CI it is not — the build fails with
|
|
// ECONNREFUSED rather than degrading. The catch is the same fallback the
|
|
// school route uses.
|
|
if (process.env.PRERENDER_PLACES !== '1') return [];
|
|
try {
|
|
return (await fetchPlaces())
|
|
.filter((p) => p.kind === 'authority')
|
|
.map((p) => ({ la: p.slug }));
|
|
} catch (error) {
|
|
console.warn('generateStaticParams: API unreachable, falling back to on-demand ISR.', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
|
const { la } = await params;
|
|
const detail = await fetchPlace('authority', la);
|
|
if (!detail) return { title: 'Place Not Found' };
|
|
|
|
const { name, count } = detail.place;
|
|
return {
|
|
title: { absolute: `Schools in ${name} — Local Authority | schoolcompare` },
|
|
description:
|
|
`All ${count} schools in the ${name} local authority, ranked by SATs and GCSE `
|
|
+ `results, with Ofsted grades and the authority average against England.`,
|
|
alternates: { canonical: absoluteUrl(`/schools/authority/${la}`) },
|
|
};
|
|
}
|
|
|
|
export default async function AuthorityPage({ params }: Props) {
|
|
const { la } = await params;
|
|
const detail = await fetchPlace('authority', la);
|
|
if (!detail) notFound();
|
|
|
|
const national = await fetchNationalAverages().catch(() => null);
|
|
return (
|
|
<PlaceView
|
|
detail={detail}
|
|
englandAverage={national?.primary?.rwm_expected_pct ?? null}
|
|
neighbours={[]}
|
|
/>
|
|
);
|
|
}
|