B2 · Part 1 · Foundations
The tech stack
Every dependency, what it does here, where it runs, and what it would cost to remove.
06On this page
The frame
package.json lists 43 runtime dependencies and 16 development ones. That sounds like a
lot for a static site, and it is: 20 of the 43 are @codemirror/ packages, and most of the
rest exist for the editor. The frame the site actually sits in is small.
| Thing | Version | What it does here |
|---|---|---|
| Next.js (App Router) | 16.3.0 | Routing, the build, static generation, metadata routes, the server/client split |
| React | 19.2.8 | Components; server components for everything that does not need a browser |
| TypeScript | 5.9.3 | Types the code and the content, so a malformed chapter fails the build. The same package also runs in the browser as the playground's TypeScript compiler |
| CSS | — | app/globals.css at about 7,400 lines, plus 84 CSS modules for the pages and tools that carry their own styles |
| Tailwind | 4 | Imported for its theme and utilities layers only, with no preflight reset. The site is styled with custom properties and its own class names |
| Vercel | — | Host, CDN, firewall, analytics |
| Node | 24.x | Pinned in engines and to 24 in .nvmrc, so the local tools, Vercel and the project settings agree |
The editor
The playground's editor is CodeMirror 6, mounted through @uiw/react-codemirror in
components/practice/CodeEditor.tsx. lib/codeLanguages.ts lists 17 languages,
and each one's syntax support is a dynamic import(), so choosing Rust downloads the Rust
grammar and nothing else. Kotlin, Swift, C#, Ruby and Lua use the older stream parsers from @codemirror/legacy-modes. Three packages from
Replit add a Vim mode, a minimap and indentation guides.
The editor's tooling runs off the main thread. lib/editor/toolsWorker.ts is a Web Worker
that loads eslint-linter-browserify with the globals package for linting,
Prettier's standalone build for formatting, and the TypeScript compiler for type errors. That is why
Prettier appears under dependencies rather than devDependencies: it is not
only a tool the repository runs on itself, it ships to readers.
The runtimes
Eleven of the 17 languages run. None of them runs on a server. Each one is loaded the first time a reader picks it, and most of them come from jsDelivr rather than from this site.
| Language | What runs it | Served from | Runs in |
|---|---|---|---|
| JavaScript | The browser's own engine | — | jsWorker, a Web Worker |
| TypeScript | The typescript package, type-checking against 46 lib files, then run as JavaScript | This site: /wasm/typescript-lib/, 1.2 MB | Compiled in the page, run in jsWorker |
| Python | Pyodide 314.0.6 | jsDelivr | pyodideWorker |
| SQL | sql.js 1.14.2, SQLite compiled to WebAssembly | The glue is bundled; the .wasm comes from jsDelivr | The page |
| Ruby | ruby.wasm 2.10.1 with Ruby 3.4 | jsDelivr | scriptWorker |
| PHP | php-wasm 0.1.0 | jsDelivr | scriptWorker |
| Lua | wasmoon 1.16.0 | jsDelivr | scriptWorker |
| C, C++ | clang from @yowasp/clang, run on a WASI shim | jsDelivr | scriptWorker |
| HTML, CSS | The browser | — | A sandboxed preview iframe |
| React components | react-sandbox.js, 204 KB, bundled by esbuild from lib/reactSandbox/ | This site: /wasm/ | An iframe with sandbox="allow-scripts" |
| Java, Go, Rust, Kotlin, Swift, C# | Nothing | — | Write-only: highlighted, formatted, not run |
.wasm from jsDelivr straight into the
page.
Why the runtimes come from a CDN
Pyodide and sql.js used to be copied into public/wasm/ by a prebuild script and shipped
with every deploy. The commit that moved them to jsDelivr records why: public/wasm/ was
18 MB, Vercel keeps every deployment, and the project had gone over its storage allowance on two
runtimes that only load when someone picks Python or SQL. After the move the folder was 1.3 MB, just the
TypeScript lib files. The Ruby, PHP, Lua and clang runtimes were added the same way from the start. The
trade is plain: deploys got small, and in exchange the Python and C++ paths depend on a third party
being up. tests/wasm-assets.test.ts fails if the pinned versions drift from what is
installed. lib/wasmAssets.ts holds every version and URL in one place, the
NEXT_PUBLIC_PYODIDE_BASE, NEXT_PUBLIC_SQL_JS_BASE and
NEXT_PUBLIC_NPM_CDN variables can point them somewhere else, and
scripts/copy-wasm-assets.mjs --all still knows how to self-host Pyodide and sql.js. The
Content-Security-Policy in next.config.ts is built from the same list, so adding a runtime
origin in one place allows it in the other.
Small pieces
- msedge-tts — the narrator's voice, behind
/api/tts. Unofficial, free, and the one real single point of failure among the server functions. See the endpoints chapter. - Radix —
@radix-ui/react-focus-scopetraps focus in the shortcut help, loaded dynamically, and inside the sharedFocusTrap, imported statically, which holds focus in the site menu, the Chapters sheet and the Filters sheet. Theradix-uipackage supplies one component, the Select incomponents/ui/select.tsx, used by four files. - clsx and tailwind-merge — one helper,
cn()inlib/utils.ts, for that Select. - next/font — 13 Google font families behind the 7 font styles in the display picker, alongside 9 colour themes.
- Vercel Analytics and Speed Insights — pageviews and field Core Web Vitals.
- @sentry/nextjs — imported only when
NEXT_PUBLIC_SENTRY_DSNis set. Without it, browser errors are posted to/api/client-errorand land in the function log instead.
The tools that never reach a reader
| Tool | What it catches |
|---|---|
ESLint + eslint-config-next | The usual, plus Next-specific mistakes |
| Prettier | Formatting, with content/interview-data.ts and content/practice/ ignored on purpose |
| cspell | Spelling across every file, including all the prose |
| Vitest | 78 test files. Among them: the content's integrity, the site's claims about itself, SEO metadata, and the pure engines behind the mock interview, the grader, the debugger and the whiteboard |
| Playwright | 27 tests in one smoke spec against a production build: the pages load, the menu opens, the path trail stays clear of itself, the playground and an exercise run, a DSA chapter's code switch, player and chapter check work, the end-of-chapter popup asks once, the placement flow lands on a plan, unknown URLs return 404 |
| Lighthouse CI | Performance budgets, run three times so one slow run cannot fail them; started by hand with npm run test:perf |
| esbuild | Bundles the React sandbox before every build |
| husky + lint-staged | Lint, format and spell-check staged files on commit; reject code comments on push |
No state management library: useSyncExternalStore over small storage modules covers it.
No component library beyond that one Select: the site has its own vocabulary and its own stylesheet.
No ORM, because there is no database. No CMS, because the content is the repository. No auth, because
there is nothing per-user on the server. And no code-execution service: every language that runs, runs
in the reader's tab, which is why the playground costs nothing to operate however much it is used.
Finished reading?
0 of 26 chapters read in this series