2026-02-02 20:34:35 +00:00
/ * *
* Individual School Page ( SSR )
* Dynamic route for school details with full SEO optimization
* /
import { fetchSchoolDetails } from '@/lib/api' ;
import { notFound } from 'next/navigation' ;
import { SchoolDetailView } from '@/components/SchoolDetailView' ;
2026-03-28 22:36:00 +00:00
import { SecondarySchoolDetailView } from '@/components/SecondarySchoolDetailView' ;
2026-02-02 20:34:35 +00:00
import type { Metadata } from 'next' ;
interface SchoolPageProps {
params : Promise < { urn : string } > ;
}
export async function generateMetadata ( { params } : SchoolPageProps ) : Promise < Metadata > {
const { urn : urnString } = await params ;
const urn = parseInt ( urnString ) ;
if ( isNaN ( urn ) || urn < 100000 || urn > 999999 ) {
return {
title : 'School Not Found' ,
} ;
}
try {
const data = await fetchSchoolDetails ( urn ) ;
const { school_info } = data ;
2026-03-28 22:36:00 +00:00
const isSecondary = ( school_info . phase ? ? '' ) . toLowerCase ( ) . includes ( 'secondary' ) ;
2026-02-02 20:34:35 +00:00
const title = ` ${ school_info . school_name } | ${ school_info . local_authority || 'England' } ` ;
2026-03-28 22:36:00 +00:00
const description = isSecondary
? ` View GCSE results, Attainment 8, Progress 8 and school statistics for ${ school_info . school_name } ${ school_info . local_authority ? ` in ${ school_info . local_authority } ` : '' } . `
: ` View KS2 performance data, results, and statistics for ${ school_info . school_name } ${ school_info . local_authority ? ` in ${ school_info . local_authority } ` : '' } . Compare reading, writing, and maths results. ` ;
2026-02-02 20:34:35 +00:00
return {
title ,
description ,
2026-03-28 22:36:00 +00:00
keywords : isSecondary
? ` ${ school_info . school_name } , GCSE results, secondary school, ${ school_info . local_authority } , Attainment 8, Progress 8 `
: ` ${ school_info . school_name } , KS2 results, primary school, ${ school_info . local_authority } , school performance, SATs results ` ,
2026-02-02 20:34:35 +00:00
openGraph : {
title ,
description ,
type : 'website' ,
url : ` https://schoolcompare.co.uk/school/ ${ urn } ` ,
siteName : 'SchoolCompare' ,
} ,
twitter : {
card : 'summary' ,
title ,
description ,
} ,
alternates : {
canonical : ` https://schoolcompare.co.uk/school/ ${ urn } ` ,
} ,
} ;
} catch {
return {
title : 'School Not Found' ,
} ;
}
}
// Force dynamic rendering
export const dynamic = 'force-dynamic' ;
export default async function SchoolPage ( { params } : SchoolPageProps ) {
const { urn : urnString } = await params ;
const urn = parseInt ( urnString ) ;
// Validate URN format
if ( isNaN ( urn ) || urn < 100000 || urn > 999999 ) {
notFound ( ) ;
}
// Fetch school data
let data ;
try {
data = await fetchSchoolDetails ( urn ) ;
} catch ( error ) {
console . error ( ` Failed to fetch school ${ urn } : ` , error ) ;
notFound ( ) ;
}
feat(data): integrate 9 UK government data sources via Kestra
Adds a full data integration pipeline for enriching school profiles with
supplementary data from Ofsted, GIAS, EES, IDACI, and FBIT.
Backend:
- Bump SCHEMA_VERSION to 3; add 8 new DB tables (ofsted_inspections,
ofsted_parent_view, school_census, admissions, sen_detail, phonics,
school_deprivation, school_finance) plus GIAS columns on schools
- Expose all supplementary data via GET /api/schools/{urn}
- Enrich school list responses with ofsted_grade + ofsted_date
Integrator (new service):
- FastAPI HTTP microservice; Kestra calls POST /run/{source}
- 9 source modules: ofsted, gias, parent_view, census, admissions,
sen_detail, phonics, idaci, finance
- 9 Kestra flow YAMLs with scheduled triggers and 3× retry
Frontend:
- SchoolRow: colour-coded Ofsted badge (Outstanding/Good/RI/Inadequate)
- SchoolDetailView: 7 new sections — Ofsted sub-judgements, Parent View
survey bars, Admissions, Pupils & Inclusion / SEN, Phonics, Deprivation
Context, Finances
- types.ts: 8 new interfaces + extended School/SchoolDetailsResponse
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 11:44:04 +00:00
const { school_info , yearly_data , absence_data , ofsted , parent_view , census , admissions , sen_detail , phonics , deprivation , finance } = data ;
2026-02-02 20:34:35 +00:00
2026-03-28 22:36:00 +00:00
const isSecondary = ( school_info . phase ? ? '' ) . toLowerCase ( ) . includes ( 'secondary' ) ;
2026-02-02 20:34:35 +00:00
// Generate JSON-LD structured data for SEO
const structuredData = {
'@context' : 'https://schema.org' ,
'@type' : 'EducationalOrganization' ,
name : school_info.school_name ,
identifier : school_info.urn.toString ( ) ,
. . . ( school_info . address && {
address : {
'@type' : 'PostalAddress' ,
streetAddress : school_info.address ,
addressLocality : school_info.local_authority || undefined ,
postalCode : school_info.postcode || undefined ,
addressCountry : 'GB' ,
} ,
} ) ,
. . . ( school_info . latitude && school_info . longitude && {
geo : {
'@type' : 'GeoCoordinates' ,
latitude : school_info.latitude ,
longitude : school_info.longitude ,
} ,
} ) ,
. . . ( school_info . school_type && {
additionalType : school_info.school_type ,
} ) ,
} ;
return (
< >
< script
type = "application/ld+json"
dangerouslySetInnerHTML = { { __html : JSON.stringify ( structuredData ) } }
/ >
2026-03-28 22:36:00 +00:00
{ isSecondary ? (
< SecondarySchoolDetailView
schoolInfo = { school_info }
yearlyData = { yearly_data }
absenceData = { absence_data }
ofsted = { ofsted ? ? null }
parentView = { parent_view ? ? null }
census = { census ? ? null }
admissions = { admissions ? ? null }
senDetail = { sen_detail ? ? null }
phonics = { phonics ? ? null }
deprivation = { deprivation ? ? null }
finance = { finance ? ? null }
/ >
) : (
< SchoolDetailView
schoolInfo = { school_info }
yearlyData = { yearly_data }
absenceData = { absence_data }
ofsted = { ofsted ? ? null }
parentView = { parent_view ? ? null }
census = { census ? ? null }
admissions = { admissions ? ? null }
senDetail = { sen_detail ? ? null }
phonics = { phonics ? ? null }
deprivation = { deprivation ? ? null }
finance = { finance ? ? null }
/ >
) }
2026-02-02 20:34:35 +00:00
< / >
) ;
}