I1 · Part 2 · How it runs
What happens when you open a page
Every page and every JSON file is already a file when you ask for it. Three handlers under /api are the only code that runs per request, plus one quiet exception.
07On this page
Four kinds of request
A request to this site ends up in one of four places. Most go to a file that the build already wrote. A few go to a function. One awkward case goes to a function when it should not. And the playground's language runtimes come from somewhere else entirely: the browser fetches them from a public CDN, not from this site.
Before anything is served
There is no middleware.ts and no proxy.ts in the repository, so no code
runs in front of the routes. What happens at the edge is set in next.config.ts, which
the build turns into routing rules:
- Headers on every path. One rule,
/:path*, attaches a Content-Security-Policy,X-Frame-Options: DENY,nosniff, a referrer policy and a permissions policy that turns off camera, microphone and geolocation. The CSP'sscript-srcandconnect-srclists are built fromlib/wasmAssets.ts, so they allow the jsDelivr origin and nothing else. - One redirect. Requests for the old
groundwork-ivory-beta.vercel.apphost get a permanent redirect togroundwork.austincoders.com, with the path kept.
Rate limiting in front of /api/ is a Vercel Firewall rule. Its threshold is set in
the Vercel dashboard and deliberately kept out of this public repository, so this chapter cannot
quote it.
The common case: a file
The last production build wrote 1,762 entries into .next/prerender-manifest.json,
and every one has initialRevalidateSeconds: false. Nothing is on a timer and nothing
is regenerated in the background. A file changes only when a deploy replaces it.
| What | Count | Made by |
|---|---|---|
| Pages, as HTML plus an RSC payload | 1,222 | page.tsx and generateStaticParams |
| Test cases for other languages | 538 | app/problems/[slug]/cases/route.ts |
| Search indexes | 21 | 20 per-topic routes plus one global route |
| Mock interview question banks | 12 | app/mock/bank/[stage]/route.ts, one per stage |
| Metadata files | 14 | 9 Open Graph images, sitemap, robots, manifest, two icons |
On top of those sit the 81 hashed JavaScript chunks under /_next/static, and
public/wasm/, which holds 46 TypeScript lib.*.d.ts files and a 205 KB
React sandbox bundle. Both are written by the prebuild script.
What a page weighs
Sizes from that same build. HTML is uncompressed, as the manifest records it. JavaScript is the
first-load set from .next/diagnostics/route-bundle-stats.json, with gzip measured
on the actual chunk files.
| Route | HTML | First-load JS |
|---|---|---|
| Home | 186 KB | 732 KB, 224 KB gzip, 18 files |
| A chapter page | 50 to 222 KB; the JavaScript topic averages 106 KB | 681 KB, 210 KB gzip, 14 files |
| A problem page | 39 to 50 KB | 1,182 KB, 373 KB gzip, 18 files |
/practice | 27 KB | 1,182 KB, 373 KB gzip, 18 files |
/whiteboard | 27 KB | 636 KB, 196 KB gzip, 13 files |
/problems | 276 KB | 681 KB, 210 KB gzip, 13 files |
Two rows need explaining. /practice has so little HTML because
PracticeClient reads useSearchParams() inside a
<Suspense fallback={null}>. During prerendering, everything below that boundary
is left for the browser to render, so the page ships an empty frame. The whiteboard is small for
a different reason: its canvas is loaded with ssr: false and is not in the first load
at all.
Moving between pages
After the first page, a click on a link does not fetch HTML. The router fetches the target's
.rsc payload, which is also a prerendered file. Nineteen chapter routes have a
loading.tsx that shows ChapterSkeleton while it arrives. The root layout
wraps every page in RouteFade, which fades in the new page on client navigations but
not on the first load.
The quiet exception, now half-closed
The chapter, problem and level routes all export generateStaticParams. Of the 25
dynamic routes in the build's manifest, 22 did not also set dynamicParams, so the
default of true applied: a slug the build never saw, such as
/notes/not-a-chapter, was not answered with a stored 404. It was handed to a function,
which rendered the page, and TopicChapterPage called notFound() after the
fact — a page built on demand that happens to say "not found."
Nineteen of those 22 now set export const dynamicParams = false: the 18 topic
[chapter] routes and /architecture/[chapter]. The manifest lists all 19
with fallback: false, the same as /git/[section], which already had it.
An unknown slug under any of those routes is now a stored 404, nothing rendered. Three routes are
still open, still listed with fallback: null: /problems/[slug],
/level/[topic], and /interview/[chapter], which renders through
RoundView rather than TopicChapterPage and was never part of this fix.
/problems/[slug] and /level/[topic] would take the same one-line fix as
the chapter routes, but that is a separate change, not made here. /interview/[chapter]
is a different page component entirely. Until all three set dynamicParams = false, a
mistyped problem id, level or interview round still costs a render before the 404.
What the browser fetches after load
| Request | When | Served as |
|---|---|---|
{topic}/search-index.json and /search-index.json | First character typed in the chapter search box | Static file |
/problems/{id}/cases | Switching a problem to a language other than JavaScript or SQL, or starting the debugger | Static file |
/mock/bank/{stage} | Pointing at or focusing the start button in the mock lobby, then starting; cached per stage in memory | Static file |
/api/tts | Pressing Listen, once per chunk of up to 700 characters | Function, then CDN-cached |
/api/joke | Opening /progress | Function |
/api/client-error | An uncaught error or rejection, at most five per page load, and only when no Sentry DSN is configured | Function |
Runtimes on cdn.jsdelivr.net | First run of Python, SQL, Lua, Ruby, PHP, C or C++ | Someone else's CDN |
Each of the three functions is an extra on top of a page that already works without it. If one fails, the narrator does not start, a hardcoded joke appears, or an error is not logged. No chapter depends on any of them. The APIs chapter goes through each one in detail.
Finished reading?
0 of 26 chapters read in this series