Next.js Interview Questions and Answers
Short answer
Next.js interviews centre on the App Router: what a Server Component can and cannot do, why the client boundary spreads downward, how fetch caching and revalidation work, and what causes a hydration mismatch. Rendering strategy questions are now about justifying a choice per route.
| The question | What it reveals |
|---|---|
| What is a Server Component? | Whether you know what it cannot do, not just what it is |
| What does 'use client' do? | Whether you think it means "runs only on the client" |
| How do you fetch data in the App Router? | Whether you still reach for useEffect |
| SSG, SSR, ISR, which and why? | Judgement per route, not a preference |
| What causes a hydration mismatch? | Real debugging, not a definition |
| What are Server Actions? | Whether you have used the App Router since it stabilised |
| How does caching work? | The hardest question here, and the most differentiating |
| How do you handle SEO metadata? | Whether you have shipped a public Next.js site |
What a Next.js round actually covers
Next.js questions used to be a short list, `getServerSideProps` versus `getStaticProps`, the `Link` component, the API routes folder. The App Router changed the shape of the interview entirely. Now almost everything asked lives at one seam: which code runs on the server, which runs in the browser, and what happens at the boundary between them. If you can reason about that seam, most of the individual questions answer themselves.
The other thing worth knowing is that interviewers are checking recency. The App Router has been the default for new projects for a while now, and describing `getServerSideProps` as how you fetch data signals you have not built anything current. You do not need to have migrated a large application, but you should be able to say what changed and why.
App Router or Pages Router, what actually changed?
The Pages Router routes by file, renders everything on the client after hydration, and fetches data through page-level exports like `getServerSideProps`. The App Router routes by folder, makes components server-rendered by default, supports nested layouts that persist across navigation, streams the response, and colocates data fetching in the component that needs it.
1// Pages Router - data fetching is a page-level export
2// pages/kits/[slug].js
3export async function getServerSideProps({ params }) {
4 const kit = await db.kit.findUnique({ where: { slug: params.slug } });
5 return { props: { kit } };
6}
7export default function KitPage({ kit }) {
8 return <h1>{kit.name}</h1>;
9}
10
11// App Router - the component itself is async and runs on the server
12// app/kits/[slug]/page.tsx
13export default async function KitPage({ params }) {
14 const kit = await db.kit.findUnique({ where: { slug: params.slug } });
15 return <h1>{kit.name}</h1>;
16}
17// → no props plumbing, no separate export, and the query never
18// → reaches the browser bundle at all
19
20// Nested layouts persist across navigation, they do not re-render
21// app/kits/layout.tsx wraps every route under /kits, and its state
22// survives moving from /kits/js-kit to /kits/react-kitThe layout persistence point is worth making because it is a real behavioural difference rather than a syntax one. In the Pages Router, moving between two pages under the same section unmounts and remounts everything; in the App Router a shared layout stays mounted, so a sidebar's scroll position and open state survive the navigation. That is the kind of detail that shows you have built with it rather than read the migration guide.
What are Server Components, and what can they not do?
A Server Component runs only on the server. It can be async, can query a database or read the filesystem directly, and ships zero JavaScript to the browser, the client receives the rendered output, not the component. The question that separates answers is the list of what it cannot do, because that list is what forces the architecture.
1// A Server Component: no directive needed, this is the default
2export default async function KitList() {
3 const kits = await db.kit.findMany(); // → secrets stay on the server
4 // useState(...) → Error: hooks are not available
5 // onClick={...} → Error: event handlers cannot be serialised
6 // window.innerWidth → Error: there is no window here
7 return (
8 <ul>
9 {kits.map((k) => <BuyButton key={k.id} kit={k} />)}
10 </ul>
11 );
12}
13
14// A Client Component: interactive, and part of the JS bundle
15"use client";
16import { useState } from "react";
17
18export function BuyButton({ kit }) {
19 const [pending, setPending] = useState(false);
20 return <button onClick={() => setPending(true)}>{kit.name}</button>;
21}
22// → still server-rendered to HTML first, THEN hydrated in the browser
23// → "use client" marks a boundary, it does not mean "client-only"
24
25// Props crossing the boundary must be serialisable
26// <BuyButton onDone={() => {}} /> → Error: functions cannot be passed
27// <BuyButton kit={kit} /> → fine: plain dataTwo consequences are worth volunteering. The boundary is contagious downward, everything imported by a Client Component joins the client bundle, so the practical rule is to push `"use client"` as far down the tree as possible rather than putting it at the top of a page. And a Client Component can still render a Server Component if it receives it as `children`, because the server rendered it before the client component ever saw it. That composition pattern is how you keep a heavy markdown renderer or a database-backed list out of the bundle while still wrapping it in an interactive shell.
How do you fetch data in the App Router?
In the component, with `await`. A Server Component can be async, so you call your database or an API directly in the body and render the result. There is no `useEffect`, no loading state to manage by hand, and no request waterfall from the browser, and saying that clearly is most of the answer.
1// Sequential - two round trips, the second waits for the first
2export default async function Slow({ id }) {
3 const user = await getUser(id);
4 const orders = await getOrders(id); // → did not need to wait
5 return null;
6}
7
8// Parallel - start both, then await together
9export default async function Fast({ id }) {
10 const userPromise = getUser(id);
11 const ordersPromise = getOrders(id);
12 const [user, orders] = await Promise.all([userPromise, ordersPromise]);
13 return null;
14}
15// → total time is the slower of the two, not the sum
16
17// Or stream: render the shell immediately and let the slow part arrive later
18import { Suspense } from "react";
19
20export default function Page({ id }) {
21 return (
22 <>
23 <Header />
24 <Suspense fallback={<Skeleton />}>
25 <Orders id={id} />
26 </Suspense>
27 </>
28 );
29}
30// → the header is sent right away; Orders streams in when its await resolves
31// → this is what makes App Router pages feel fast on a slow connectionStreaming with `Suspense` is the part to bring up unprompted, because it is the answer to "how would you handle one slow query on an otherwise fast page". The shell reaches the browser immediately and the slow subtree arrives when it is ready, so the user sees something long before the data resolves, without the page-level loading spinner the Pages Router forced on you.
How do caching and revalidation work?
This is the hardest Next.js question and the one that most differentiates answers. Next.js caches at several levels, the fetch results themselves, the rendered route output, and the client-side router cache, and the two mechanisms you must be able to name are time-based revalidation and on-demand revalidation.
1// Time-based: regenerate at most once every hour
2export const revalidate = 3600;
3// → or per request: fetch(url, { next: { revalidate: 3600 } })
4
5// Opt out entirely - render on every request
6export const dynamic = "force-dynamic";
7// → or: fetch(url, { cache: "no-store" })
8
9// On-demand: purge when something actually changes
10import { revalidatePath, revalidateTag } from "next/cache";
11
12fetch(url, { next: { tags: ["kits"] } });
13
14async function onKitUpdated() {
15 revalidateTag("kits"); // → every fetch tagged "kits" is invalidated
16 revalidatePath("/kits"); // → that route's rendered output is purged
17}
18// → this is the pattern for a CMS webhook: publish, then purge
19
20// Reading a dynamic API opts the route out of static rendering implicitly
21import { cookies } from "next/headers";
22const theme = cookies().get("theme");
23// → the route becomes dynamic automatically, which surprises people
24// → when a page they expected to be static is rendered per requestThe implicit opt-out in the last block is the detail worth knowing, because it is the cause of most "why is my page not static" confusion. Reading `cookies()`, `headers()` or `searchParams` makes the route dynamic whether or not you intended it. And the honest framing to offer is that caching defaults have changed between major versions of Next.js, so the professional answer is to state the mechanisms and check the defaults for the version in use rather than assert them from memory.
This is 1 of 200+ questions in the Complete Frontend Kit
Get every question with detailed answers, follow-ups and real code, plus coding challenges and a last-minute revision sheet. One-time payment, instant access.
⚡ Get the Complete Frontend Kit → ₹499Which rendering strategy would you choose?
Static generation for anything that is the same for every visitor and changes rarely, marketing pages, documentation, blog posts, and the free content pages on a site like this one. Incremental Static Regeneration when that content changes on a schedule but not per request. Server rendering when the output genuinely depends on the request: a logged-in dashboard, a personalised feed, search results. Client rendering for the interactive parts inside any of those.
The mistake interviewers listen for is picking by preference rather than per route, and it goes in both directions, "we server-render everything" gives up free performance on pages that never change, while "we statically generate everything" quietly breaks personalisation. The strong answer names the axis: does the output depend on who is asking, and how stale is acceptable? Those two questions decide it every time, and the comparison page linked below works through the trade-offs in detail.
What is hydration, and what causes a mismatch?
Hydration is React attaching event handlers and state to HTML that was already rendered on the server, so the page is visible before it is interactive. A mismatch happens when the first client render produces different markup from what the server sent, React warns, discards the server HTML for that subtree, and re-renders it on the client, which costs exactly the performance the server render was buying.
1// 1. Values that differ between server and client
2function Bad() {
3 return <time>{new Date().toLocaleTimeString()}</time>;
4}
5// → server renders 10:00:01, client renders 10:00:02 → mismatch
6
7// 2. Reading browser APIs during render
8function AlsoBad() {
9 const wide = window.innerWidth > 768; // → ReferenceError on the server
10 return wide ? <Desktop /> : <Mobile />;
11}
12
13// 3. Random ids
14// const id = Math.random(); → different on each side
15const id = useId(); // → stable across server and client
16
17// 4. Invalid HTML nesting the browser silently repairs
18// <p><div>text</div></p> → the browser moves the div out, so the
19// DOM no longer matches what React rendered
20
21// The fix for genuinely client-only values: render after mount
22function Good() {
23 const [time, setTime] = useState(null);
24 useEffect(() => setTime(new Date().toLocaleTimeString()), []);
25 return <time>{time ?? "--:--:--"}</time>;
26}
27// → server and first client render agree; the real value appears afterThe invalid-nesting cause is the one almost nobody names, and it is worth having ready because it explains mismatches that look impossible, your code is deterministic, but the browser silently restructured the DOM to make invalid HTML valid, so React's expectation no longer matches reality. A `div` inside a `p`, or a `p` inside a `p` from a markdown renderer, are the usual culprits.
What are Server Actions?
Functions marked `"use server"` that run on the server but can be called from the client, including as a form's `action`, which means mutations work before any JavaScript has loaded. Next.js generates the endpoint and the fetch for you, so a form submission and a database write need no API route and no client-side handler.
1// app/actions.ts
2"use server";
3import { revalidatePath } from "next/cache";
4
5export async function createKit(formData) {
6 const name = formData.get("name");
7 if (typeof name !== "string" || !name) return { error: "Name required" };
8 await db.kit.create({ data: { name } }); // → runs on the server only
9 revalidatePath("/kits"); // → refresh the cached list
10}
11
12// app/kits/new/page.tsx (a Server Component)
13import { createKit } from "../../actions";
14
15export default function NewKit() {
16 return (
17 <form action={createKit}>
18 <input name="name" required />
19 <button type="submit">Create</button>
20 </form>
21 );
22}
23// → submits and writes to the database with JS disabled
24// → progressive enhancement, which the Pages Router could not doTwo things are worth adding. A Server Action is a real HTTP endpoint that anyone can call, so it must validate its own input and check authorisation, treating it as trusted because it looks like a local function call is a genuine security mistake and a common follow-up question. And pairing it with `useFormStatus` or `useOptimistic` in a Client Component is how you add a pending state and optimistic UI on top without giving up the no-JavaScript baseline.
How do you handle metadata and SEO?
Export a `metadata` object from a layout or page for static values, or an async `generateMetadata` function when the title and description depend on the data. Because both run on the server before the response is sent, the tags are in the initial HTML, which is the whole point, since a crawler that does not execute JavaScript will never see tags set by an effect.
1// Static, in a layout, inherited by every nested route
2export const metadata = {
3 title: { default: "ForgeFrontend", template: "%s - ForgeFrontend" },
4 metadataBase: new URL("https://www.forgefrontend.com"),
5};
6
7// Dynamic, from the same params the page uses
8export async function generateMetadata({ params }) {
9 const kit = await getKit(params.slug);
10 return {
11 title: kit.name, // → "React Kit - ForgeFrontend"
12 description: kit.summary.slice(0, 155),
13 alternates: { canonical: "/kits/" + params.slug },
14 openGraph: { images: ["/og/" + params.slug + ".png"] },
15 };
16}
17
18// Pre-render every route at build time
19export async function generateStaticParams() {
20 const kits = await getAllKits();
21 return kits.map((k) => ({ slug: k.slug }));
22}
23// → each becomes a static HTML file; app/sitemap.ts lists them allThe `template` field in the root metadata is the detail worth naming, because it is how every nested page gets a consistent suffix without repeating the brand name in forty files. `generateStaticParams` is the other half of the answer: it tells Next.js which dynamic routes to pre-render at build time, so a content site ends up as static HTML files that any CDN can serve instantly. Pairing the two, plus `app/sitemap.ts` and `app/robots.ts`, is the complete answer to how a Next.js site is made crawlable, and it is a question that comes up whenever the role involves anything public-facing.
What loses marks in a Next.js round
The failures here are mostly about being one model behind.
- →Describing getServerSideProps as the current way to fetch data.
- →Saying 'use client' means the component runs only in the browser.
- →Putting 'use client' at the top of a page, pulling the whole tree into the bundle.
- →Fetching in a useEffect inside a Server Component-capable page.
- →Awaiting independent requests sequentially and creating a waterfall.
- →Not knowing that reading cookies() or headers() makes a route dynamic.
- →Treating a Server Action as trusted and skipping validation or an auth check.
Frequently asked questions
- What is the difference between the App Router and the Pages Router?
- The App Router routes by folder, renders Server Components by default, supports nested layouts that persist across navigation, streams responses, and colocates data fetching inside components. The Pages Router routes by file and fetches through page-level exports such as getServerSideProps. The App Router is the default for new projects.
- What is a React Server Component?
- A component that runs only on the server. It can be async and query a database directly, and it ships no JavaScript to the browser, the client receives its rendered output. It cannot use hooks, event handlers or browser APIs, because none of those exist where it runs.
- What does the 'use client' directive actually do?
- It marks a boundary. That component and everything it imports join the client bundle and become interactive, but it is still server-rendered to HTML on the first request. It does not mean client-only rendering, and the boundary spreads downward, which is why it belongs as low in the tree as possible.
- Can a Client Component render a Server Component?
- Not by importing one, but yes by receiving it as children or through props. The server renders that subtree first and passes the result through, which is how you keep heavy server-only work out of the client bundle while still wrapping it in an interactive shell.
- How do you fetch data in the Next.js App Router?
- Make the Server Component async and await the call directly in its body, no useEffect and no loading state to manage. Start independent requests together and await them with Promise.all to avoid a waterfall, and wrap slow subtrees in Suspense so the rest of the page streams immediately.
- What is ISR in Next.js?
- Incremental Static Regeneration: statically generated pages that regenerate in the background after a set interval, so visitors get static-file speed with periodically fresh content. Set it with export const revalidate, or per request through fetch's next.revalidate option.
- What is the difference between revalidatePath and revalidateTag?
- revalidatePath purges the cached output for a specific route, while revalidateTag invalidates every fetch that was tagged with that string, wherever it was made. Tags are the better fit for a CMS webhook, because one publish event can refresh every page that used the affected data.
- When should I use SSR instead of SSG in Next.js?
- Use server rendering when the output genuinely depends on the request, a logged-in dashboard, personalised results, anything per-user. Use static generation when the page is identical for every visitor and changes rarely, and ISR when it changes on a schedule. Decide per route rather than picking one for the whole app.
- What causes a hydration mismatch in Next.js?
- Anything that renders differently on the server and the client: timestamps and Date.now(), reading window or localStorage during render, Math.random() for ids, and invalid HTML nesting that the browser silently repairs. Use useId for ids, and move genuinely client-only values into an effect after mount.
- What are Server Actions?
- Functions marked "use server" that run on the server but can be invoked from the client, including directly as a form's action attribute. Next.js creates the endpoint and the request for you, so a form can submit and write to the database even before JavaScript has loaded.
- Are Server Actions secure by default?
- No. A Server Action compiles to a real HTTP endpoint that anyone can call, so it must validate its own input and perform its own authorisation checks. Treating it as trusted because the call site looks like a local function is a genuine vulnerability and a common interview follow-up.
- How do you handle SEO metadata in Next.js?
- Export a metadata object for static values or an async generateMetadata function when the tags depend on data. Both run on the server before the response is sent, so the tags appear in the initial HTML, which matters because crawlers that do not execute JavaScript never see tags added by an effect.
This is 1 of 200+ questions in the Complete Frontend Kit
Get every question with detailed answers, follow-ups and real code, plus coding challenges and a last-minute revision sheet. One-time payment, instant access.
⚡ Get the Complete Frontend Kit → ₹499