skip to content

Optimization and Deployment

Next ships opinionated primitives for images, fonts, and bundle splitting, and it can run on a managed platform or in a plain Node container you own. Interviewers use this area to find out whether you've actually shipped a Next app, not just built one.

part ofNext.jsoverview, primer and where to startread it →
on this pageshow

explore

questions

26

In a Next.js App Router app, do you have to configure anything for one route's JavaScript to stay out of another route's initial download, and what is next/dynamic for then?

level: juniorimportance: must knowfreq 60%

answer

  1. routes are already separate downloads
  2. the extra split lives inside one route
  3. deferral, not deletion
  4. interaction-gated, and big relative to the route
  5. loading fallback reserves the space

basics

~10 s

Nothing to configure: Next.js code-splits per route automatically, so visiting /dashboard does not download /settings' client code. next/dynamic splits inside a route, deferring a heavy component until it is actually rendered or needed.

solid answer

~40 s

Route-level splitting is free in the App Router. Each route segment's client components end up in their own chunks, plus a shared chunk for what every route needs (the framework runtime, the root layout's client code, widely imported modules). You never touch bundler config for that. `next/dynamic` is the *within-route* tool: `const Chart = dynamic(() => import('./Chart'))` keeps that component out of the chunk the route loads up front and fetches it when it first renders. You reach for it when a single route carries something heavy that most visitors never see — a chart library, a rich text editor, a map, a modal's contents — or something that only works in the browser. It is a deferral, not a deletion: the code is still built and shipped, just requested later.

go deeper

for a junior

Know that Next splits JavaScript per route with no setup, and that next/dynamic is how you defer one heavy component inside a route. Be able to write the dynamic() call with a loading fallback.

for a middle

Explain that the import graph, not the render branch, decides what ships, so a conditionally rendered component still lands in the eager chunk. Say what the loading option is for and why deferral is not deletion.

for a senior

Show you pick split points from measurements: which routes are heavy, what fraction of users hit the deferred path, and what the added request latency costs at the moment of interaction. Mention prefetching on intent and verifying the drop in the build output.

for a principal

Own the policy question — where splitting is worth the extra chunk and where the team should instead cut the dependency or move work to the server. Set a budget the build output can be checked against so splitting decisions stop being folklore.

## The default: one split per route In the App Router you do not configure code splitting. When Next builds, every route segment gets its own client JavaScript chunks, and modules needed by many routes are hoisted into shared chunks. A visitor landing on `/dashboard` downloads the framework runtime, the shared chunks, the client code of the layouts wrapping that route, and that route's own client code — and nothing that belongs only to `/settings`. This is why the `next build` route table prints a per-route number alongside a "shared by all" number: the per-route figure is what that page adds on top of the common baseline. ## What that default does not do Route splitting is *coarse*. It says nothing about the size of a single route. If one page statically imports a 300 kB charting library at the top of a client component, that library lands in the chunk the route needs before it can render — even if the chart lives behind a tab nobody clicks. ```tsx 'use client' import { Chart } from 'heavy-chart-lib' // downloaded on first paint of this route export function Panel({ open }: { open: boolean }) { return open ? <Chart /> : <p>Open the panel to see the chart.</p> } ``` The conditional render does not help. Whether a module ships is decided by the import graph at build time, not by which branch runs. ## What next/dynamic adds `next/dynamic` turns a static import into a lazily fetched chunk that is requested when the component first renders: ```tsx 'use client' import dynamic from 'next/dynamic' const Chart = dynamic(() => import('./Chart'), { loading: () => <p>Loading chart…</p>, }) ``` The factory must be a literal `import()` the bundler can see statically — a computed specifier gives it nothing to split on. If the module exports the component by name rather than as a default, resolve it in the factory: ```tsx const Chart = dynamic(() => import('./Chart').then((mod) => mod.Chart)) ``` Two options matter. `loading` supplies the placeholder shown while the chunk is in flight — give it roughly the height of the real thing so the page does not jump when it arrives. `ssr: false` additionally tells Next not to render the component on the server at all, which is how you host browser-only libraries. ## It defers; it does not delete A dynamically imported module is still compiled, still deployed, and still downloaded by anyone who reaches the code path. The win is *when*, not *whether*. That has two consequences worth saying out loud in an interview: - **Total bytes can go up.** More chunks means more requests and a little duplicated boilerplate. Splitting a 4 kB component is pure overhead. - **The deferred fetch has to happen sometime**, and it happens at the moment the user asked for the thing. If that moment is a click, the spinner is visible latency you have moved rather than removed. Prefetching on hover or intent is the usual mitigation. ## When it is actually worth it Good candidates: a component that is large relative to the route (tens of kilobytes and up), that a minority of visitors on that route ever sees, and whose appearance is already gated behind an interaction — modals, drawers, editors, maps, charts, video players, admin-only panels. Bad candidates: anything above the fold, anything on the critical render path, and anything small. Also note the pull in the other direction: if two routes each dynamically import the same heavy module, the bundler will usually put it in a chunk they share, so the first route to load it pays and the second gets it from cache. ## Checking your work The honest check is the build output before and after: did this route's First Load JS actually drop? If it did not, the module is still reachable from something that loads eagerly — a static import elsewhere in the same route's graph will keep it in the eager chunk no matter how many `dynamic()` wrappers point at it. `@next/bundle-analyzer` shows which chunk it landed in and what dragged it there.

  • If splitting is automatic per route, why does the build output still report a chunk shared by every route?
    Because some code genuinely is common: the framework runtime, the root layout's client components, and modules imported from enough places that the bundler hoists them instead of duplicating them per route. That baseline is downloaded once and cached, so it counts toward every route's first load but only costs the user on the first page they visit.
  • Does wrapping a component in next/dynamic remove it from the deployed build?
    No. It is still compiled and shipped as its own chunk; you have only changed when the browser requests it. Anyone who triggers the code path downloads it then. If your goal is to stop shipping the code at all, the answer is deleting the dependency or moving the work to the server — not a lazy import.
  • What happens if you pass a computed module path to next/dynamic, such as dynamic(() => import(`./widgets/${name}`))?
    The bundler can no longer identify a single target, so it either fails or bundles every module matching the pattern into the split — usually the opposite of what you wanted. Keep the specifier a literal string, and use an explicit map from key to a distinct `dynamic()` call when you need to choose at runtime.

