6. Next.js Interview Questions
Grouped Basic → Intermediate → Advanced. Answers target Next.js 16, with the Next 14/15 behaviour noted where it differs — interviewers ask both.
Basic
1. What is Next.js and why use it over plain React?
A React framework that adds routing, server rendering, a server runtime, bundling and deployment on top of React.
Plain React gives you an empty HTML shell — bad SEO, slow first paint, no routing, no backend. Next.js gives you file-system routing, SSR/SSG/ISR/Server Components, Route Handlers, automatic code splitting, and image/font optimisation out of the box.
2. What is file-system based routing?
Folders in app/ become URL segments; a page.tsx makes the route accessible.
app/blog/[slug]/page.tsx → /blog/:slugNo route config file to maintain.
3. What are the special files in the App Router?
| File | Purpose |
|---|---|
page.tsx | Route UI |
layout.tsx | Shared shell, preserves state across navigation |
template.tsx | Like layout but remounts every navigation |
loading.tsx | Suspense fallback |
error.tsx | Error boundary (must be a Client Component) |
not-found.tsx | 404 UI |
route.ts | API endpoint |
default.tsx | Parallel-route fallback — required in Next 16 |
4. layout.tsx vs template.tsx?
A layout does not re-render when you navigate between its children — scroll position and state survive. A template creates a new instance every navigation.
Use a template when you need an enter animation or a per-route state reset.
5. What are the dynamic route patterns?
| Pattern | Matches | params |
|---|---|---|
[id] | /posts/1 | { id: "1" } |
[...slug] | /shop/a/b | { slug: ["a","b"] } |
[[...slug]] | /shop and /shop/a/b | optional catch-all |
6. What is a route group?
A folder in parentheses — (marketing) — that organises files without adding a URL segment. Lets you give different sections different layouts while keeping clean URLs.
A folder prefixed with an underscore — _components — is a private folder, excluded from routing entirely.
7. next/link vs an anchor tag?
<Link> does client-side navigation — no full page reload, state preserved — and automatically prefetches routes entering the viewport. An <a> triggers a full document request and throws away all client state.
Next 16 improved prefetching with layout deduplication (a shared layout downloads once for 50 links, not 50 times) and incremental prefetching.
8. How do you navigate programmatically?
"use client";
import { useRouter } from "next/navigation"; // NOT next/router
const router = useRouter();
router.push("/dashboard");
router.replace("/login");
router.refresh(); // re-fetch server data, keep client stateServer-side: redirect() and notFound() from next/navigation.
Common bug: next/router is the Pages Router. next/navigation is the App Router.
9. What does next/image give you?
Automatic WebP/AVIF conversion, responsive srcset, lazy loading by default, and a reserved layout box that prevents CLS.
priority disables lazy loading for above-the-fold images. fill + sizes handles unknown dimensions.
Next 16 default changes: minimumCacheTTL is 4 hours (was 60s), qualities defaults to [75], redirects capped at 3. Use images.remotePatterns — images.domains is deprecated.
10. What does next/font do?
Downloads fonts at build time and self-hosts them. No request to Google at runtime (better privacy and latency) and zero layout shift, because Next.js computes a matching fallback metric automatically.
11. What is metadata and generateMetadata?
export const metadata = { title: "Blog", description: "…" };
export async function generateMetadata({ params }) {
const { slug } = await params;
const post = await getPost(slug);
return { title: post.title, openGraph: { images: [post.image] } };
}Static export for fixed values, generateMetadata for per-route data. A template: "%s | DDocs" in the root layout composes child titles.
12. How do environment variables work?
Only variables prefixed NEXT_PUBLIC_ reach the browser — and they are inlined into the bundle at build time, so they are permanently public and require a rebuild to change.
Everything else is server-only and safe for secrets.
serverRuntimeConfig and publicRuntimeConfig were removed in Next 16 — use .env files.
Intermediate
13. Server Components vs Client Components?
| Server Component | Client Component | |
|---|---|---|
| Directive | none (default) | "use client" |
| JS shipped to browser | Zero | Yes |
| Hooks / events | No | Yes |
Direct DB, secrets, fs | Yes | Never |
async component | Yes | No |
| Browser APIs | No | Yes |
Everything in app/ is a Server Component by default.
14. When do you need "use client"?
State (useState, useReducer), effects, context, event handlers, browser APIs (window, localStorage), and browser-only libraries.
The key nuance: "use client" marks a boundary, not a file. Everything a Client Component imports also becomes client-side. Push it as far down the tree as possible — one "use client" in the root layout ships your whole app to the browser.
15. Can a Client Component render a Server Component?
Not by importing it. But it can render one passed as children:
// Server Component page
<ClientWrapper>
<ServerThing /> {/* stays server-rendered */}
</ClientWrapper>This composition pattern is the standard answer for mixing the two.
16. Server Components vs SSR — aren't they the same?
No, and this is a favourite trap.
SSR renders your client components to HTML on the server, then ships their JavaScript so React can hydrate them. The JS still goes over the wire.
Server Components never ship that JavaScript at all. Their output is a serialised RSC payload, not a hydratable component.
You can use both on the same page.
17. Explain SSG, SSR, ISR and PPR.
| Rendered | Use for | |
|---|---|---|
| SSG | Build time | Blogs, docs, marketing |
| SSR | Every request | Personalised, always-fresh pages |
| ISR | Build time, regenerated in the background | Large content sites |
| PPR | Static shell + streamed dynamic holes | Both at once — the Next 16 default |
PPR removed the all-or-nothing choice. Before it, one cookies() call made the whole page dynamic. Now the static shell ships from the CDN instantly and the dynamic parts stream in behind their <Suspense> fallbacks.
18. How has caching changed in Next.js 16?
The single biggest change: caching flipped from opt-out to opt-in.
Next 13–15: fetch was cached by default. You opted out with cache: "no-store" or dynamic = "force-dynamic". People shipped accidentally-static pages serving stale data.
Next 16: nothing is cached. All dynamic code runs at request time. You opt in with the "use cache" directive under cacheComponents: true.
async function BlogPosts() {
"use cache";
cacheLife("hours");
cacheTag("posts");
const posts = await fetch(url).then((r) => r.json());
return <PostList posts={posts} />;
}Arguments and closed-over values automatically become part of the cache key.
19. revalidateTag vs updateTag vs refresh?
| API | Semantics | Use for |
|---|---|---|
revalidateTag(tag, profile) | Stale-while-revalidate — serve cached now, refresh in background | Content tolerating eventual consistency |
updateTag(tag) | Read-your-writes — expire and re-read in the same request | Forms and settings — user must see their own change |
refresh() | Refresh uncached data only | Notification counts, live metrics |
In Next 16 revalidateTag requires a cacheLife profile as the second argument: revalidateTag("posts", "max"). The single-argument form is deprecated.
updateTag and refresh are Server-Actions-only.
20. What are Server Actions?
Async functions marked "use server" that run on the server and can be called directly from a component or passed to <form action={…}>.
"use server";
export async function createPost(formData: FormData) {
const session = await auth();
if (!session) throw new Error("Unauthorized");
const parsed = schema.safeParse(Object.fromEntries(formData));
if (!parsed.success) return { error: parsed.error.flatten() };
await db.post.create({ data: parsed.data });
revalidatePath("/blog");
}Biggest advantage: progressive enhancement. <form action={serverAction}> works with JavaScript disabled, because Next.js generates a real POST endpoint behind it.
21. Are Server Actions secure?
No, not by themselves — and saying this unprompted scores well.
A Server Action compiles to a public HTTP endpoint with a generated ID. Anyone can call it directly with any payload. So:
- Authenticate and authorise inside the action
- Validate every input with Zod — never trust
formData - Hiding the button in the UI protects nothing
22. Server Actions vs Route Handlers — when do you use which?
| Server Action | Route Handler |
|---|---|
| Form submissions in your own app | A public REST API |
| Mutations tied to a component | Mobile apps, third parties |
| Progressive enhancement wanted | Webhooks |
revalidatePath right there | Custom headers, status codes, streaming |
One-liner: "Server Actions for mutations inside my app; Route Handlers when something outside the app calls it."
23. How do Route Handlers work?
A route.ts exporting one function per HTTP method:
export async function GET(request: NextRequest) {
return NextResponse.json(await db.user.findMany());
}
export async function POST(request: NextRequest) {
const body = await request.json();
return NextResponse.json(await create(body), { status: 201 });
}route.ts and page.tsx cannot coexist in the same folder — both claim the URL.
24. Why is params a promise now?
Since Next 15, params, searchParams, cookies(), headers() and draftMode() are async.
const { slug } = await params;
const theme = (await cookies()).get("theme")?.value;The reason is Partial Prerendering. Making them promises lets Next.js prerender the static shell without knowing the request, then resolve request-specific values later. Sync access was removed in Next 16.
25. What is loading.tsx and how does streaming work?
loading.tsx automatically wraps page.tsx in a <Suspense> boundary. The shared layout renders instantly while the page streams in.
Streaming means the server sends HTML in chunks as they become ready, so the slowest query no longer blocks the entire page. Use manual <Suspense> boundaries for finer control.
26. How do you handle errors?
error.tsx per segment — must be a Client Component, receives error and reset. It catches errors in that segment and below, but not in its own layout. global-error.tsx catches root layout errors and must render its own <html> and <body>.
notFound() renders not-found.tsx.
27. What is proxy.ts?
Next 16 renamed middleware.ts to proxy.ts, and it runs on the Node.js runtime. Same logic, clearer name — it makes the network boundary explicit.
export default function proxy(request: NextRequest) {
const token = request.cookies.get("token");
if (!token) return NextResponse.redirect(new URL("/login", request.url));
return NextResponse.next();
}
export const config = { matcher: ["/dashboard/:path*"] };middleware.ts still works for Edge cases but is deprecated.
Keep it light — it runs on every matched request. Check for a token there; do the real database session lookup in the page.
28. Edge runtime vs Node.js runtime?
| Node.js | Edge | |
|---|---|---|
| Cold start | Slower | Very fast |
| Node APIs | Full | Web APIs only |
| DB drivers | All | HTTP-based only (Neon, PlanetScale, Upstash) |
| Best for | Business logic, ORM | Auth checks, geo, redirects |
29. What are parallel and intercepting routes?
Parallel routes (@folder) render several pages in the same layout at once — a dashboard with independent analytics and team panes, each with its own loading and error state. Next 16 requires an explicit default.tsx per slot or the build fails.
Intercepting routes ((.)folder) show a route in a modal when navigated to in-app, but as a full page on direct visit or refresh. This is the Instagram photo-modal pattern.
30. How do you avoid a data fetching waterfall?
// ❌ Sequential
const user = await getUser(id);
const posts = await getPosts(id);
// ✅ Parallel
const [user, posts] = await Promise.all([getUser(id), getPosts(id)]);Only go sequential when the second call genuinely needs the first result. For independent slow sections, give each its own <Suspense> boundary so they stream separately.
Advanced
31. Walk me through what happens on a request in Next.js 16.
proxy.tsruns (if the path matches) — auth, rewrites, redirects- Next.js matches the route segment
- The static shell — static markup plus
"use cache"results — is served immediately, from the CDN if prerendered - Server Components render on the server; anything uncached or reading runtime APIs streams behind its
<Suspense>fallback - The RSC payload streams to the browser alongside the HTML
- Client Components hydrate; only their JavaScript is downloaded
- Subsequent
<Link>navigations fetch only the changed RSC payload, reusing the deduplicated layout
32. Where does cached content actually live?
A cached result is serialised into an RSC payload and can live in three places:
| Store | Detail |
|---|---|
| Prerendered HTML | On disk when self-hosting, or CDN-backed storage |
| Shared store | In-memory per instance by default (ephemeral on serverless); "use cache: remote" for a durable shared cache handler |
| Browser | Sent with the RSC payload on navigation/prefetch; "use cache: private" lives only here |
All are scoped to a single deployment — the cache key includes the build ID, so a new deploy starts cold. That prevents a new build from serving markup from the old one.
33. What is "maximising the static shell"?
Push async work deeper into the tree so more of the page can be prerendered.
// ❌ Awaiting params at the top blocks the whole layout
export default async function Layout({ children, params }) {
const { slug } = await params;
return <div><Sidebar />{slug}{children}</div>;
}
// ✅ Await inside a boundary
export default function Layout({ children, params }) {
return (
<div>
<Sidebar />
<Suspense fallback={<h1>Loading…</h1>}>
{params.then(({ slug }) => <h1>{slug}</h1>)}
</Suspense>
{children}
</div>
);
}Now Sidebar, children and the fallback are all in the static shell.
34. How does ISR work under Cache Components?
generateStaticParams prerenders the URLs you list at build time. Any other URL is served the reusable App Shell instantly, then upgraded in the background with its now-known params and cached for the next visitor.
Under the old model, ISR was revalidate: 60 on getStaticProps — serve stale, regenerate in the background, swap in.
35. What is "use cache: private" vs "use cache: remote"?
"use cache: private"— gives a lifetime to a function that readscookies(),headers()orsearchParamsdirectly. The result is cached in the browser only, per session."use cache: remote"— moves the result to a durable cache handler shared across server instances. A network round-trip, so it only pays off at a high hit rate.- Plain
"use cache"— in-memory per instance by default, which is ephemeral on serverless.
36. What is hydration and what causes a mismatch?
Hydration is the client attaching event listeners to server-rendered HTML rather than rebuilding the DOM.
A mismatch occurs when the server HTML differs from the first client render. Causes: Date.now(), Math.random(), crypto.randomUUID(), window/localStorage, locale formatting, browser extensions.
Cache Components makes this explicit — the dev overlay surfaces blocking-prerender-random / blocking-prerender-current-time insights. The fix is await connection() before the call plus a <Suspense> boundary (unique per request), or "use cache" (shared across users).
37. How do you handle authentication in Next.js?
- Login route sets an
httpOnly,secure,sameSitecookie — notlocalStorage proxy.tsdoes a cheap presence check and redirects unauthenticated users- The real session lookup happens in a Server Component or Route Handler, ideally through a cached
getSession() - Every Server Action and Route Handler re-checks authorisation — the proxy is not a security boundary
- Server Components read the session and never send it to the client
Common libraries: Auth.js (NextAuth), Clerk, Lucia.
Why not localStorage: any XSS on your site reads it. An httpOnly cookie is invisible to JavaScript.
38. How would you reduce the bundle size of a Next.js app?
@next/bundle-analyzerfirst — find the actual weight- Move components out of
"use client"where they don't need it - Push
"use client"down the tree next/dynamicwithssr: falsefor browser-only libraries (charts, editors, maps)- Replace heavy dependencies (moment → date-fns or
Intl) - Verify tree shaking — deep imports over barrel files where the library isn't ESM-friendly
next/fontandnext/imageinstead of hand-rolled versions- Check
First Load JSper route in the build output
39. What is the React Compiler and how does Next.js use it?
Stable since React Compiler v1.0 (October 2025). It automatically memoises components and values at build time, replacing manual useMemo / useCallback / React.memo.
In Next.js 16 the reactCompiler option is promoted from experimental to stable, but is not on by default — it relies on Babel, so compile times increase.
const nextConfig = { reactCompiler: true };40. What changed with Turbopack?
Turbopack is stable and the default bundler in Next.js 16 — 2–5× faster production builds and up to 10× faster Fast Refresh.
Opt out with next dev --webpack / next build --webpack if you have a custom webpack setup. Filesystem caching for dev is available behind experimental.turbopackFileSystemCacheForDev.
41. What was removed or deprecated in Next.js 16?
Removed: AMP support, the next lint command (use ESLint or Biome directly), serverRuntimeConfig / publicRuntimeConfig, experimental.ppr and experimental.dynamicIO flags, sync params / searchParams / cookies() / headers().
Deprecated: middleware.ts (→ proxy.ts), next/legacy/image, images.domains (→ remotePatterns), single-argument revalidateTag().
Behaviour changes: Turbopack default, new next/image defaults, parallel routes require default.tsx, revalidateTag needs a profile argument.
Requirements: Node 20.9+, TypeScript 5.1+.
42. How do you migrate from Pages Router to App Router?
Both routers coexist — app/ wins on conflicting paths — so migrate incrementally:
- Add
app/alongsidepages/ - Start with the lowest-risk static page
- Mirror
_appproviders inapp/layout.tsx getServerSideProps→asyncServer ComponentgetStaticProps→"use cache"+generateStaticParamsnext/router→next/navigation;router.query→await params- Add
"use client"only where hooks or handlers exist - Move
pages/api/*toroute.tslast
Honest answer to "should you?": incrementally, for pages that gain from server rendering or nested layouts — and not at all for a stable app nobody touches. A rewrite with no user-visible benefit is hard to justify.
43. How do you decide the rendering strategy for a page?
Reason out loud rather than naming an acronym:
| Page | Choice | Why |
|---|---|---|
| Marketing homepage | Cached, long life | Same for everyone, changes rarely |
| Blog post | Cached + cacheTag, purged on publish | Editorial update cycle |
| Product listing | Cached hourly + tag purge | Tolerates minutes of staleness |
| Product page with live stock | Static shell + streamed stock widget | PPR — fast shell, fresh number |
| User dashboard | Uncached behind Suspense | Personalised, must be fresh |
44. What happens to bots and crawlers with PPR?
Browsers get the static shell instantly. Bots are detected by user agent and handled differently — because they need a complete document, Next.js skips the shell and renders the whole page dynamically at request time, sending finished HTML.
The gotcha: work that succeeded at build time now runs at request time for a crawler. If your shell depends on build-only data, a page that loads fine for a person can fail for Googlebot. Make sure shell data is available at request time too.
45. How do you self-host Next.js properly?
const nextConfig = { output: "standalone" };Standalone traces only the node_modules actually used — image size drops from ~1 GB to ~150 MB. Use a multi-stage Dockerfile, npm ci, a non-root user, and Node 20.9+.
What you take on yourself: a shared ISR cache handler across instances, image optimisation CPU cost, a CDN in front of /_next/static, health checks for zero-downtime rollouts, and your own logging.
Say the trade-off. "Self-hosting is cheaper" is only half true.
46. What do you check before deploying to production?
- Build output reviewed — no route with surprising First Load JS
- Bundle analysed
- No secret behind
NEXT_PUBLIC_ - Every Server Action authenticates and validates internally
- Route Handlers validate input with Zod
- Auth cookies
httpOnly+secure+sameSite - Security headers set (HSTS,
X-Frame-Options,nosniff, Referrer-Policy) images.remotePatternsrestricted — a wildcard makes your image endpoint an open proxy- Rate limiting on auth and mutation endpoints
- Error responses generic; details logged server-side only
- Core Web Vitals checked