30 Next.js Interview Questions and Answers (2026)
30 Next.js interview questions with full answers: App Router, Server Components, use cache, PPR, Turbopack, and auth. Updated for Next.js 15 and 16.
On this page
Next.js shows up in more frontend and full-stack job postings every year, and the framework keeps moving fast enough that last year's interview prep is already out of date. The App Router, React Server Components, the use cache directive, and Partial Prerendering are all stable in Next.js 15 and 16, and interviewers are asking about them directly, not the Pages Router patterns that dominated guides written two or three years ago.
This guide collects 30 questions you are likely to be asked, grouped into five categories that roughly mirror how a real interview escalates: framework fundamentals, App Router conventions, the server/client component split, data fetching and caching, and performance plus advanced topics like PPR and Turbopack. Each answer is written the way you would actually say it out loud, not the way the docs phrase it, with code samples you can reference if you get asked to write something on a whiteboard or shared editor.
If you are setting up a fresh Next.js project to practice these concepts in, getting the tooling right first pays off. A solid Husky, Prettier, and lint-staged setup means you are not fighting formatting issues while you are trying to demo App Router patterns.
Category 1: Fundamentals (Q1-Q8)
These are the questions that establish whether you understand what Next.js actually adds on top of React. They come up early in almost every interview, often as a warm-up before the conversation moves into App Router specifics.
Q1. What is Next.js and why do companies prefer it over plain React?
Next.js is a React framework built by Vercel that adds server-side rendering, static generation, file-based routing, image optimization, and a full caching layer on top of React. Plain React is a UI library: it handles the view layer but leaves routing, data fetching, and rendering strategy entirely to you. Next.js makes those decisions configurable rather than something you build from scratch.
Companies prefer it because it ships production-ready defaults: SEO-friendly HTML output, optimized images out of the box, automatic code splitting per route, and a deployment model that works at the edge. For content-heavy or SEO-dependent products, like e-commerce, SaaS marketing sites, and editorial platforms, the performance advantages are measurable from day one rather than something you have to engineer in later.
- SSR, SSG, and ISR rendering strategies built directly into the framework
- File-based routing, so there is no router configuration to maintain
next/imagefor automatic image optimization and CLS prevention- A built-in Metadata API for SEO
- First-class TypeScript support out of the box
Q2. What is the difference between the Pages Router and the App Router?
The Pages Router is the original Next.js routing model, built around a /pages directory. The App Router, stable since Next.js 13.4, is the modern model built around /app, React Server Components, and a richer set of file conventions. Most production codebases still have Pages Router routes somewhere, so knowing both is expected at senior level even though new projects build on the App Router in 2026.
| Pages Router (/pages) | App Router (/app) | |
|---|---|---|
| Routing convention | Every .tsx file in /pages maps directly to a route | Every folder containing a page.tsx becomes a route |
| Data fetching | getStaticProps, getServerSideProps, getStaticPaths | Directly inside async Server Components, no special exports |
| Default component type | Everything is a Client Component by default | Everything is a Server Component by default |
| Layouts | Handled manually in _app.tsx, easy to get wrong | First-class layout.tsx files that persist across navigation |
| Where you'll see it | Still common in production codebases built before 2023 | What new Next.js projects use in 2026 |
Q3. What rendering strategies does Next.js support?
Next.js supports five rendering strategies, and knowing when to reach for each one is one of the most practical signals of real-world experience an interviewer can probe for.
| Strategy | How it works | Best for |
|---|---|---|
| SSG (Static Site Generation) | HTML generated at build time and served from a CDN | Blogs, docs, marketing pages: content that rarely changes |
| SSR (Server-Side Rendering) | HTML generated on the server on every request | Real-time data, user-specific content, dashboards needing fresh data |
| ISR (Incremental Static Regeneration) | Statically generated, then regenerated in the background on an interval or on demand via `revalidateTag`/`revalidatePath` | Product pages, news feeds: content that changes occasionally but benefits from CDN speed |
| CSR (Client-Side Rendering) | Server sends a minimal HTML shell; the browser renders via JavaScript after load | Dashboards and highly interactive apps where SEO doesn't matter |
| PPR (Partial Prerendering) | New default in Next.js 16. Mixes static and dynamic at the component level: everything outside a `<Suspense>` boundary is prerendered, everything inside streams at request time | Most production pages: CDN-speed TTFB with fresh, personalized content in the same response |
Q4. What is file-based routing in Next.js?
In Next.js, the folder structure inside app/ (or pages/) directly determines the URL structure. You don't configure routes in a separate router file: the filesystem is the router.
| File path | Resulting route |
|---|---|
| app/page.tsx | / |
| app/blog/page.tsx | /blog |
| app/blog/[slug]/page.tsx | /blog/:slug (dynamic segment) |
| app/blog/[...slug]/page.tsx | /blog/a/b/c (catch-all segment) |
| app/(marketing)/about/page.tsx | /about (route group, adds no URL segment) |
| app/@sidebar/page.tsx | named parallel slot, not a standalone URL |
- Routes are discoverable just by looking at the folder structure
- Page logic, layouts, and loading states naturally co-locate
- Nested layouts compose automatically without extra wiring
- There is no separate router configuration file to keep in sync with your folders
Q5. How do dynamic routes work in Next.js?
Dynamic route segments are created by wrapping a folder name in square brackets, like [slug], [id], or [category]. The detail that trips people up in 2026: starting in Next.js 15, params is a Promise, so you must await it before reading any values.
export default async function ProductPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params; // async since Next.js 15
const product = await fetchProduct(id);
return <div>{product.name}</div>;
}- `[slug]` — matches a single segment, e.g.
/blog/my-post - `[...slug]` — catch-all, matches
/blog/a/b/cand requires at least one segment - `[[...slug]]` — optional catch-all, matches both
/blogand/blog/a/b/c
Q6. What is generateStaticParams and when do you use it?
generateStaticParams is the App Router equivalent of getStaticPaths from the Pages Router. It returns an array of parameter objects that Next.js uses to pre-generate static pages for a dynamic route at build time.
export async function generateStaticParams() {
const products = await fetchAllProducts();
return products.map((p) => ({ id: p.id.toString() }));
}Reach for it whenever you want SSG for a dynamic route: blog posts at /blog/[slug], product pages at /products/[id], documentation pages at /docs/[...path]. Without it, dynamic routes render on demand per request, which behaves like SSR. With it, Next.js pre-builds every page at deploy time and serves each one straight from a CDN. You can combine it with revalidate to get ISR behavior: static pages that quietly regenerate in the background after a set interval.
Q7. How does Next.js handle SEO?
Next.js handles SEO at several layers at once, which is exactly why it tends to outrank single-page apps built without a framework's help.
- Server-side HTML rendering — Server Components and SSG/SSR produce full HTML, so crawlers receive indexable content without needing to execute JavaScript.
- The Metadata API — export
metadata(static) orgenerateMetadata(dynamic) from anylayout.tsxorpage.tsxto control title, description, Open Graph data, and more. - File conventions —
app/robots.tsandapp/sitemap.tsgeneraterobots.txtandsitemap.xmlautomatically at build time. - Core Web Vitals —
next/imageprevents Cumulative Layout Shift andnext/fonteliminates font-related layout shift, both of which are direct Google ranking signals. - Canonical URLs — set through
metadata.alternates.canonicalto avoid duplicate-content penalties.
// Static metadata
export const metadata = {
title: "Product Name",
description: "Under 155 characters.",
openGraph: { title: "Product", images: ["/og.png"] },
};
// Dynamic metadata
export async function generateMetadata({ params }) {
const product = await fetchProduct((await params).id);
return { title: product.name };
}Q8. What is next/image and why does it matter for performance?
next/image is a built-in component that wraps the HTML <img> tag with automatic optimization: it converts images to modern formats like WebP and AVIF (typically 20 to 40 percent smaller than JPEG), resizes images to the size they're actually rendered at, lazy-loads anything below the fold, and reserves layout space before the image loads so the page doesn't jump around as it finishes loading.
That last point is the one interviewers care about most. Cumulative Layout Shift (CLS) is a Core Web Vitals metric that directly affects Google ranking, and unoptimized images are one of the most common causes of a bad CLS score.
| Prop | What it does |
|---|---|
| priority | Loads the image eagerly instead of lazily. Use it on the LCP image (hero/banner); without it, that image lazy-loads and hurts your LCP score. |
| fill | Makes the image fill its positioned parent. Use it for responsive containers where you don't know the dimensions at compile time. |
| sizes | Tells the browser which image size to download per viewport. Critical for responsive performance, e.g. `sizes="(max-width: 768px) 100vw, 50vw"`. |
| quality | Compression level from 1 to 100, defaulting to 75. |
| alt | Required. An empty string is valid for purely decorative images. |
Category 2: App Router and File Conventions (Q9-Q14)
Once an interviewer knows you understand the basics, the conversation usually moves to the App Router's file conventions: layouts, loading and error states, route groups, parallel routes, and middleware. These questions test whether you've actually built something with the App Router, not just read about it.
Q9. What is layout.tsx and how does it differ from page.tsx?
page.tsx defines the unique content for a specific route. layout.tsx defines the persistent shell that wraps one or more pages. The detail that matters most: layouts do not re-render when you navigate between child routes. They stay mounted and keep their state, which is exactly why shared UI like navbars, sidebars, and tab bars belongs in a layout. It doesn't flash or reset on navigation.
app/layout.tsx is the required root layout and must include <html> and <body> tags. Inner layouts, like app/dashboard/layout.tsx, just return their children; they don't need html/body of their own.
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex">
<Sidebar />
<main>{children}</main>
</div>
);
}Q10. What are loading.tsx and error.tsx used for?
loading.tsx renders instantly while a page's async data is being fetched. Next.js automatically wraps the page in a Suspense boundary using this file as the fallback, so you get streaming with zero extra configuration: just create it alongside any page.tsx.
error.tsx catches runtime errors thrown during rendering or data fetching within its route segment, acting as a React error boundary. It must be a Client Component, since error boundaries rely on client-side lifecycle behavior, and it receives two props: error (the thrown Error object) and reset (a function to retry rendering the segment).
"use client";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return (
<div>
<p>Something went wrong: {error.message}</p>
<button onClick={reset}>Try again</button>
</div>
);
}- Both files are scoped to their directory:
dashboard/error.tsxonly catches errors from dashboard routes, not the whole app. - `not-found.tsx` renders when
notFound()is called or a route simply doesn't exist. - `template.tsx` behaves like
layout.tsx, except it remounts on every navigation instead of persisting.
Q11. How does Next.js implement nested layouts?
Any folder in app/ can have its own layout.tsx, and Next.js composes them automatically from outermost to innermost. For a route like /dashboard/settings, three layers stack: the root layout (app/layout.tsx), the dashboard layout (app/dashboard/layout.tsx, which provides the sidebar and top nav), and finally the page content itself.
Navigate from /dashboard/overview to /dashboard/settings and only the page content re-renders. The dashboard layout stays mounted, so the sidebar doesn't flash, and the root layout stays mounted too, so there's no full page reload. That's a meaningful UX improvement over the Pages Router, where persisting a layout meant manually engineering it inside _app.tsx.
Q12. What are route groups and when would you use them?
Route groups are folders wrapped in parentheses, like (marketing) or (auth). They let you organize routes under a shared layout without adding the folder name to the URL.
| File path | Resulting route |
|---|---|
| app/(marketing)/about/page.tsx | /about (not /marketing/about) |
| app/(marketing)/pricing/page.tsx | /pricing |
| app/(auth)/login/page.tsx | /login |
| app/(auth)/register/page.tsx | /register |
- Different layouts per section without changing URLs:
(auth)pages can get a centered card layout while(app)pages get a sidebar-and-topnav layout, both serving routes at the same URL depth. - Organizing large codebases by grouping related routes in the filesystem for clarity, without the group name leaking into the URL.
- Multiple root layouts: each route group can define its own
layout.tsx, enabling entirely different root layouts for, say, a marketing site versus the app itself.
Q13. What are parallel routes in Next.js?
Parallel routes let you render multiple pages simultaneously within the same layout. They're defined by folders prefixed with @, like @modal or @sidebar, and these slots get passed to the parent layout as props.
// Folder structure:
// app/
// layout.tsx <- receives both children and modal as props
// page.tsx
// @modal/
// page.tsx <- the parallel route content
// default.tsx <- rendered when the slot has no active match
export default function Layout({
children,
modal,
}: {
children: React.ReactNode;
modal: React.ReactNode;
}) {
return (
<>
{children}
{modal}
</>
);
}The most common use case is intercepted route modals: clicking a photo opens it in a modal overlay while the URL changes to /photos/42, navigating directly to that URL shows the full photo page, refreshing on the modal URL shows the full page rather than the modal, and closing the modal returns you to the previous page without losing scroll position. Parallel routes combined with intercepting routes is how Next.js implements that Instagram-style modal pattern without hand-rolled client-side state management.
Q14. What is middleware in Next.js and what changed in Next.js 16?
Middleware is code that runs on every request before it reaches a page or API route, operating at the edge before any rendering happens. Typical uses include authentication redirects, role-based authorization, A/B testing rewrites, locale-based redirects for internationalization, and adding security headers to every response.
In Next.js 15 and earlier, this lived in middleware.ts at the project root. In Next.js 16, the file was renamed to proxy.ts. The reasoning: the file operates at the network proxy layer, not inside the React rendering pipeline, and the new name makes that clearer. The API itself is identical; only the filename changed.
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const token = request.cookies.get("session-token");
if (!token) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*", "/account/:path*"],
};Category 3: Server Components and Client Components (Q15-Q20)
This is where interviews tend to separate candidates who have shipped App Router code from candidates who have only read about it. The server/client boundary touches everything: data fetching, bundle size, hydration, and how you structure components day to day. If your background is heavier on the React internals side, how React's renderer decides what to re-render and when is a useful companion to the questions in this section, since RSC builds directly on those same rendering primitives.
Q15. What is a React Server Component and how does it differ from a Client Component?
| React Server Component | Client Component | |
|---|---|---|
| Where it runs | Exclusively on the server, never in the browser | In the browser (also pre-rendered on the server for the initial HTML) |
| Can it be async? | Yes, it can await database queries and API calls directly | No, but it can call functions that return promises inside effects/handlers |
| Hooks and browser APIs | Cannot use useState, useEffect, event handlers, or browser APIs | Full access to React state, effects, refs, event handlers, and browser APIs |
| Bundle impact | Sends only serialized HTML to the client; adds no JS for this component | Adds JavaScript to the client bundle |
| How you opt in | The default for every component in the App Router | Marked explicitly with a "use client" directive at the top of the file |
// Server Component — no "use client", can be async
async function ProductList() {
const products = await db.products.findAll(); // direct DB access
return (
<ul>
{products.map((p) => (
<li key={p.id}>
{p.name} <AddToCartButton id={p.id} /> {/* Client Component */}
</li>
))}
</ul>
);
}Q16. When should you use "use client" and when should you avoid it?
Add "use client" the moment a component genuinely needs interactivity or browser access: useState, useReducer, or useContext; useEffect, useLayoutEffect, or useRef; event handlers like onClick, onChange, or onSubmit; browser APIs such as window, document, localStorage, navigator, or sessionStorage; or third-party libraries (animation, charts, drag-and-drop) that rely on any of those internally.
Avoid it when the component only fetches and displays data, receives everything it needs as props, or involves no user interaction at all. Every "use client" boundary adds JavaScript to the client bundle, and marking a purely display-oriented component as a Client Component sends unnecessary code to the browser, throws away the performance benefit of RSC, and makes the component ineligible for server-side data fetching.
The pattern that resolves almost every case: keep data fetching in a Server Component and pass the results as props to a thin Client Component that handles only the interactive part.
// Server Component — fetches data
async function ProductPage({ id }: { id: string }) {
const product = await db.products.findById(id);
return <AddToCartButton productId={product.id} price={product.price} />;
}
// Client Component — handles interaction only
"use client";
function AddToCartButton({ productId, price }: Props) {
const [added, setAdded] = useState(false);
return (
<button onClick={() => setAdded(true)}>
{added ? "Added!" : `Add to cart, $${price}`}
</button>
);
}Q17. What causes a hydration error in Next.js and how do you fix it?
A hydration error happens when the HTML the server generated doesn't match what React renders on the client during hydration. React expects them to be identical so it can attach event listeners to the existing DOM without a full re-render; when they differ, it throws.
- Browser-only code running on the server —
typeof window !== "undefined"checks that produce different output server-side vs. client-side. Fix: move it intouseEffect, or usedynamic()with{ ssr: false }. - Timestamps or random values —
new Date()orMath.random()resolve to different values on the server than in the browser. Fix: generate the value client-side only, insideuseEffectoruseMemo. - Browser extensions modifying the DOM — ad blockers and password managers can alter the DOM before React hydrates. Fix:
suppressHydrationWarningon the affected element; this is safe specifically because you know an external source is causing the mismatch. - Invalid HTML nesting — a
<p>cannot contain a<div>; browsers silently auto-correct this, which creates a mismatch. Fix: correct the HTML structure. - Conditional rendering based on user state — rendering different content for logged-in vs. logged-out users on the server when you don't actually have that information at SSR time. Fix: render a neutral state on the server and update it client-side in
useEffect.
// Fix for browser-only components
import dynamic from "next/dynamic";
const BrowserOnlyMap = dynamic(() => import("./Map"), { ssr: false });Q18. How do you pass data from a Server Component to a Client Component?
You pass data as serializable props. A Server Component can fetch data and hand it to a Client Component as plain JavaScript values: strings, numbers, booleans, arrays, and plain objects.
async function ProductPage() {
const product = await db.products.findById(id);
// Pass serializable data as props
return (
<AddToCartButton
productId={product.id}
price={product.price}
name={product.name}
/>
);
}What you cannot pass: functions (they aren't serializable across the server/client boundary), class instances, React components as values rather than JSX, anything with circular references, or symbols. Trying to pass a non-serializable value triggers a serialization error at build time or request time, so for complex shapes, convert to a plain object before handing it across.
// Bad: passing a Prisma model instance directly
return <Component product={prismaResult} />;
// Good: extract only what you need
return <Component product={{ id: prismaResult.id, name: prismaResult.name }} />;Q19. What is Suspense in the context of Next.js streaming?
Suspense is a React feature that shows a fallback UI while waiting for an async operation to finish. In Next.js, it's the mechanism that powers streaming Server Components.
- A user requests a page.
- Next.js immediately sends everything outside
Suspenseboundaries as HTML: the static shell, layout, header, footer, placeholders. - Inside each
Suspenseboundary, Next.js sends a loading skeleton or spinner. - As each Server Component resolves its data, Next.js streams the real HTML into the browser and swaps out the skeleton, all without a new request.
export default function Page() {
return (
<div>
<Header /> {/* renders immediately */}
<StaticHeroSection /> {/* renders immediately */}
<Suspense fallback={<ProductListSkeleton />}>
<ProductList /> {/* streams in when the DB query resolves */}
</Suspense>
<Suspense fallback={<ReviewsSkeleton />}>
<Reviews /> {/* streams in independently */}
</Suspense>
</div>
);
}The payoff: users see meaningful content instantly instead of a blank screen, slow data fetches don't block fast ones, and the time to first byte stays low because the server doesn't have to wait on every fetch before sending anything. loading.tsx is really just syntactic sugar for wrapping an entire page in Suspense; reach for <Suspense> directly when you want component-level control.
Q20. What are Server Actions in Next.js?
Server Actions are async functions marked with "use server" that run on the server but can be called directly from Client Components. They're designed for mutations: creating, updating, and deleting data, and they replace the older pattern of standing up a POST API route just to handle a form submission or button click.
"use server";
export async function createPost(formData: FormData) {
const title = formData.get("title") as string;
await db.posts.create({ data: { title } });
revalidatePath("/blog"); // update cached data
redirect("/blog"); // navigate after success
}"use client";
import { createPost } from "../actions/posts";
export function PostForm() {
return (
<form action={createPost}>
<input name="title" placeholder="Post title" />
<button type="submit">Publish</button>
</form>
);
}- Progressive enhancement — passing a Server Action to a form's
actionmakes the form work without JavaScript; it submits as a normal HTML form if JS fails to load. - Security — Server Actions are POST endpoints under the hood, and Next.js automatically adds CSRF protection. Never put sensitive logic in a Client Component; keep it inside the action.
- After a mutation — always call
revalidatePath()orrevalidateTag()to clear stale cached data, orredirect()to move the user on after success. - `useFormStatus` / `useActionState` — the React hooks for tracking the pending and error state of a Server Action call from the client.
Category 4: Data Fetching and Caching (Q21-Q26)
Caching is where Next.js interviews tend to get genuinely hard, and for good reason: it's where the framework's behavior is least intuitive and most consequential for production bugs. These six questions cover the explicit caching model that replaced the confusing implicit fetch caching of Next.js 13/14.
Q21. How does the "use cache" directive work in Next.js 15/16?
"use cache" is an explicit caching directive introduced in Next.js 15 and stable in Next.js 16. Placing it at the top of an async function, component, or file marks its return value for server-side caching. It exists specifically to replace the implicit fetch() caching from Next.js 13/14, which left most developers unsure when results were being cached or for how long. "use cache" makes caching explicit and predictable instead.
// Cache at the function level
async function getProducts() {
"use cache";
cacheLife("hours"); // cache TTL profile
cacheTag("products"); // tag for on-demand invalidation
return await db.products.findAll();
}
// Cache at the component level
async function ProductList() {
"use cache";
cacheLife("minutes");
const products = await getProducts();
return <ul>{products.map((p) => <li key={p.id}>{p.name}</li>)}</ul>;
}
// Cache at the page/file level (top of file, caches all exports)
"use cache";
export default async function BlogPage() {
const posts = await fetchPosts();
return <PostGrid posts={posts} />;
}- `cacheLife("seconds" | "minutes" | "hours" | "days" | "weeks")` sets how often the cached value revalidates.
- `cacheTag("products", "category-electronics")` attaches one or more string tags you can later invalidate on demand with
revalidateTag("products")from a Server Action.
Q22. What is the difference between revalidatePath and revalidateTag?
Both invalidate cached data and trigger a re-fetch, but at different levels of granularity. revalidatePath(path) invalidates everything cached for a specific URL path, which is the right call when a mutation only affects a single page. revalidateTag(tag) invalidates every cached entry carrying that tag, regardless of which URL it appears on, which is the right call when the same underlying data shows up across multiple pages.
// After creating a new blog post, when only one page is affected:
revalidatePath("/blog"); // clears the /blog listing page
revalidatePath("/blog/my-new-post"); // clears a specific post page
// After updating a product that appears in many places:
"use server";
export async function updateProduct(id: string, data: ProductData) {
await db.products.update({ where: { id }, data });
revalidateTag("products"); // clears /products, /products/[id],
revalidateTag(`product-${id}`); // homepage, and anywhere else tagged
}Q23. What is ISR (Incremental Static Regeneration) and how does it work?
ISR lets you generate pages statically at build time and then automatically regenerate them in the background after a set interval, without a full redeploy. The flow: a page is generated statically (at build time or on first request), an incoming request gets served the cached static version instantly, and once that cache expires, Next.js triggers a background regeneration so the next request gets the freshly built version. Users never wait on the regeneration; they always get a fast static response.
// Option 1 — route segment config (whole page)
// app/blog/page.tsx
export const revalidate = 3600; // re-generate at most every hour
// Option 2 — "use cache" with cacheLife (function/component level)
async function getBlogPosts() {
"use cache";
cacheLife("hours"); // roughly equivalent to hourly revalidation
return await fetchPosts();
}
// Option 3 — on-demand ISR, usually preferred in production
// In a Server Action or Route Handler, e.g. a CMS webhook:
revalidateTag("blog-posts"); // re-generates every page tagged with it,
// only when content actually changedOn-demand ISR via revalidateTag is generally preferred in production, because time-based ISR regenerates pages even when nothing changed, while on-demand regenerates only when your CMS or database actually has new content to serve.
Q24. What is the difference between Server Actions and Route Handlers?
Both run on the server, but they exist for different reasons.
| Server Actions | Route Handlers (app/api/.../route.ts) | |
|---|---|---|
| Tied to | The React rendering model and your own app's UI | Standard HTTP semantics: GET, POST, PUT, DELETE |
| Best for | Form submissions, button clicks, and mutations triggered from inside your Next.js app | Webhooks (Stripe, GitHub), mobile app APIs, public endpoints, third-party consumers |
| Form integration | Pass directly to <form action={...}>; works with useFormStatus/useActionState; degrades gracefully without JS | No built-in form integration or progressive enhancement |
| Security | CSRF protection handled automatically by Next.js | Full control, but you own request validation and protection yourself |
| Cache updates | Call revalidatePath/revalidateTag after a mutation to refresh cached data | You manage caching headers and invalidation explicitly |
Q25. What are the four Next.js cache layers and what does each one do?
Next.js 15 and 16 ship with four distinct caching layers, and being able to explain which layer is responsible for a given behavior, and how to invalidate it, is one of the most reliable signals of senior-level mastery an interviewer can check for.
| Layer | Scope | What it does | Lifetime |
|---|---|---|---|
| 1. Request Memoization | A single server request (one render pass) | If the same async function or `fetch` call runs more than once during a single render, it executes once and the result is shared, e.g. `getUser()` called in both `layout.tsx` and `page.tsx` | Cleared after each request completes |
| 2. Data Cache ("use cache") | Persistent across requests and server restarts | Functions marked `"use cache"` store their return values server-side, e.g. a product catalog cached for hours while a user session is never cached | Until the `cacheLife` TTL expires or `revalidateTag` clears it |
| 3. Full Route Cache | Server-side, shared across all users | Statically generated HTML and RSC payloads are cached on the server and served to every visitor without re-rendering, e.g. an `/about` page served from cache to all traffic | Until the page regenerates via ISR or `revalidate` |
| 4. Router Cache (Client Cache) | Per browser session, in memory | The browser keeps previously visited RSC payloads, so navigating back and forward is instant with no server round trip | Session-based; cleared on a hard refresh |
Q26. What are the async Request APIs introduced in Next.js 15?
Next.js 15 made four previously synchronous APIs asynchronous: params, searchParams, headers(), and cookies(). You now have to await all four.
// Next.js 15+ — params and searchParams are Promises
export default async function Page({
params,
searchParams,
}: {
params: Promise<{ id: string }>;
searchParams: Promise<{ q: string }>;
}) {
const { id } = await params;
const { q } = await searchParams;
const cookieStore = await cookies();
const headersList = await headers();
// ...
}The reasoning behind the change: accessing these synchronously meant the response had to wait until the values were available. Making them asynchronous lets Next.js start streaming the page before all of the request data has resolved, which improves both time to first byte and overall streaming performance.
Category 5: Performance, Deployment, and Advanced (Q27-Q30)
The last stretch of a Next.js interview, especially for senior roles, often turns to the newest parts of the framework: Partial Prerendering, Turbopack, deeper next/image mechanics, and how to wire up authentication correctly across the server/client boundary.
Q27. What is Partial Prerendering (PPR) and why is it significant?
PPR is a rendering strategy that removes the all-or-nothing static-versus-dynamic choice at the page level and moves it down to the component level instead. It went from experimental in Next.js 14 to the stable default in Next.js 16.
The mental model: everything outside a <Suspense> boundary is static and prerendered at build time, and everything inside one is dynamic and rendered at request time. Next.js serves the static shell from the CDN edge instantly, then streams the dynamic parts in as they resolve, all within a single HTTP response.
export default async function ProductPage() {
return (
<div>
{/* Prerendered at build time, served from the CDN edge */}
<ProductHeader />
<ProductImages />
<ProductDescription />
{/* Dynamic — streamed at request time */}
<Suspense fallback={<InventorySkeleton />}>
<LiveInventoryStatus /> {/* real-time stock check */}
</Suspense>
<Suspense fallback={<RecommendationsSkeleton />}>
<PersonalizedRecommendations /> {/* user-specific */}
</Suspense>
</div>
);
}The result is CDN-speed time to first byte (often under 50ms) for the static shell, a fast LCP because the core product information ships in that shell, and dynamic data that's still fresh and personalized. Before PPR, a single dynamic component, like a cart count, forced the entire page into SSR mode and slowed down everything on it. PPR removes that tradeoff entirely: the cart can be dynamic inside its own Suspense boundary while the rest of the page stays static.
Q28. What is Turbopack and what does it change?
Turbopack is a Rust-based JavaScript bundler built by Vercel as a modern replacement for Webpack. It was introduced as experimental in Next.js 13 (next dev --turbo), became stable and opt-in by default for next dev in Next.js 15, and became the default bundler for everything, including next build, in Next.js 16.
- Up to 10x faster Fast Refresh (HMR) than Webpack
- 2 to 5x faster cold builds
- File system caching in Next.js 16, so build artifacts persist to disk and even the first startup on subsequent runs is dramatically faster
- It's written in Rust, giving it native speed without V8 overhead
- It replaces Webpack as the default in Next.js 16
- Most apps need zero configuration changes; it's simply faster
- The Webpack plugin ecosystem is the main compatibility concern, though most common plugins either have Turbopack equivalents or aren't needed at all
next.config.tsuses the same configuration shape for both bundlers
Q29. How does next/image handle Core Web Vitals and what are its key props?
next/image directly addresses two Core Web Vitals metrics. For LCP (Largest Contentful Paint), the priority prop tells Next.js to preload the hero or banner image instead of lazy-loading it; skip it and your LCP image loads late, which hurts the score. For CLS (Cumulative Layout Shift), next/image requires either explicit width and height or fill, which lets the browser reserve space before the image arrives. Zero layout shift from images means zero CLS contribution from them, while plain <img> tags without dimensions are a common cause of poor CLS.
<Image src="/hero.jpg" alt="Hero" width={1200} height={600} priority />| Prop | What it controls |
|---|---|
| priority | Loads eagerly with no lazy loading. Reserve it for the LCP image only; too many priority images defeats the purpose. |
| fill | Expands the image to fill its positioned parent (`position: relative` required on the parent). Combine with `sizes` for the best results. |
| sizes | Tells the browser which size to download at each viewport width, e.g. `sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"`. Without it, the browser may download a 2000px image for a 300px thumbnail. |
| quality (default 75) | Compression level from 1 to 100. Higher means better quality and a larger file; 75 is a solid default, and 60-65 works for background images. |
| placeholder="blur" | Shows a blurred preview while the full image loads, paired with a `blurDataURL` (Next.js generates one automatically for local images). Improves perceived performance over a plain gray box. |
Q30. How would you implement authentication in a Next.js App Router project?
Authentication in the App Router rests on three layers working together, and walking an interviewer through all three, in order, is usually the strongest answer you can give. If you're also handling provider keys or session secrets in the same project, it's worth pairing this with how Next.js environment variables actually reach the server versus the browser, since getting that boundary wrong is one of the most common ways auth secrets leak.
- Middleware / `proxy.ts` for route protection — runs on every request before any page renders. Check for a valid session cookie and redirect to
/loginif it's missing or invalid. This is the only reliable security layer, because it runs server-side before the user ever sees anything. - Server Components for reading session state — read the session on the server with
cookies()orheaders(). The session token never reaches client-side JavaScript; it stays on the server the entire time. - Server Actions for login, logout, and mutations — handle credential validation and cookie management inside Server Actions, never inside Client Components.
export async function middleware(request: NextRequest) {
const session = request.cookies.get("session-token")?.value;
const isValid = session ? await verifyToken(session) : false;
if (!isValid && request.nextUrl.pathname.startsWith("/dashboard")) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
}// In any Server Component:
const cookieStore = await cookies();
const token = cookieStore.get("session-token")?.value;
const user = token ? await getUserFromToken(token) : null;"use server";
export async function loginAction(formData: FormData) {
const email = formData.get("email") as string;
const password = formData.get("password") as string;
const user = await verifyCredentials(email, password);
if (!user) throw new Error("Invalid credentials");
const token = await createSessionToken(user.id);
(await cookies()).set("session-token", token, {
httpOnly: true, // not readable by browser JS
secure: true, // HTTPS only
sameSite: "lax", // CSRF protection
maxAge: 60 * 60 * 24 * 7, // 7 days
});
redirect("/dashboard");
}- Auth.js (NextAuth v5) — the most popular option, with support for 50+ OAuth providers
- Clerk — managed auth with pre-built UI components and strong developer experience
- Lucia Auth — lightweight, with full control over the implementation
- Kinde and WorkOS — enterprise-focused options
Quick Reference: All 30 Questions at a Glance
Use this as a final scan the night before an interview: if any row makes you pause, jump back up to that question's full answer.
| # | Question | Core concept |
|---|---|---|
| 1 | What is Next.js? | Framework overview |
| 2 | Pages Router vs. App Router | Routing models |
| 3 | Rendering strategies | SSG / SSR / ISR / CSR / PPR |
| 4 | File-based routing | Filesystem as router |
| 5 | Dynamic routes | [slug], [...slug] |
| 6 | generateStaticParams | Static param generation |
| 7 | SEO in Next.js | Metadata API, RSC HTML |
| 8 | next/image | Image optimization, CLS |
| 9 | layout.tsx vs. page.tsx | Persistent layouts |
| 10 | loading.tsx and error.tsx | Suspense + error boundaries |
| 11 | Nested layouts | Layout composition |
| 12 | Route groups | (folder) organization |
| 13 | Parallel routes | @slot multi-panel UI |
| 14 | middleware / proxy.ts | Request interception |
| 15 | RSC vs. Client Components | Server-first rendering |
| 16 | When to use "use client" | Client opt-in boundary |
| 17 | Hydration errors | SSR/client mismatch |
| 18 | Passing data server to client | Serializable props |
| 19 | Suspense and streaming | Progressive rendering |
| 20 | Server Actions | Server-side mutations |
| 21 | "use cache" directive | Explicit caching |
| 22 | revalidatePath vs. revalidateTag | Cache invalidation |
| 23 | ISR | Background regeneration |
| 24 | Server Actions vs. Route Handlers | Mutations vs. API |
| 25 | Four cache layers | Full caching model |
| 26 | Async Request APIs | params, cookies, headers |
| 27 | Partial Prerendering (PPR) | Static shell + streaming |
| 28 | Turbopack | Rust-based bundler |
| 29 | next/image props | LCP, CLS, WebP |
| 30 | Authentication | Middleware + Server Actions |
Frequently Asked Questions
How should I actually prepare for a Next.js interview, beyond memorizing answers?
Build something small with the App Router end to end, even a single CRUD feature, so you've personally hit the rough edges these questions are testing for: an async params you forgot to await, a hydration mismatch from a Date.now() call, a Server Action that needed revalidateTag to actually show its results. Interviewers can tell the difference between an answer that came from documentation and one that came from debugging your own mistake at 11pm.
After that, rehearse the answers in this guide out loud, not just read them silently. The caching questions especially (Q21, Q22, Q25) are where candidates who understand the concepts in writing still stumble when asked to explain them in real time, because the four-layer model has to come out in the right order to make sense.
Which of these 30 questions come up most often at the senior level?
The caching and rendering questions carry the most weight for senior roles: Q21 ("use cache"), Q22 (revalidatePath vs. revalidateTag), Q25 (the four cache layers), and Q27 (Partial Prerendering). These are the areas where the framework's behavior is least obvious and where a wrong answer in production costs real money in stale data or wasted compute.
Q15 and Q16, on Server Components and the "use client" boundary, are a close second. They reveal whether you actually understand why the App Router is structured the way it is, rather than just knowing the directive syntax.
Do I still need to know the Pages Router for a 2026 interview?
Yes, at least at a conceptual level. Most production Next.js codebases still have Pages Router routes somewhere, often in older parts of larger applications, and a senior-level interviewer will likely ask you to compare the two models (this guide's Q2) to see whether you can reason about migration paths and legacy code.
You don't need deep getServerSideProps mastery the way you would have in 2022. You need to recognize the patterns, explain how they map onto App Router equivalents (generateStaticParams for getStaticPaths, async Server Components for getServerSideProps), and speak credibly about why a team might migrate or choose not to.
Should I study Next.js 15 or Next.js 16 for an interview?
Both, with an emphasis on whichever the job posting or your conversation with the recruiter signals the company runs in production. Next.js 16 made Turbopack and PPR the defaults and renamed middleware.ts to proxy.ts, but the underlying APIs (the async Request APIs, "use cache", Server Actions, the four cache layers) were already stable in Next.js 15 and haven't changed in any way that matters for an interview answer.
If you're not sure which version a team is on, mention both filenames when the topic of middleware comes up (Q14). It signals that you're current without assuming the interviewer's stack matches the latest release.
If I'm asked to write code live, how much detail is expected?
Enough to show you know the shape of the API and the gotchas around it, not enough to reproduce a textbook from memory. For a dynamic route question (Q5), for example, writing the function signature with params: Promise<{ id: string }> and the await params line matters far more than getting every type annotation perfect. That single await is the detail that separates someone who has actually used Next.js 15 from someone reciting older patterns.
When you're unsure of an exact API name mid-interview, say so directly and describe what you'd reach for and why; "I'd use the cache invalidation function, I believe it's revalidateTag, to clear everything tagged with the product's ID after the mutation" reads as far stronger than confidently guessing wrong.
Related Articles
How to Use Environment Variables in Next.js (Without Leaking Them to the Browser)
Learn how to use .env files in Next.js correctly. Understand NEXT_PUBLIC_, avoid common mistakes, and set variables in Vercel and Cloudflare.
Husky + Prettier + lint-staged Setup for Next.js
Set up Husky v9, Prettier, and lint-staged in your Next.js project. Step-by-step guide covering pre-commit hooks with the correct 2026 config.
React Fiber Architecture Explained: How React Renders UI
React Fiber is the engine behind every React render. Learn how it works: render phase, commit phase, Lanes, and double buffering, explained with analogies.