saying these in an interview costs you the question

  • Thinks you must hand-configure webpack splitChunks in Next
  • Says next/dynamic reduces the total JavaScript shipped
  • Believes a conditional render keeps an imported module out of the bundle
  • Wraps small components in dynamic() and calls it an optimization
  • Confuses lazy-loading code with lazy-loading data

context

open as a page

In a Next.js app, what does importing a font from `next/font/google` do that a `<link>` to fonts.googleapis.com does not — where does the browser actually fetch the font file from at runtime?

level: juniorimportance: must knowfreq 70%

basics

~20 s

next/font/google downloads the font files at build time and serves them from your own origin, so the browser never contacts Google. The build generates the @font-face CSS, and the extra DNS, TLS and stylesheet round trips disappear.

open as a page

In a Next.js App Router app, what does the `<Image>` component from `next/image` do that a plain `<img>` tag does not, and why does it refuse to render without width and height (or `fill`)?

level: juniorimportance: must knowfreq 80%

basics

~20 s

next/image generates a responsive srcset, negotiates a modern format such as WebP or AVIF, lazy-loads by default, and requires intrinsic dimensions so the browser can reserve layout space before the bytes arrive, which prevents layout shift.

open as a page

In a Next.js App Router app, what does the `ssr: false` option on `next/dynamic` change about where a component renders, and why does the build reject that option inside a Server Component?

level: middleimportance: must knowfreq 64%

basics

~20 s

With ssr: false, Next.js skips the component during server rendering and mounts it only in the browser after hydration, rendering the loading fallback in its place meanwhile. The App Router rejects the option in a Server Component because opting out of server rendering is a client-side decision.

open as a page

In a Next.js app you set `output: 'standalone'` in next.config and build a Docker image from the result. What does that build produce, and what do you have to copy into the runtime image yourself?

level: middleimportance: must knowfreq 62%

basics

~20 s

It emits a self-contained .next/standalone folder with a server.js and only the traced node_modules the server needs, so the image runs node server.js without the full project. It does not include public or .next/static — copy those in yourself.

open as a page

A Next.js app's `next.config.ts` has a custom `webpack()` function that adds an SVGR rule so `.svg` files import as React components. After the team switches to Turbopack, the rule stops applying. Why, and how do you express that rule for Turbopack?

level: middleimportance: must knowfreq 46%

basics

~20 s

The webpack() function in next.config is only read by webpack, so Turbopack ignores it entirely and Next warns rather than silently honouring it. Port the rule to the turbopack.rules key, which maps a file glob to a list of loaders — a supported subset of webpack loaders, with no plugin equivalent.

