This is a genuinely under-discussed Next.js App Router behavior, and we hit it directly building a converter that turns full HTML documents into Next.js output.
The setup
Next.js's App Router supports React Server Components, and every page.tsx route gets client-side navigation support baked in automatically — click a next/link anywhere on the site, and Next.js can swap in the new page's content without a full browser reload. To make that instant, App Router embeds a serialized copy of a page's own rendered content into the initial HTML response, as a hidden JSON payload the client-side router can read from immediately.
Where this becomes a real cost
For a typical app page — a few components, some text, some data — this payload is small and the tradeoff is clearly worth it. For a page whose actual content is a large amount of raw HTML (a scraped or migrated page, a CMS-rendered document, anything in that shape), that same mechanism serializes the entire rendered page a second time as an escaped JSON string embedded in the first response.
We measured this directly: the same page, same assets, same everything, served as a real page.tsx versus a plain Route Handler returning identical HTML. Mobile Performance: 36 vs 81. Cumulative Layout Shift: 0.947 (the worst possible score) vs 0.016. The only variable that changed was which Next.js primitive served the response.
Why it doesn't show up in typical guidance
Most Next.js performance advice is written for pages built from normal-sized React component trees, where this payload duplication is negligible. It only becomes visible at the specific intersection of "App Router page.tsx" and "a large amount of pre-rendered HTML content" — an intersection most apps never hit, but content migration, static-site generation, and CMS-heavy use cases hit constantly.
The practical takeaway
If a Next.js page's content is genuinely large (tens of KB or more of markup) and doesn't need client-side soft navigation — no next/link, no shared layout transitions — a Route Handler (route.ts) serving the same HTML directly sidesteps this entirely, since Route Handlers never enter App Router's page/hydration pipeline at all. It's a less commonly reached-for tool than page.tsx, but for this specific shape of content, it's the right one.