- Modern Next.js interviews are about the App Router: file-based layouts, Server Components by default, and Client Components marked with
"use client". - Know the rendering spectrum: static (SSG), ISR, dynamic SSR, streaming, client rendering, and what makes a route dynamic.
- Caching is the hardest topic. Learn the layers (request memoization, data cache, full route cache, client router cache) and that Next.js 15 made fetch uncached by default.
- Server Actions are public POST endpoints: validate and authorize inside them.
- Version awareness wins: async
paramsandcookies()in 15, Turbopack as the default bundler and middleware renamed toproxyin 16. State the version you are describing.
Next.js has changed more in the last three major versions than most frameworks change in a decade. That creates a specific interview problem: many candidates answer with Pages Router concepts or Next.js 13 caching behaviour, and interviewers who ship on the current version notice immediately. This guide focuses on the App Router and React Server Components while noting where behaviour changed between versions, so you can answer precisely and say which version you mean.
Code examples use TypeScript and the App Router file conventions. If React fundamentals (hooks, reconciliation, state) are shaky, review our React and JavaScript interview questions first; Next.js questions assume them.
What Next.js Interviews Ask
| Level | Focus |
|---|---|
| Fresher / junior frontend | Why Next.js over plain React, file-based routing, SSR vs CSR, next/link and next/image |
| Mid-level (2-4 years) | Server vs Client Components, data fetching and caching, layouts and streaming, Server Actions, middleware, SEO metadata |
| Senior / full-stack | Caching strategy and invalidation, rendering choices per route, auth architecture, self-hosting and ISR across instances, Core Web Vitals |
Next.js Fundamentals: Questions 1-6
Q1. What is Next.js and why use it instead of plain React?
Next.js is a React framework that adds what React alone does not provide: file-based routing, server rendering and static generation, data fetching on the server, API endpoints (route handlers), image, font and script optimization, code splitting per route, and a production build pipeline. The main reasons teams choose it are SEO and first-load performance (HTML arrives already rendered) and full-stack colocation (server code and UI in one project). A client-only React app sends an almost empty HTML page and renders everything in the browser.
Q2. App Router vs Pages Router?
Pages Router (pages/) | App Router (app/) | |
|---|---|---|
| Default component type | Client components (all hydrate) | Server Components |
| Data fetching | getServerSideProps, getStaticProps, getStaticPaths | async components with fetch or direct DB calls |
| Layouts | _app and manual layout patterns | Nested layout.tsx files that persist across navigation |
| Loading / errors | Manual | loading.tsx, error.tsx, streaming with Suspense |
| Mutations | API routes | Server Actions and route handlers |
Both can coexist in one project during migration. The App Router became stable in 13.4 and is the recommended approach for new apps.
Q3. What does each special file in the app directory do?
page.tsx: the UI for a route; makes the segment publicly routable.layout.tsx: shared UI that wraps child segments and persists across navigations (state is kept, it does not re-render). The root layout must render<html>and<body>.template.tsx: like a layout but re-mounted on every navigation (useful for enter animations or per-page effects).loading.tsx: a loading UI automatically wrapped in a Suspense boundary around the page.error.tsx: an error boundary for the segment; must be a Client Component and receiveserrorandreset.not-found.tsx: rendered whennotFound()is called or a route does not match.route.ts: an HTTP endpoint (route handler) exportingGET,POSTand so on. A folder cannot have bothpage.tsxandroute.ts.
Q4. What is hydration, and what causes hydration errors?
Hydration is React attaching event handlers and state to server-rendered HTML in the browser, so it becomes interactive. React expects the first client render to produce the same markup as the server. Mismatches cause hydration errors. Common causes: rendering Date.now(), Math.random() or locale-formatted dates directly; reading window or localStorage during render; invalid HTML nesting (a <div> inside a <p>); and browser extensions modifying the DOM. Fixes: move browser-only values into useEffect, render them only after mount, or load the component with next/dynamic and ssr: false from a Client Component.
Q5. What do next/link and prefetching do?
<Link> performs client-side navigation without a full page reload, preserving shared layouts and state. In production, links in the viewport are prefetched: for static routes the route payload is prefetched; for dynamic routes, by default, only the part down to the nearest loading.tsx boundary is prefetched, so navigation shows the loading state instantly. Use prefetch={false} for links to rarely visited heavy pages.
Q6. How do environment variables work in Next.js?
Variables in .env files are available on the server via process.env. Only variables prefixed with NEXT_PUBLIC_ are inlined into client bundles at build time. Two follow-ups: never put secrets in NEXT_PUBLIC_ variables, and because public variables are inlined at build, changing them requires a rebuild, which matters when one Docker image is promoted across environments.
App Router and Routing: Questions 7-14
Q7. How do dynamic routes work?
app/blog/[slug]/page.tsxmatches/blog/anything.app/shop/[...path]/page.tsxis a catch-all:/shop/a/b/cgivespath = ["a", "b", "c"].app/docs/[[...path]]/page.tsxis an optional catch-all that also matches/docs.
import { notFound } from "next/navigation";
import type { Metadata } from "next";
type Props = { params: Promise<{ slug: string }> }; // params is async in Next.js 15+
async function getPost(slug: string) {
const res = await fetch(`https://cms.example.com/posts/${slug}`, {
next: { revalidate: 3600, tags: [`post:${slug}`] }, // ISR: refresh at most hourly
});
if (!res.ok) return null;
return res.json() as Promise<{ title: string; excerpt: string; html: string }>;
}
export async function generateStaticParams() {
const posts: { slug: string }[] = await fetch("https://cms.example.com/posts").then(r => r.json());
return posts.map(p => ({ slug: p.slug })); // prerender these at build time
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
const post = await getPost(slug); // deduplicated with the call in the page
return { title: post?.title ?? "Post not found", description: post?.excerpt };
}
export default async function BlogPost({ params }: Props) {
const { slug } = await params;
const post = await getPost(slug);
if (!post) notFound();
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.html }} />
</article>
);
}
Points to narrate: generateStaticParams prerenders known slugs; unknown slugs render on demand (unless dynamicParams = false, which returns 404 for them); the same fetch in metadata and page is deduplicated within one render; and since 15, params is a Promise. Only render CMS HTML you trust or have sanitized.
Q8. What are route groups?
Folders in parentheses, like app/(marketing)/about/page.tsx, organize routes without affecting the URL (/about). Use them to give different sections different root layouts (a marketing layout vs a dashboard layout), or simply to group related files.
Q9. What are parallel and intercepting routes?
Parallel routes use named slots (@analytics, @team) that a layout renders side by side, each with independent loading and error states; useful for dashboards. A default.tsx provides the fallback when a slot has no match. Intercepting routes ((.)photo/[id]) render a route within the current layout during client navigation, for example opening a photo in a modal over a feed, while a direct visit or refresh to /photo/123 shows the full page. Together they implement the "modal with its own shareable URL" pattern.
Q10. How do layouts behave during navigation?
On navigation, only the segments that change re-render. Shared layouts are preserved: they keep their state and are not re-fetched, which makes navigation fast. A consequence: a layout cannot access the current page's searchParams, and code in a layout should not assume it re-runs on every navigation. If you need that, use a template or move the logic into the page.
Q11. How do you navigate programmatically?
In Client Components, useRouter() from next/navigation (not next/router, which is Pages Router) provides push, replace, back, refresh and prefetch. usePathname() and useSearchParams() read the URL. On the server (Server Components, Server Actions, route handlers), call redirect() or permanentRedirect().
Q12. What are route handlers?
import { NextResponse, type NextRequest } from "next/server";
export async function GET(request: NextRequest) {
const status = request.nextUrl.searchParams.get("status") ?? "PAID";
const orders = await db.order.findMany({ where: { status }, take: 20 });
return NextResponse.json(orders);
}
export async function POST(request: NextRequest) {
const body = await request.json();
if (typeof body.sku !== "string" || !Number.isInteger(body.qty) || body.qty < 1) {
return NextResponse.json({ error: "invalid order" }, { status: 400 });
}
const order = await db.order.create({ data: { sku: body.sku, qty: body.qty } });
return NextResponse.json(order, { status: 201 });
}
They are built on the Web Request and Response APIs and replace Pages Router API routes. Use them for webhooks (payment gateway callbacks), endpoints consumed by mobile apps or third parties, and streaming responses. For mutations triggered by your own UI, Server Actions are usually simpler.
Q13. What is middleware (proxy) and what should it be used for?
Middleware runs before a request is matched to a route, so it can rewrite, redirect, set headers or cookies, or return a response early. Typical uses: redirecting unauthenticated users away from /dashboard, locale detection, A/B test bucketing, and bot filtering. In Next.js 16 the file convention was renamed from middleware.ts to proxy.ts to reflect its role at the network boundary; the concept is the same.
import { NextResponse, type NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const session = request.cookies.get("session")?.value;
if (!session && request.nextUrl.pathname.startsWith("/dashboard")) {
const login = new URL("/login", request.url);
login.searchParams.set("next", request.nextUrl.pathname);
return NextResponse.redirect(login);
}
return NextResponse.next();
}
export const config = { matcher: ["/dashboard/:path*"] };
In Next.js 16's proxy.ts, the exported function is named proxy. The key interview point: middleware is an optimistic, coarse check. Do not make it your only authorization layer. A 2025 vulnerability (CVE-2025-29927) allowed middleware to be bypassed with a crafted internal header on unpatched versions, a good illustration of why authorization must also happen where data is read and mutated.
Q14. How do you handle SEO metadata?
Export a static metadata object or an async generateMetadata function from a layout or page. Next.js merges metadata from parent to child segments and renders the tags in the head. File conventions handle favicon.ico, opengraph-image.tsx (generated OG images), sitemap.ts and robots.ts. Server-rendered metadata is a key reason SEO-heavy products such as e-commerce listings and blogs pick Next.js.
Rendering Modes: Questions 15-21
Q15. Explain CSR, SSR, SSG and ISR.
| Mode | When HTML is generated | Best for | Trade-off |
|---|---|---|---|
| CSR (client-side) | In the browser after JS loads | Highly interactive app screens behind login | Slow first paint, weak SEO |
| SSR (dynamic) | On the server, per request | Personalized or always-fresh pages (cart, account) | Server cost and latency per request |
| SSG (static) | At build time | Marketing pages, docs, blog posts | Stale until rebuilt |
| ISR | At build or first request, then regenerated in the background after a revalidate period or on demand | Product pages, listings with many pages that change occasionally | Briefly stale content (stale-while-revalidate) |
In the App Router these are not separate APIs; they emerge from how a route fetches data and which dynamic APIs it uses.
Q16. What makes a route dynamic in the App Router?
A route is rendered statically by default, and becomes dynamic when it uses request-time information: cookies(), headers(), searchParams, connection(), or uncached data fetches, or when you set export const dynamic = "force-dynamic". The build output marks each route as static or dynamic, and checking it is the first step when a page you expected to be static is slow.
Q17. How does ISR work and what is on-demand revalidation?
With time-based ISR (export const revalidate = 60 or fetch(url, { next: { revalidate: 60 } })), the first request after 60 seconds still receives the cached (stale) page, and triggers regeneration in the background; the next request receives the fresh page. On-demand revalidation invalidates immediately when data changes: call revalidateTag("post:my-slug") or revalidatePath("/blog/my-slug") from a Server Action or a webhook route handler that your CMS calls on publish. On-demand is usually better: fresh within seconds of a change, cached otherwise.
Q18. What is streaming and how does Suspense enable it?
Streaming sends HTML in chunks as each part is ready, instead of waiting for the slowest data. Wrap slow sections in <Suspense fallback={...}> (or add loading.tsx for the whole segment); the server sends the shell and fallbacks immediately and streams each section when its data resolves. A product page can show the title and price at once while reviews and recommendations stream in. This improves time to first byte and perceived speed.
import { Suspense } from "react";
export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const product = await getProduct(id); // fast, cached
return (
<main>
<h1>{product.name}</h1>
<p>₹{product.price}</p>
<Suspense fallback={<p>Loading reviews…</p>}>
<Reviews productId={id} /> {/* slow; streamed when ready */}
</Suspense>
</main>
);
}
async function Reviews({ productId }: { productId: string }) {
const reviews = await getReviews(productId);
return <ul>{reviews.map(r => <li key={r.id}>{r.text}</li>)}</ul>;
}
Q19. What is Partial Prerendering?
Partial Prerendering (PPR) combines a static shell with dynamic holes in a single route: the static parts are prerendered and served instantly from the edge, while Suspense-wrapped dynamic parts (a personalized cart count, for instance) stream in per request. It started as an experimental feature in Next.js 14 and evolved into the Cache Components model in Next.js 16. Describe the idea and check the current docs for its status and configuration before claiming production use.
Q20. How did data fetching work in the Pages Router, and how does it map to the App Router?
getStaticProps (build-time data, optionally with revalidate for ISR) maps to cached fetches in Server Components. getServerSideProps (per-request data) maps to dynamic rendering with uncached fetches or request APIs. getStaticPaths maps to generateStaticParams, and its fallback option roughly maps to dynamicParams. Interviewers ask this to check you can work in older codebases and plan a migration.
Q21. Static export: when and what are the limits?
With output: "export", Next.js generates static HTML, CSS and JS that any static host or CDN (S3, GitHub Pages) can serve without a Node server. You lose features that need a server: dynamic rendering, ISR, Server Actions, middleware, cookies and headers, and default image optimization. It fits documentation sites and marketing pages.
React Server Components: Questions 22-27
Q22. What are React Server Components?
Server Components run only on the server, either at build time or per request. They can be async, read from databases or the file system directly, and keep secrets and heavy libraries (markdown parsers, date libraries) off the client because their code is never sent to the browser. Their output is a serialized description of the UI (the RSC payload) that React merges into the page. They cannot use state, effects, event handlers or browser APIs.
Q23. When do you need a Client Component?
When the component needs interactivity or the browser: useState, useEffect, event handlers like onClick, browser APIs (localStorage, geolocation), or libraries that depend on them (most charting and animation libraries). Add "use client" at the top of the file. It marks a boundary: that module and everything it imports become part of the client bundle.
"use client";
import { useState, useTransition } from "react";
import { addToCart } from "./actions"; // a Server Action, callable from the client
export function AddToCart({ productId }: { productId: string }) {
const [qty, setQty] = useState(1);
const [pending, startTransition] = useTransition();
return (
<div>
<input type="number" min={1} max={10} value={qty}
onChange={e => setQty(Number(e.target.value))} />
<button disabled={pending}
onClick={() => startTransition(() => addToCart(productId, qty))}>
{pending ? "Adding…" : "Add to cart"}
</button>
</div>
);
}
Q24. Can a Client Component render a Server Component?
It cannot import one: anything imported into a client module becomes client code. But it can render Server Components passed to it as children or other props, because those are rendered on the server first and passed in as already-rendered output. This is the standard pattern for wrapping server-rendered content in a client-side provider or an interactive shell like a collapsible sidebar.
// app/layout.tsx (Server Component)
import { ThemeProvider } from "./theme-provider"; // "use client" inside
import { Sidebar } from "./sidebar"; // Server Component
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<ThemeProvider>
<Sidebar /> {/* still rendered on the server */}
{children}
</ThemeProvider>
</body>
</html>
);
}
Q25. What can be passed from a Server Component to a Client Component?
Only serializable props: strings, numbers, booleans, null, plain objects and arrays, Dates, Maps and Sets, Promises (which the client can unwrap with React's use), JSX, and Server Actions. You cannot pass regular functions, class instances or database connections. A common bug is passing an ORM model object with methods; map it to a plain object first and include only the fields the client needs, so you do not leak data.
Q26. How do you keep server-only code out of the client?
Import the server-only package at the top of modules that touch secrets or the database; the build fails if a Client Component imports them. Combine that with the rule that only NEXT_PUBLIC_ variables reach the client, and with a data access layer: a module of server-only functions that check authorization and return minimal data transfer objects.
Q27. Server Components vs SSR: what is the difference?
SSR is about when and where HTML is produced (on the server, per request). Server Components are about which components ship JavaScript. In the Pages Router, SSR rendered HTML on the server but every component still shipped JS and hydrated. With Server Components, only Client Components hydrate, so a content-heavy page might send a fraction of the JavaScript. The two combine: a dynamically rendered page is SSR'd, and within it, Server Components send no JS.
Caching and Revalidation: Questions 28-32
Q28. What are the caching layers in the App Router?
| Layer | What it caches | Where | Lifetime |
|---|---|---|---|
| Request memoization | Identical fetch calls (and functions wrapped in React cache) within one render | Server | One request |
| Data cache | Fetch results opted into caching | Server, persistent | Until revalidated |
| Full route cache | Rendered HTML and RSC payload of static routes | Server | Until revalidated or redeployed |
| Router cache | RSC payloads of visited and prefetched segments | Browser memory | Session; time-based staleness |
Explaining these four layers clearly is one of the best signals of real App Router experience.
Q29. What changed about caching in Next.js 15?
Next.js 14 cached fetch requests by default, cached GET route handlers by default, and the client router cache reused page segments for a period. Developers found this surprising, so Next.js 15 changed the defaults: fetch is not cached by default (opt in with cache: "force-cache" or next.revalidate), GET route handlers are not cached by default, and the client router cache no longer reuses page segments by default (layouts and loading states are still reused). Next.js 16 introduced the opt-in "use cache" directive with Cache Components for explicit caching of components and functions.
Q30. How do revalidatePath and revalidateTag differ?
revalidatePath("/products/42") invalidates cached data and rendered output for a path. revalidateTag("product:42") invalidates every cached fetch tagged with that tag, across all pages that used it. Tags scale better: a product price change invalidates the product page, category listings and the homepage carousel at once, without you listing paths.
Q31. A user updates their profile but still sees old data. What could be wrong?
- Server data cache: the profile fetch is cached and nothing revalidated it. Call
revalidateTagorrevalidatePathin the mutation. - Static route: the page was prerendered at build. Personalized pages must be dynamic (they read cookies, so they usually are).
- Client router cache: after a Server Action, revalidation also refreshes the router cache; after a mutation done some other way (a direct API call), call
router.refresh(). - CDN cache: a CDN in front caches a page that should be private. Check the
Cache-Controlheaders.
Q32. How do you cache non-fetch data like database queries?
Use React's cache() to deduplicate a function within a request (for example, getCurrentUser called by the layout and the page). For persistent caching across requests, use unstable_cache with tags in Next.js 14-15, or the "use cache" directive with cacheTag and cacheLife in Next.js 16 with Cache Components enabled. Mention the version; these APIs have been actively evolving.
Server Actions and Data Mutations: Questions 33-36
Q33. What are Server Actions?
Async functions marked with "use server" that run on the server but can be called from forms or Client Components. Next.js turns each into a POST endpoint behind the scenes. Forms that use them work even before JavaScript loads (progressive enhancement), and after the action runs you can revalidate caches and redirect in the same round trip.
"use server";
import { revalidateTag } from "next/cache";
import { getSession } from "@/lib/auth"; // your server-only auth helper
import { db } from "@/lib/db";
export async function addToCart(productId: string, qty: number) {
const session = await getSession();
if (!session) throw new Error("Not signed in"); // authorization inside the action
if (!Number.isInteger(qty) || qty < 1 || qty > 10) throw new Error("Invalid quantity");
const product = await db.product.findUnique({ where: { id: productId } });
if (!product || product.stock < qty) throw new Error("Out of stock");
await db.cartItem.upsert({
where: { userId_productId: { userId: session.userId, productId } },
update: { qty: { increment: qty } },
create: { userId: session.userId, productId, qty },
});
revalidateTag(`cart:${session.userId}`);
}
Q34. How do you show pending states and validation errors with Server Actions?
React 19 provides useActionState (which returns the action's latest result, such as field errors, plus a pending flag), useFormStatus (pending state inside a form's child component, such as a submit button), and useOptimistic (show the expected result immediately, for example a liked state, and roll back if the action fails). Return structured errors from the action instead of throwing for validation problems, so the form can display them.
Q35. Server Actions vs route handlers vs a separate backend?
Use Server Actions for mutations triggered by your own Next.js UI. Use route handlers for endpoints others call: webhooks, mobile clients, public APIs. Keep a separate backend (Spring Boot, Django, Node services) when business logic is shared by many clients, has its own scaling needs, or is owned by another team; then Next.js acts as a backend-for-frontend that calls those services from Server Components. Many Indian product companies run exactly that split.
Q36. How should authentication be structured in an App Router app?
Layer it: middleware or proxy for optimistic redirects (no session cookie, so send to login); a data access layer that verifies the session and authorization on every data read and mutation; and Server Actions and route handlers that check authorization themselves. Never rely on a layout check alone: layouts do not re-run on every navigation, and a Server Action can be called directly. Libraries such as Auth.js or a hosted identity provider handle sessions and OAuth; see our OAuth 2.0 and JWT guide for the protocols.
Performance and Production: Questions 37-40
Q37. How does next/image improve performance?
It serves resized, modern-format (WebP or AVIF) images per device, lazy-loads off-screen images, and requires width and height (or fill) so space is reserved and layout does not shift (better CLS). The sizes prop tells the browser which width to download for responsive layouts; omitting it with fill often downloads images far larger than needed. Mark the hero image as high priority so the browser loads it early, which helps LCP; check your version's docs for the exact prop. Remote images require configuring allowed domains in next.config.
Q38. How do you reduce JavaScript bundle size in Next.js?
- Keep components as Server Components unless they need interactivity, and push
"use client"down to the smallest interactive leaf (the button, not the whole page). - Lazy-load heavy client widgets (charts, editors, maps) with
next/dynamic. - Analyze bundles (
@next/bundle-analyzer) and replace heavy dependencies; import only what you use from large libraries. - Use
next/fontto self-host fonts without layout shift andnext/scriptwithstrategy="lazyOnload"or"afterInteractive"for third-party scripts.
Measure with Core Web Vitals (LCP, INP, CLS) from real users, not just Lighthouse on a fast laptop. Many Indian users are on mid-range Android phones and variable mobile networks, where JavaScript cost dominates.
Q39. How do you self-host Next.js, and what changes compared with Vercel?
Run next build and next start on Node, or build a small Docker image with output: "standalone". Put a CDN or reverse proxy in front for static assets. The catch with multiple instances: the data cache and ISR output are stored locally per instance by default, so instances can serve different versions and revalidation on one does not reach the others. Configure a shared cache handler (for example, backed by Redis) in that case. Image optimization needs the sharp package and CPU. Containers and orchestration are covered in Docker and Kubernetes for backend engineers.
Q40. Edge runtime vs Node.js runtime?
The Edge runtime runs on a lightweight V8-isolate environment with Web APIs only: fast cold starts and close to users, but no native Node modules, restricted APIs, and limits on execution time and size. The Node.js runtime supports the full Node API and most npm packages. Use Edge for simple, latency-sensitive logic such as redirects or geolocation-based routing; use Node for anything touching databases with Node drivers, heavy libraries or long work. Middleware historically ran only on Edge; Node.js runtime support for it arrived in Next.js 15.x.
How to Answer Next.js Questions Well
For design-style questions ("how would you build a product listing page for an e-commerce site?"), structure the answer by route: which parts are static, which are dynamic, where Suspense boundaries go, how data is cached and invalidated, and which components are client-side. For example: the category page is ISR with tag-based revalidation from the catalogue service; price and stock badges stream in or are fetched per request; the filter panel is a Client Component that updates search params; and the cart badge is personalized and dynamic.
"use client" at the top of every page; fetching data in useEffect when a Server Component could do it; relying only on middleware for authorization; passing whole database records to Client Components; describing Next.js 13-era caching defaults as current; and not knowing whether a given route is static or dynamic.Next.js questions sit on top of React, TypeScript and web fundamentals. Round out your preparation with our TypeScript interview questions, HTML and CSS interview questions, and the frontend developer roadmap.
Frequently Asked Questions
Get Frontend Interview Ready
Prepflix's frontend track covers React, Next.js, JavaScript and web performance with mock rounds that probe exactly these concepts.
Explore the Prepflix Course →