open as a page

A Next.js app's First Load JS has grown by roughly 150 kB over a sprint and nobody knows which change did it. How would you use @next/bundle-analyzer to find the cause, and what do you typically find?

level: seniorimportance: must knowfreq 58%

basics

~20 s

Wire @next/bundle-analyzer into next.config, build with ANALYZE=true, and read the client treemap: find the largest unexpected block, then trace which module pulled it in. The usual culprits are a barrel import, a heavy dependency added to a shared client component, and a library dragged across the client boundary.

open as a page

In a Next.js project, what is the difference between running `next dev` and running `next build` followed by `next start`, and why can you not judge caching or bundle size from `next dev`?

level: juniorimportance: should knowfreq 60%

basics

~20 s

next dev compiles routes on demand with optimizations and caching turned off for fast feedback. next build produces the optimized production artifacts once, and next start serves them without compiling. Only the build-and-start pair reflects real caching, prerendering and bundle sizes.

open as a page

In a Next.js project, what is Turbopack, and what does running `next dev --turbopack` do differently from running the dev server on webpack?

level: juniorimportance: should knowfreq 58%

basics

~20 s

Turbopack is Next.js's Rust-based bundler, built to replace webpack for both the dev server and production builds. next dev --turbopack starts the dev server on Turbopack; in Next 16 Turbopack is the default for dev and build, and --webpack opts back out.

open as a page

After running `next build` on a Next.js app, the route table prints a "First Load JS" column per route plus a "First Load JS shared by all" line. What does each number actually count?

level: middleimportance: should knowfreq 50%

basics

~20 s

First Load JS is all the JavaScript a browser downloads to render that route on a cold visit: the route's own chunks plus the shared chunks every route needs. The "shared by all" line breaks out that common baseline, which is included in every route's figure.

open as a page

A Next.js root layout calls `Inter({ subsets: ['latin'], variable: '--font-inter' })` from `next/font/google` and assigns the result to `inter`. What is on that returned object, and which property do you use to make the font available to arbitrary CSS rules rather than to one element?

level: middleimportance: should knowfreq 55%

basics

~20 s

The loader returns an object with className and style, plus variable when you pass the variable option. className applies the font family directly to an element; variable is a class that only defines the CSS custom property, which any CSS rule can then reference.

open as a page

In a Next.js App Router app, calling `Inter({ subsets: [chosenSubset] })` from `next/font/google` inside a component body fails the build, even though `chosenSubset` holds a valid string. Why does next/font insist the loader be called at module scope with literal arguments?

level: middleimportance: should knowfreq 42%

basics

~20 s

next/font is a compile-time transform, not a runtime function. The compiler must statically read the options to fetch and emit the right font files during the build, so it only accepts a call at module scope whose arguments are literals it can evaluate without running the code.

open as a page

In next/image, when do you reach for the `fill` prop instead of width and height, what must be true of the parent element, and why does a `fill` image usually also need a `sizes` prop?

level: middleimportance: should knowfreq 62%

basics

~20 s

Use fill when the intrinsic dimensions are unknown; it absolutely positions the image to fill the nearest positioned ancestor, which must have position: relative and a real height. sizes then tells the browser which srcset candidate to download.

open as a page

What does the `priority` prop on a next/image `<Image>` change about how the browser loads that image, and why is putting it on every image on the page counterproductive?

level: middleimportance: should knowfreq 56%

basics

~20 s

priority turns off lazy loading for that image, marks the fetch high priority, and preloads it from the document head. Reserve it for the LCP image, because preloading everything makes requests compete and helps nothing.

open as a page

A Next.js page renders `<Image src="https://cdn.example.com/a.jpg" width={800} height={600} alt="" />` and fails with an error saying that hostname is not configured under images in next.config. What is the fix, and why does Next require that configuration at all?

level: middleimportance: should knowfreq 54%

basics

~20 s

Add the host to images.remotePatterns in next.config. Next blocks unlisted hosts because /_next/image would otherwise be an open image proxy: anyone could make your server fetch, transform and serve arbitrary remote files under your own origin.

open as a page

A Next.js App Router team sets `output: 'export'` in next.config so the app can be deployed to a plain static file host. Which capabilities stop working, and what has to change in the code?

level: middleimportance: should knowfreq 50%

basics

~20 s

Static export emits plain files into out/ with no server at runtime, so anything needing a request or a live server is gone: middleware, Server Actions, cookies/headers, draft mode, ISR and revalidation, and the default image optimizer. Every dynamic route must be enumerated at build time.

