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
+26
View File
@@ -0,0 +1,26 @@
'use client';
/**
* Client wrappers for the lazily-loaded charts.
*
* `next/dynamic` with `ssr: false` is only legal inside a Client Component,
* and the sections that render charts are Server Components. These one-line
* wrappers are the client boundary, so the charts stay browser-only and
* code-split while the section markup around them stays on the server.
*
* Chart.js is ~64 KB gzipped, so keeping it lazy matters.
*/
import dynamic from 'next/dynamic';
export const PerformanceChart = dynamic(
() => import('../PerformanceChart').then((m) => m.PerformanceChart),
{ ssr: false },
);
export const SatsChart = dynamic(() => import('../SatsChart'), { ssr: false });
export const AdmissionsTrendChart = dynamic(
() => import('../AdmissionsTrendChart'),
{ ssr: false },
);