Case Study: Porting a 1.37M-LOC Next.js App

A 205-route production Next.js app ported to nextrs — every dev-loop dimension measured, honestly, including the two Next still wins

The full report, with methodology and raw numbers, is served here: nextrs vs Next.js — the dev loop, measured. This page is the summary. One machine, one app, same database, same day; every number from a reproducible harness. nextrs numbers are from the debug build — a conservative floor.

The app

A production SaaS dashboard: 1.37M lines of first-party TypeScript, 205 routes, 404 models / 360 enums, ~1,935 API procedure signatures, and a ~768k-LOC React UI. The port is structurally complete (100% of routes, schema, and dispatch wired and type-checked; the React UI reused byte-for-byte via zero-copy) and behaviorally partial (~40% of procedures have real sqlx bodies; ~7% of the 22,680 backend tests converted, with 681 documented PORT-GAPs instead of fake greens).

Headline numbers

DimensionNext.js 16 devnextrsEdge
First open of an unseen page (median)7.1 s (≤22 s)3.6 ms~1,975×
…in a real browser18.7 s45 ms~400×
Warm page682 ms TTI2.2 ms~300×
Dev-server RAM (load-tested)16–27 GB46→75 MB~210–600×
Minimum RAM to run at all~14 GB (OOM below)<48 MB~300×
Production build238 s → 5.7 GB46 s → 47 MB binary~5× / 120×
Type-checktsc 117 s (12 GB heap)cargo check 27.5 s~4×
~1,200 DB-backed testsvitest+Prisma ~141 s/shardcargo-nextest 1.6 s~85×
Cold start (live on Vercel)11.2 s to first byteapp-init ~9 ms locally
React hot-edit (HMR)0.3–0.7 s ✅2.5 sNext
LintBiome 3.8 s ✅clippy 17.7 sNext

Eleven dimensions favor nextrs; two favor the JS toolchain, stated plainly. The trade: give up ~2 s on the cheapest loop (HMR) to erase 7–22 s on the most expensive one, plus two orders of magnitude of memory.

The recurring villain: the JS module graph

The same root cause dominates three symptoms. Next's cold start (11.2 s live on Vercel) is Node resolving and evaluating an enormous import graph before the first byte. The test suite spends 445 s of CPU on collect (rebuilding that module graph per test file, per worker) versus 78 s actually running assertions — the real Postgres queries are not the bottleneck. And dev-server memory holds that graph resident: kernel-OOM at 4 GB and 8 GB caps; needs ~14 GB to render a page. A compiled binary has no module resolution at runtime — the graph was linked at build time.

Does it scale?

The workspace was artificially inflated 158k → 278k LOC with handler-shaped code and re-measured: the compile slope is ~24 ms per 1,000 LOC, because dependencies (63% of the work) compile once and cache. The schema crate — the heaviest, most serial part — is 100% ported already; what remains is cheaper-per-line leaf-crate logic. Projected full-port cold build: ~45 s best case, ~2 min worst — versus next build at 238 s today.

What it unlocks

  • ~7 engineer-weeks/year across 20 devs recovered from cold-page spinners alone.
  • No more 32 GB-machine floor (~$0 vs $60–280/mo/dev of cloud dev boxes).
  • One 47 MB function instead of per-route bundles creeping toward Vercel's 250 MB ceiling — React ships as static JS on the CDN, not in the function.
  • The foreclosed-today unlock: a live preview env per open PR, and agent-scale dev — dozens of full app instances per box. Impossible at 27 GB per instance; trivial at 48 MB.

Honesty ledger

The comparison's imperfections all run against nextrs: the 24-core benchmark box flatters Next's heavier compile and memory; nextrs's numbers are the understating debug build; the behavioral port is partial and says so with counted, documented gaps. Where the JS toolchain wins (HMR, lint), the report says that too. Full methodology in the complete report.