open as a page

Turbopack is described as compiling incrementally and on demand in `next dev`. What work happens when, and why can the first visit to a route still take a noticeable moment even though the dev server started instantly?

level: middleimportance: should knowfreq 42%

basics

~20 s

Turbopack's dev server compiles nothing up front: it compiles a route the first time you request it, so a cold route pays for its own module graph. After that, an edit recomputes only the cached units whose inputs changed and pushes an update, which is why edits feel instant while first visits do not.

open as a page

A team wraps a heavy charting component in `next/dynamic`, but the Next.js build output shows the route's First Load JS unchanged and the analyzer still puts the charting library in a chunk fetched on first paint. What explains that, and how would you confirm it?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Something else still imports that module eagerly. A lazy import only defers a module nothing else reaches statically, so one ordinary import elsewhere in the route's graph — a barrel, a type-and-value import, a shared component — keeps the library in the eager chunk regardless of the dynamic wrapper.

open as a page

A Next.js page still shows a visible text reflow shortly after load even though its typeface comes from `next/font/google`. Which layout-shift protections does next/font apply on its own, and where do they stop helping?

level: seniorimportance: should knowfreq 48%

basics

~20 s

next/font defaults font-display to swap and, for Google fonts, generates a metric-adjusted fallback face so the fallback occupies almost the same space as the real font. Shift returns when you disable that adjustment, supply a local font without usable metrics, or load a face outside next/font.

open as a page

A Next.js app declares a `next/font/google` face in its root layout, and every route's HTML contains a preload link for that font's woff2. What decides which routes preload a next/font file, and how would you stop it?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Preload hints are emitted for the routes whose module graph uses the font, and a face declared in the root layout is used by every route. Move the declaration into the modules that need it, or pass preload: false on the loader call.

open as a page

A self-hosted Next.js app running `next start` in a container shows CPU spikes and slow image responses for the first minutes after every deploy, then settles down. How does the built-in image optimizer explain that pattern, and what levers do you have?

level: seniorimportance: should knowfreq 44%

basics

~20 s

next/image transforms images on request and caches them on disk under .next/cache/images, so a fresh container recomputes every size and format variant. Cut the number of variants, persist or share the cache, or move transforms to an external loader.

open as a page

A self-hosted Next.js App Router app runs three container replicas behind a load balancer. An ISR page shows fresh data on some refreshes and stale data on others, and a `revalidateTag` call after a mutation seems to work only sometimes. What is happening, and how do you fix it?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Each replica keeps its own cache on its own filesystem, so the three containers hold independent copies of the page and revalidation only invalidates the replica that handled the request. Fix it by pointing all replicas at one shared cache through a custom cache handler.

open as a page

Your team wants to move a large Next.js app's production builds from webpack to Turbopack. How would you validate that switch before you ship on it?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Build the same commit with both bundlers and compare: does every route compile, are the emitted routes and their sizes equivalent, do source maps still reach your error tracker, and does the built app behave the same under smoke tests. Ship behind the ability to fall back to webpack, then watch build times and runtime errors.

open as a page

For a new Next.js product you have to choose how images are served: the built-in optimizer behind `next/image`, a custom loader pointing at a dedicated image service, or pre-sized assets rendered with `unoptimized`. How would you make that call?

level: principalimportance: should knowfreq 32%

basics

~20 s

Decide by who performs and pays for each transform, and by who controls the image URL. The built-in optimizer is zero-config but runs on your servers, a custom loader hands transformation to an external service, and unoptimized assets skip the step entirely.

open as a page

Your team is deciding whether to run a Next.js App Router app on a managed Next.js platform or self-host it as a container on your own infrastructure. What responsibilities move to you when you self-host, and how would you make the call?

level: principalimportance: nice to knowfreq 36%

basics

~20 s

Self-hosting runs identical application code but hands you the operational layer a managed platform supplied: a shared regeneration cache, static asset delivery, the image optimizer's cost, deploy atomicity and observability. Decide on constraints and team capacity, not on framework features.

open as a page

A Next.js app depends on a webpack plugin that has no Turbopack equivalent, so the team cannot adopt Turbopack builds. As the lead, how do you decide between staying on webpack, replacing the tooling, or splitting the pipeline?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

Price both sides: what the faster builds and dev loop are worth across the team, against the cost of replacing the plugin's function or maintaining a split pipeline. Then pick an option with an explicit review date, because webpack is the legacy path in Next 16 and staying on it is a decision that expires.

open as a page