How does Next.js automatic code splitting and SSR work
Next.js code-splits per route by default — each page becomes its own chunk plus a shared vendor chunk. Dynamic imports (`next/dynamic`) split inside pages. SSR happens per request: server renders the React tree, sends HTML + a `__NEXT_DATA__` payload, browser hydrates. App Router adds RSC (server-only components, ship less JS) and streaming SSR via Suspense.
Automatic code splitting
Next.js inspects the route map and emits one chunk per page. The build output looks roughly like:
.next/static/chunks/
pages/_app.js
pages/index.js
pages/dashboard.js
framework.js // React + ReactDOM
main.js // Next runtime
webpack.js // module loaderWhen you visit /dashboard, the browser fetches:
framework.js(cached across pages),main.js,webpack.js,pages/_app.js,pages/dashboard.js.
Marketing page / doesn't ship dashboard.js. Each route loads only its slice.
Dynamic imports inside a page
import dynamic from "next/dynamic";
const HeavyChart = dynamic(() => import("@/components/HeavyChart"), { ssr: false });The chart becomes its own chunk, loaded only when rendered. { ssr: false } opts out of SSR entirely (useful for browser-only libraries).
Prefetching
<Link href="/dashboard"> prefetches the dashboard's JS chunk + data when the link enters the viewport. By the time the user clicks, the chunk is already cached — feels instant.
SSR (Pages Router)
export async function getServerSideProps(ctx) {
const data = await fetchData();
return { props: { data } };
}
export default function Page({ data }) { ... }On request:
- Server runs
getServerSideProps. - Server renders the React tree to HTML.
- Response = HTML +
__NEXT_DATA__script with serialized props. - Browser parses HTML, hydrates using
__NEXT_DATA__for state continuity.
SSR (App Router)
// app/dashboard/page.tsx — React Server Component
export default async function Page() {
const data = await fetch("/api/data", { cache: "no-store" }).then(r => r.json());
return <Dashboard data={data} />;
}- Server components render server-only — no client JS shipped for them.
- Suspense boundaries enable streaming SSR: server flushes HTML in chunks as data resolves.
"use client"components hydrate on the browser.
RSC vs SSR
| SSR | RSC | |
|---|---|---|
| Where renders | Server, per request | Server only |
| Client JS shipped | All component JS | Only for client components |
| State + interactivity | After hydration | Client components only |
| Use case | SEO + first paint | Reduce JS / data layer code |
RSC ships less JavaScript by keeping data-fetching and rendering on the server for non-interactive parts.
Caching
App Router fetches default to deduplication + caching:
cache: "force-cache"— like SSG.cache: "no-store"— dynamic.next: { revalidate: 60 }— ISR.
Image / font / script optimization
<Image>— auto srcset, lazy load, AVIF/WebP.<Script>— control strategy (beforeInteractive,afterInteractive,lazyOnload).next/font— self-hosted, zero CLS.
Tradeoffs
| Pro | Con |
|---|---|
| Per-route splitting works out of the box | Bundle waterfalls if you don't watch chunk graphs |
| SSR + RSC = small client JS | Server cost + cold starts on serverless |
| Streaming + Suspense for fast above-the-fold | More mental model to debug |
Interview framing
"Next.js splits per route by default — each page becomes a chunk, sharing a vendor chunk. Dynamic imports split inside a route. <Link> prefetches the next route's chunk in the background, so navigation feels instant. SSR: Pages Router uses getServerSideProps to fetch, then renders the React tree to HTML and ships hydration data. App Router uses React Server Components — non-interactive components stay on the server and ship no client JS, while "use client" boundaries hydrate. Streaming SSR via Suspense flushes HTML in chunks. The cache modes (force-cache / no-store / revalidate) let you dial freshness per fetch."
Follow-up questions
- •Compare RSC vs SSR vs SSG.
- •How does the Link prefetch work?
- •What is streaming SSR and Suspense's role?
Common mistakes
- •Importing a heavy library at the top of every page.
- •Forgetting prefetch hurts cold-start nav.
- •Mixing server-only code into client components.
Performance considerations
- •Per-route splitting cuts initial bundle. RSC + streaming cut hydration cost. Link prefetch eliminates first-visit lag.
Edge cases
- •Server-only code (db, secrets) leaking into client bundle.
- •Hydration mismatches.
- •Static export limits with SSR.
Real-world examples
- •Vercel.com, Next.js docs, large e-commerce running on App Router.