Trial the map as the default for every postcode search, not only on desktop (DEFAULT_LOCATION_VIEW in HomeView; results_view_changed shows how often readers switch away). The view is now derived: the reader's choice once made, else the default for the kind of search. That follows a hero search through client-side navigation without syncing state. With nothing left varying by device, the user-agent default goes (lib/device.ts, the headers() read in page.tsx). That settles two review points: the HTML no longer needs Vary: User-Agent, and there is no narrow-window fallback left to flash the map before the list. Review fix in LeafletMapInner: the card reopened only when the selection or the school list changed, so a rebuild caused by the centre, reference point or radius closed it for good while the page still had the school selected. The selection effect now follows a pins-rebuilt counter. SchoolMap memoises its fallback centre, which was a fresh array on every render and would have rebuilt every pin each time. Phones hide the map view's list pane, so it no longer builds up to 500 hidden cards there (decided after mount, so hydration still matches). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SchoolCompare frontend and CMS
Next.js App Router with React, TypeScript, CSS Modules, Chart.js, Leaflet and Payload CMS. It serves school search, comparisons, rankings, school/place detail pages and editorial content across England.
Start with the repository overview, architecture and development checks.
Source map
| Path | Purpose |
|---|---|
app/(frontend)/ |
Public root layout, server pages and FastAPI proxy |
app/(payload)/ |
Payload root layout, /admin and /cms-api |
app/robots.ts, app/opengraph-image.tsx, root icons |
Site-wide metadata endpoints |
components/ |
Client views and reusable display components |
components/school/ |
School detail sections |
lib/api.ts, lib/types.ts |
Fetch wrappers and manual school API types |
lib/schoolSections.ts, lib/compareLogic.ts |
Presentation decisions and data preparation |
context/, hooks/ |
Comparison state, suggestion state and responsive behaviour |
collections/, blocks/, migrations/ |
CMS schema and production migrations |
__tests__/ |
Jest and React Testing Library tests |
Do not introduce a shared app/layout.tsx: public pages and Payload have separate
root layouts. Keep root metadata files outside the route groups.
Data and state
Server pages fetch initial data directly from FASTAPI_URL. Browser fetches use
/api by default, forwarded by app/(frontend)/api/[...path]/route.ts.
FASTAPI_URL must include /api. See .env.example for CMS and API settings.
State uses React hooks/context, URL search parameters and localStorage for the comparison basket. SWR is not installed. Maps use dynamic Leaflet wrappers. Revalidation intervals are configured in fetch wrappers and pages; they vary by resource. Backend reloads do not automatically invalidate every Next.js cache.
Commands
npm ci
npm run typecheck
npm test -- --runInBand
npm run build
test:watch and test:coverage are also available. There is no lint script.
A running application needs the backend/data environment described in the
development guide.
After CMS field or editor changes, run npm run generate:importmap. Keep
payload-types.ts generated from the CMS schema rather than editing it by hand.
The build must work without a database connection; avoid module-scope CMS queries
and DB-backed generateStaticParams functions.
See publishing for CMS operations and deployment for staging and production promotion.