refactor(detail): render sections on the server behind a client shell

page.tsx now composes the sections and passes them through SchoolDetailShell
as children, so ~1,300 lines of static markup stop shipping as client
JavaScript. The shell keeps what is genuinely interactive: back link, header
reveal, hero map, compare CTA, sticky nav and scroll-spy.

The scroll-spy already located sections via document.getElementById, so it
works unchanged against server-rendered children.

Charts needed a client wrapper: next/dynamic with ssr:false is illegal in a
Server Component, so components/school/charts.tsx is the boundary that keeps
Chart.js (64 KB gz) lazy and browser-only.

Measured on this build:
- school route client chunk: 8 KB gz (33 KB raw)
- total static JS across all chunks: 380.6 -> 350.7 KB gz
- section markup is absent from every client chunk ("Got their first choice",
  "Ofsted reports", "Most deprived" etc. all return 0 hits); shell strings
  still present, as expected
- shared baseline unchanged at 172 KB gz -- out of scope, as designed

The 14 characterization tests pass byte-identical to the commit that
introduced them. Only the render helper changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Tudor
2026-08-02 21:39:21 +01:00
co-authored by Claude Opus 5
parent a7f6ff4035
commit 752eb07310
15 changed files with 1586 additions and 5089 deletions
@@ -11,15 +11,14 @@
* toggle renders at all, so such pages ship zero admissions JavaScript.
*/
import dynamic from 'next/dynamic';
import type { ReactNode } from 'react';
import type { SchoolAdmissions } from '@/lib/types';
import { formatAcademicYear, formatPercentage } from '@/lib/utils';
import { summariseAdmissions } from '@/lib/compareLogic';
import { Section, sectionStyles as styles } from './sectionShared';
import { AdmissionsViewToggle } from './AdmissionsViewToggle';
import { AdmissionsTrendChart } from './charts';
const AdmissionsTrendChart = dynamic(() => import('../AdmissionsTrendChart'), { ssr: false });
export function AdmissionsSection({
admissions,