Next.js Interview Questions and Answers

Last updated:

Check out 30 of the most common Next.js interview questions, then take an AI-powered practice interview

ReactTypeScriptVercelSSRAPI Routes
30+
Questions
12
Basic
13
Intermediate
5
Advanced
Q1

What is Next.js and what problems does it solve over plain React?

BasicFundamentals

Answer

Next.js is a React meta-framework built by Vercel that adds server-side rendering, static generation, file-system routing, image optimization, and a unified data-fetching model on top of React. Plain React (Create React App or a Vite SPA) ships a near-empty HTML shell and hydrates everything on the client, that hurts SEO, time-to-first-byte, and Core Web Vitals on slow networks. Next.js solves four real problems: (1) HTML is rendered on the server so search crawlers and slow Indian 4G clients see content immediately, (2) routing is derived from the file system instead of needing a separate router config, (3) images, fonts, and scripts are optimized automatically via the Image, Font, and Script components, and (4) data fetching, caching, and revalidation are first-class, you can mark a fetch as static (cached forever), revalidated (ISR), or dynamic (per-request) with a single option. In 2026 the App Router also unlocks React Server Components, which let you keep heavy data-fetching logic on the server without shipping it as JavaScript to the browser.

Key Points

  • Hybrid rendering: static, ISR, SSR, and streaming in one app
  • File-system routing, no separate router config
  • Built-in Image, Font, Script, and Link optimization
  • App Router enables React Server Components
  • First-class deployment story on Vercel; portable to Docker / Bun / Node
Q2

What is the difference between the Pages Router and the App Router?

BasicRouting

Answer

The Pages Router (in /pages) is the original Next.js routing system: every file is a route, data fetching happens in getStaticProps / getServerSideProps / getStaticPaths, and the whole tree is a client React component by default. The App Router (in /app), introduced in Next.js 13 and stabilised in 13.4, is the modern default: every file is a React Server Component by default, data fetching happens directly inside async components, and the framework supports nested layouts, loading.tsx, error.tsx, route groups, parallel routes, and intercepting routes. Pages Router files are .js/.tsx files like /pages/blog/[slug].tsx; App Router uses folders with special files like /app/blog/[slug]/page.tsx.

Both routers can coexist in the same project, most production apps in 2026 still have a hybrid, with new features built in App Router and legacy pages still in Pages Router. The App Router is what interviewers expect you to know in depth; Pages Router is what you will actually maintain.

// Pages Router, /pages/blog/[slug].tsx
export async function getStaticProps({ params }) {
  const post = await fetchPost(params.slug);
  return { props: { post }, revalidate: 60 };
}
export default function Post({ post }) { return <article>{post.title}</article>; }

// App Router, /app/blog/[slug]/page.tsx
export const revalidate = 60;
export default async function Post({ params }) {
  const { slug } = await params;          // params is a Promise in Next 15+
  const post = await fetchPost(slug);
  return <article>{post.title}</article>;
}

Key Points

  • App Router lives in /app, Pages Router in /pages
  • App Router defaults to React Server Components
  • params and searchParams are Promises in Next 15+
  • Both routers can coexist, migrate incrementally
Q3

What is the difference between SSR, SSG, ISR, and CSR in Next.js?

BasicRendering

Answer

These are four rendering strategies Next.js supports, and understanding when to use each is one of the most-asked Next.js interview questions in India. SSG (Static Site Generation) renders pages to HTML at build time, fast, cacheable on a CDN, but the content is frozen until you rebuild. Good for marketing pages, documentation, and any page where content changes rarely.

The HTML is shipped directly from edge caches like Cloudflare or Vercel's CDN, so TTFB is often <50ms even from India. SSR (Server-Side Rendering) renders on every request, fresh data on each load, but every request hits your server. Good for dashboards, personalised pages, and any page where each user sees different content.

The trade-off: every request costs server CPU and network round-trip time. ISR (Incremental Static Regeneration) is the middle ground: pages are statically generated, but Next.js re-renders them in the background after a configured interval (revalidate: 60 means every 60 seconds the next request triggers a rebuild, and the previous static version is served until the rebuild completes, so no user waits). It gives you static performance with eventually-consistent freshness, perfect for blogs, product pages, e-commerce listings, and SEO landing pages where you want to push content updates without redeploying.

CSR (Client-Side Rendering) renders only on the client, the server sends a shell, React fetches and renders in the browser. Used for highly interactive parts of the page (charts, dashboards behind auth, real-time data) that do not need to be SEO-friendly. In the App Router these map to: default static for Server Components without dynamic APIs, dynamic = 'force-dynamic' for SSR, revalidate = N for ISR, and 'use client' components that fetch in useEffect for CSR. The Next.js sweet spot is to mix all four in the same application: marketing pages SSG, product listings ISR, user dashboard SSR, real-time chart CSR, and now with Partial Prerendering you can mix them within the same page.

Key Points

  • SSG: HTML built at build time, served from CDN
  • SSR: HTML built per request, fresh but server load
  • ISR: static HTML revalidated in background after N seconds
  • CSR: client-side fetch, no HTML rendering of content
  • App Router infers the mode from your code, no manual switch needed
Q4

What is the difference between Server Components and Client Components?

BasicReact Server Components

Answer

In the App Router every component is a React Server Component (RSC) by default, it runs only on the server, never ships JavaScript to the browser, can be async, can import server-only code (fs, database drivers, secrets, large libraries like markdown parsers), and can directly fetch data using await. A Client Component is a component you opt into by adding the 'use client' directive at the top of the file. It runs both on the server (for the initial HTML, yes, Client Components still SSR for the first render) and on the client (for hydration and interactivity), and it is the only place useState, useEffect, onClick, useRef, useContext, and any other React hooks or browser APIs work.

The mental model: Server Components are for data and structure; Client Components are for interactivity. You cannot import a Server Component INTO a Client Component directly because at that point the file boundary has crossed into client-bundled code, but you CAN pass a Server Component as children or as a prop, which is the standard composition pattern. In practice you want most of your tree to be Server Components, they reduce bundle size massively because the React code never ships to the browser, and push 'use client' down to the leaves like buttons, search inputs, modals, and tabs.

A common production pattern: a Server Component fetches the data, renders the layout, and passes the data as props to small 'use client' islands that handle interactivity. This keeps the bundle small while preserving rich UX. The other thing to remember: once a component is marked 'use client', everything it imports is also bundled for the client, so a small useState in a giant file accidentally ships the whole file. Split files aggressively.

// Server Component, fetches on the server, no JS shipped
async function Posts() {
  const posts = await db.post.findMany();   // direct DB call, OK on the server
  return (
    <ul>
      {posts.map(p => <li key={p.id}><LikeButton postId={p.id} /></li>)}
    </ul>
  );
}

// Client Component, interactivity only
'use client';
import { useState } from 'react';
export function LikeButton({ postId }) {
  const [liked, setLiked] = useState(false);
  return <button onClick={() => setLiked(!liked)}>{liked ? '♥' : '♡'}</button>;
}
💡 Pro Tip: Interviewers love asking 'why is my useState throwing in this file?', the answer is almost always that the file is a Server Component and needs 'use client' at the top.
Q5

How do you create dynamic routes in the App Router?

BasicRouting

Answer

Dynamic routes are folders wrapped in square brackets. /app/blog/[slug]/page.tsx matches /blog/hello, /blog/anything, etc. The dynamic segment becomes a param inside the page. In Next.js 15+ params is a Promise, you must await it (or unwrap with React.use() in a Client Component) before reading the value. This was a breaking change from Next 14 where params was a plain object; the move to a Promise was made because params can depend on dynamic server logic and the framework wants to encourage async-safe code.

For catch-all routes, use [...slug] which matches /blog/a/b/c as ['a', 'b', 'c']. For optional catch-all, use [[...slug]] which also matches /blog with slug === undefined. The same pattern works for route groups (folders wrapped in parentheses like (marketing)/page.tsx that group routes for organizational purposes without affecting the URL, useful when different sections need different layouts), private folders (prefixed with _ like _components/ which are never routable and live inside /app for colocation), and parallel routes (prefixed with @ for named slots).

For statically generating dynamic route pages at build time, export generateStaticParams to return the list of params to prerender, this is the App Router replacement for getStaticPaths. If a request comes in for a param that was not in generateStaticParams, Next.js will fall back to rendering on demand (and optionally cache the result, depending on dynamicParams config).

// /app/blog/[slug]/page.tsx, single param
export default async function Post({ params }) {
  const { slug } = await params;
  return <h1>{slug}</h1>;
}

// /app/docs/[...path]/page.tsx, catch-all
export default async function Docs({ params }) {
  const { path } = await params;  // path is string[]
  return <pre>{path.join('/')}</pre>;
}
Q6

How do you handle API routes in Next.js?

BasicAPI Routes

Answer

In the App Router, API routes are called Route Handlers and live in /app/.../route.ts. You export one async function per HTTP verb: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS. Each function receives a Request object and returns a Response (or NextResponse for the Next.js-specific helpers).

The Pages Router equivalent is /pages/api/.../filename.ts with a single default export taking (req, res), different shape entirely. Route Handlers run on the Node.js runtime by default, but you can opt into the Edge runtime with export const runtime = 'edge' for lower latency at the cost of restricted APIs (no fs, no native modules, limited bundle size). Route Handlers are by default cached for GET requests in Next.js 14 but in Next.js 15 GET handlers are also uncached by default, the framework has been moving towards explicit caching.

To opt out of caching explicitly, set export const dynamic = 'force-dynamic', or use cookies(), headers(), or read NextRequest.url with searchParams, any of these mark the handler as dynamic. For most CRUD work, Route Handlers are enough; for full backends you would still pair Next.js with a dedicated server (Express, FastAPI, Hono) or use server actions for mutations. Route Handlers are also where you put public webhooks (Stripe, Razorpay, Twilio) and any endpoints consumed by mobile apps or third parties, server actions cannot serve that role because their endpoints are Next.js-internal and not stable URLs.

// /app/api/users/route.ts
import { NextResponse } from 'next/server';

export async function GET() {
  const users = await db.user.findMany();
  return NextResponse.json(users);
}

export async function POST(request: Request) {
  const body = await request.json();
  const user = await db.user.create({ data: body });
  return NextResponse.json(user, { status: 201 });
}
Q7

What is next/link and why should you use it instead of an anchor tag?

BasicNavigation

Answer

next/link is the Next.js client-side navigation component. When the user clicks a Link, Next.js performs a soft navigation: it fetches only the data needed for the next route (the RSC payload, not a full HTML reload), updates the URL, and re-uses any layouts that have not changed. Plain anchor tags trigger a full browser navigation, throwing away the React tree and re-downloading all the JavaScript.

Link also prefetches the target route automatically when it enters the viewport, so the click feels instant. In Next.js 15 prefetching of dynamic routes is on-demand (you opt in with prefetch={true}) to avoid wasted server work. For external links (https://...) plain <a> is correct, Link is only for internal routes.

import Link from 'next/link';

export default function Nav() {
  return (
    <nav>
      <Link href="/dashboard">Dashboard</Link>
      <Link href="/pricing" prefetch>Pricing</Link>
      <Link href="/blog/[slug]" as="/blog/hello">Blog</Link>
    </nav>
  );
}
Q8

How does the next/image component improve performance?

BasicPerformance

Answer

next/image is a wrapper around the native img element that adds five optimizations Next.js handles automatically: (1) it serves WebP or AVIF when the browser supports it, falling back to JPEG/PNG, which can reduce image payloads by 30-50% over JPEG, (2) it generates multiple sizes and uses srcSet so phones download a small image and laptops download a large one, this is huge for mobile-first Indian traffic, (3) it lazy-loads images that are below the fold using the native loading='lazy' attribute, so the browser does not waste bandwidth downloading images the user has not scrolled to, (4) it reserves space using the width/height props to prevent Cumulative Layout Shift (CLS), a Core Web Vitals metric that Google factors into search rankings, and (5) on Vercel it runs the optimizer at the edge and caches the result on the CDN so repeat visitors get the optimized image without round-tripping to your origin. For Indian users on slow 4G, this is often the single biggest perceived-speed improvement you can make to a Next.js site, replacing 2 MB of unoptimized JPEGs with 200 KB of optimized AVIFs typically cuts LCP from 4-5 seconds to under 2 seconds. Gotcha: external images need their domain added to images.remotePatterns in next.config.js, or Next.js refuses to optimize them.

On self-hosted Docker, you need sharp installed (auto-included in 13+) and enough memory for the optimizer (~512 MB recommended, more for very high traffic). For self-hosted at scale, consider offloading image optimization to Cloudflare Image Resizing or BunnyCDN's image processor to avoid your Next.js servers becoming CPU-bound on sharp.

import Image from 'next/image';

export default function Hero() {
  return (
    <Image
      src="/hero.jpg"
      alt="Hero"
      width={1200}
      height={600}
      priority         // above-the-fold, disable lazy loading
      sizes="(max-width: 768px) 100vw, 1200px"
    />
  );
}
💡 Pro Tip: Always set priority on your above-the-fold hero image, without it, lazy loading hurts Largest Contentful Paint (LCP).
Q9

What is getStaticProps and getServerSideProps?

BasicData Fetching

Answer

These are the Pages Router data-fetching functions. getStaticProps runs at build time on the server, returns props for the page, and produces static HTML. Combined with revalidate: N it becomes ISR, the page is regenerated in the background every N seconds on the next visitor. getServerSideProps runs on every request on the server, returns props, and renders fresh HTML each time. They both run only on the server (so you can safely use database drivers and secrets) and they both deliver props to the page component.

In the App Router these are gone, you fetch directly inside async Server Components, and the caching behaviour is controlled by the fetch options (cache: 'force-cache' for SSG, no-store for SSR, next: { revalidate: 60 } for ISR). Interviewers still ask about Pages Router functions because most production apps still have them.

// Pages Router
export async function getStaticProps() {
  const posts = await fetch('https://api.example.com/posts').then(r => r.json());
  return { props: { posts }, revalidate: 60 };  // ISR, re-render at most every 60s
}

export async function getServerSideProps({ req }) {
  const user = await getUserFromCookie(req);
  return { props: { user } };
}
Q10

What is the difference between fetch() in a Server Component and useEffect fetch in a Client Component?

BasicData Fetching

Answer

In a Server Component, fetch runs on the server during the render, the user never sees a loading state because the HTML arrives with data already in it. The data is part of the initial response, which means better SEO (crawlers see content immediately), better Largest Contentful Paint, and no client-side waterfall. Next.js extends the standard fetch with caching options: by default fetch is memoized within a single render so calling it twice for the same URL hits the network once.

You also get cache, revalidate, and tags options for cache control. In a Client Component, fetch inside useEffect runs in the browser after the component mounts, the user sees a loading state, the bundle has to ship the fetching code as JavaScript, and you usually need a library like SWR or TanStack Query (React Query) to handle deduplication, retries, error states, and caching. There is also a hidden cost: client-side fetches happen AFTER hydration, which means after the browser has parsed and executed your JavaScript bundle.

On a slow Indian 4G connection this can add 2-3 seconds before the data even starts loading. The rule of thumb in 2026: fetch on the server whenever possible (faster, smaller bundle, no waterfalls, better SEO), and only use client-side fetching for data that depends on user interaction (search-as-you-type, polling, real-time updates, optimistic mutations). When you do need client-side data, prefer the new useSWR or useQuery hooks pre-populated with server-fetched initialData so the first paint shows real data and only subsequent updates hit the client fetch.

Q11

What is the purpose of layout.tsx and how does it differ from page.tsx?

BasicApp Router

Answer

page.tsx is the unique UI for a route, only one page.tsx is rendered for a given URL. layout.tsx wraps all pages inside its folder and persists across navigation between sibling routes. The root layout in /app/layout.tsx is mandatory and must contain the <html> and <body> tags. Nested layouts compose naturally: /app/dashboard/layout.tsx wraps every page under /dashboard/*.

The key UX benefit is preservation, when a user navigates from /dashboard/overview to /dashboard/billing, the dashboard layout (with its sidebar) does not re-render; only the page slot inside it changes. This is what makes Next.js navigation feel like a native app. Layouts receive a children prop and can themselves be async Server Components fetching data. Common patterns: auth check in the root layout, sidebar + topbar in a section layout, and modal layouts for /@modal parallel routes.

// /app/layout.tsx
export default function RootLayout({ children }) {
  return <html><body>{children}</body></html>;
}

// /app/dashboard/layout.tsx
export default function DashboardLayout({ children }) {
  return (
    <div className="flex">
      <Sidebar />
      <main>{children}</main>
    </div>
  );
}
Q12

How do you deploy a Next.js application?

BasicDeployment

Answer

Two broad routes. (1) Vercel: connect your GitHub repo, push to main, done. Vercel runs the optimised build, deploys the static assets to its global CDN (which has a Mumbai PoP since 2024), runs Server Components and Route Handlers on its serverless or edge runtime, and exposes preview deployments on every PR with a unique URL. This is the path Next.js is designed around, features like ISR on-demand revalidation, image optimization, partial prerendering, and middleware just work out of the box without configuration.

Vercel pricing in India: Hobby is free for personal projects, Pro is $20/month per user (~₹1,700/month at current INR rates) with included bandwidth and build minutes, Enterprise is custom and starts in the range of $2,000+/month. (2) Self-hosted: run next build and then next start behind a process manager (PM2, systemd) or in Docker. Custom servers need to handle ISR cache, image optimization (sharp), and revalidation manually, the official Next.js Docker example is the easiest starting point. In 2026 many Indian teams self-host on AWS Mumbai region (ap-south-1), GCP Mumbai (asia-south1), or DigitalOcean Bangalore for compliance reasons or to keep costs in INR rather than USD.

Newer options include Coolify (open-source PaaS, runs on your own VPS) and Bun as a lighter, faster runtime than Node.js, Bun cuts cold-start times by 40-60% and uses less memory, though some npm packages are still not fully compatible. The standalone build (output: 'standalone' in next.config.js) trims node_modules to only what is needed and is the right starting point for Docker, slim images are typically 150 MB instead of 500 MB. For multi-instance self-hosted deployments, you also need a shared cache handler (typically Redis) to keep ISR revalidations consistent across replicas.

Key Points

  • Vercel: zero-config, preview deploys, all Next.js features 'just work'
  • Self-host with Docker + Node.js or Bun
  • Use output: 'standalone' for slim Docker images
  • Self-hosted ISR needs an external cache handler for multi-instance setups
Q13

How does the Next.js fetch cache work and what are the four cache layers?

IntermediateCaching

Answer

Next.js has four caches stacked on top of each other, and understanding the boundary between them is what separates senior candidates from junior ones in an interview. (1) Request Memoization, within a single render, calling fetch() with the same URL is deduped automatically; the second call returns the cached result, not a fresh HTTP request. This means you can call your data layer in a layout AND in a page without worrying about double fetching. The memoization lives only for the duration of a single React render, then disappears. (2) Data Cache, fetched data is persisted across requests and deployments on the server.

Controlled by fetch options: cache: 'force-cache' (default in Next 14, opt-in in Next 15), no-store, or next: { revalidate: 60 }. The data cache is on-disk by default, swappable to Redis in self-hosted setups via a custom cache handler. (3) Full Route Cache, the rendered HTML and RSC payload of a route are cached at build time for static routes. Dynamic routes (using cookies, headers, dynamic params) skip this cache entirely.

The Full Route Cache is what makes static pages serve in <50ms from the CDN. (4) Router Cache, client-side cache of RSC payloads in the browser. When you navigate back to a route you visited recently, Next.js serves from this cache without re-fetching. The router cache lasts 30 seconds by default for dynamic routes and 5 minutes for static ones, and you can invalidate it with router.refresh().

In Next.js 15 the defaults flipped, fetch is no longer cached by default; you have to opt in with cache: 'force-cache' or revalidate options. This was a major change because the old defaults caused many production bugs where stale data was shown. The thing to remember in an interview: caching is layered, and revalidatePath/revalidateTag invalidate the Data Cache and Full Route Cache but NOT the Router Cache, that one only clears on full page reload or router.refresh().

// Static, cached until revalidated
await fetch('/api/posts', { cache: 'force-cache' });

// SSR, never cached
await fetch('/api/me', { cache: 'no-store' });

// ISR, revalidate after 60s
await fetch('/api/posts', { next: { revalidate: 60 } });

// Tag-based, invalidate from anywhere
await fetch('/api/posts', { next: { tags: ['posts'] } });
// ...elsewhere:
import { revalidateTag } from 'next/cache';
revalidateTag('posts');

Key Points

  • 4 layers: request memo → data cache → full route cache → router cache
  • Next 15 changed defaults: fetch is uncached by default
  • revalidatePath and revalidateTag are the escape hatches
  • force-dynamic disables every server-side cache for a route
Q14

What are server actions and when should you use them?

IntermediateServer Actions

Answer

Server actions are async functions that run on the server but are called from the client like a regular function, Next.js handles the RPC, serialization, and revalidation for you. You declare a function with 'use server' at the top of an async function (or in a file marked 'use server' at the top), and you can pass it as the action prop on a form or call it from an onClick handler in a Client Component. They were stabilised in Next.js 14 and are the recommended way to do mutations in the App Router.

Compared to writing a Route Handler + fetch + revalidate, server actions are: less boilerplate, type-safe end-to-end (the types of the arguments flow from the server function to the client without you writing a separate API contract), work without JavaScript (progressive enhancement for forms, the form still submits if the browser has not yet hydrated), and let you call revalidatePath / revalidateTag directly without a separate roundtrip. Under the hood, when you pass a server action to a form, Next.js generates a unique opaque ID for that function, the form POSTs to that ID, and Next.js routes the request to the right server function. Caveats: server actions are POST requests with a Next.js-generated endpoint, they are not meant to be public APIs, and you must validate inputs server-side because the client cannot be trusted (always use Zod or similar).

Server actions can also leak server bundle into client bundle if you mistakenly import non-action exports from a 'use server' file, keep actions in dedicated files. Use them for: form submissions, CRUD mutations, anything that updates state on the server. Do not use them for: high-frequency reads (use Server Components or Route Handlers with caching), public APIs consumed by mobile apps or third parties (use a Route Handler so you control the URL contract), or anything where you need precise control over HTTP semantics (status codes, custom headers, streaming).

// /app/actions.ts
'use server';
import { revalidatePath } from 'next/cache';
import { z } from 'zod';

const Schema = z.object({ title: z.string().min(3), body: z.string() });

export async function createPost(_prev: unknown, formData: FormData) {
  const parsed = Schema.safeParse(Object.fromEntries(formData));
  if (!parsed.success) return { error: 'Invalid input' };
  await db.post.create({ data: parsed.data });
  revalidatePath('/blog');
  return { ok: true };
}

// /app/blog/new/page.tsx
import { createPost } from '@/app/actions';
export default function NewPostPage() {
  return (
    <form action={createPost}>
      <input name="title" />
      <textarea name="body" />
      <button>Create</button>
    </form>
  );
}
Q15

How do you handle loading and error states in the App Router?

IntermediateApp Router

Answer

Next.js gives you two special files per route. loading.tsx is automatically rendered as a Suspense fallback while the page or its data is loading. error.tsx is rendered when an error is thrown in the page or any of its data fetches, it has to be a Client Component because it receives a reset() function to retry. The framework wires up the React 18+ Suspense and Error Boundary semantics so you do not have to write them by hand. There is also a not-found.tsx file that renders when notFound() is called from a Server Component, and a global-error.tsx that catches errors in the root layout (since errors in the root layout would otherwise break error.tsx too).

For finer-grained streaming, you can wrap parts of your page in <Suspense fallback={...}> manually so different sections of the UI become interactive at different times. The big idea: with Server Components and streaming, the user can see a skeleton for the slow part (search results) and the fast part (page chrome, sidebar) within milliseconds. Without Suspense the whole route would wait for the slowest fetch, that is the 'TTFB cliff' interviewers love to ask about.

A practical pattern for an e-commerce listing page: the layout streams immediately, the product grid streams once the product data resolves (~150ms), the recommendations section streams in last (~400ms because they require a personalisation call). The user starts seeing content at 50ms instead of waiting 400ms for everything. Combine this with error boundaries scoped per Suspense region so a single broken section does not take down the whole page.

// /app/dashboard/loading.tsx, automatic Suspense fallback
export default function Loading() {
  return <div>Loading dashboard…</div>;
}

// /app/dashboard/error.tsx, must be a Client Component
'use client';
export default function Error({ error, reset }) {
  return (
    <div>
      <p>Something broke: {error.message}</p>
      <button onClick={() => reset()}>Try again</button>
    </div>
  );
}
Q16

What are parallel routes and intercepting routes?

IntermediateApp Router

Answer

Parallel routes (folders prefixed with @, called slots) let you render multiple pages in the same layout simultaneously, each with its own independent loading and error states. A common pattern is /app/dashboard/@analytics/page.tsx and /app/dashboard/@notifications/page.tsx, the dashboard layout receives them as named props (analytics and notifications) and renders them side by side. Each slot can be loaded, errored, or revalidated independently, so a broken analytics chart does not take down the notifications panel.

Slots are particularly useful for dashboards with multiple independent data widgets. You can also have a default.tsx in each slot to define what to show when the slot is not matched by the active URL, and parallel routes work alongside conditional rendering (e.g., showing different slots based on user role). Intercepting routes (folders with (.) (..) (..)(..) (...)) let a route 'intercept' a sibling route's URL, used for modals that show on top of the current page but also have a real URL the user can share.

Classic example: clicking a thumbnail in /photos opens /photos/[id] as a modal on top of the grid (great UX, no full page reload), but visiting /photos/[id] directly (e.g. from a shared link) shows the full page. /app/photos/(..)photo/[id]/page.tsx intercepts the route when navigated to from within /photos. The conventions: (.) intercepts the same level, (..) intercepts one level above, (..)(..) two levels above, (...) intercepts from the root. Combined with parallel routes you can build Instagram-style modal navigation with shareable URLs in <100 lines of code. These are powerful but easy to over-use, interviewers ask about them to test whether you have built non-trivial UIs in App Router, not just basic CRUD.

// /app/dashboard/layout.tsx
export default function Layout({ children, analytics, team }) {
  return (
    <div className="grid grid-cols-2 gap-4">
      <div>{children}</div>
      <div>{analytics}</div>
      <div>{team}</div>
    </div>
  );
}
// Folders: /app/dashboard/@analytics/page.tsx and /app/dashboard/@team/page.tsx
Q17

What is middleware in Next.js and how is it different from middleware in Express?

IntermediateMiddleware

Answer

Next.js middleware runs at the Edge runtime BEFORE a request is matched to a route. It is a single function exported from /middleware.ts at the project root, and it runs for every request matching a configured matcher. Common uses: authentication checks (redirect to /login if no cookie), A/B testing (rewrite to a variant page), geolocation (redirect Indian users to /in), rate limiting (block bad actors), and feature flags.

Unlike Express middleware, Next.js middleware is severely constrained: it runs on the Edge runtime (a V8 isolate, not full Node.js), so no fs, no native modules, no node: imports, max 1 MB bundle. It can only return NextResponse.next(), NextResponse.redirect(), NextResponse.rewrite(), or NextResponse.json(), you cannot do arbitrary work like database queries here (use the Node runtime for that). The Edge runtime gives you very low latency (the middleware runs at the CDN edge closest to the user, often <50ms in India), but you trade off the ability to do heavy work. As of Next.js 15 you can also opt in to the Node.js runtime for middleware if you need fs or larger bundles, with the trade-off that it runs on your origin, not the edge.

// /middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const token = request.cookies.get('session')?.value;
  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
  return NextResponse.next();
}

export const config = {
  matcher: ['/dashboard/:path*', '/api/private/:path*'],
};
Q18

What is partial prerendering (PPR) and why is it interesting?

IntermediateRendering

Answer

Partial Prerendering, stabilised in Next.js 15, lets a single page combine static and dynamic content in the same HTML response. The static shell (navbar, footer, product description, hero image) is prerendered at build time and served instantly from the CDN, while the dynamic parts (user-specific recommendations, cart badge, A/B test variant, personalised pricing) are streamed in via Suspense boundaries as soon as they are ready. Before PPR you had to choose: either the whole page was static (fast TTFB but no personalization) or the whole page was dynamic (personalized but slow TTFB because every request waited for the slowest piece of data).

With PPR you get the best of both, the static shell hits the user in under 50ms from the CDN, and the dynamic holes fill in within a few hundred milliseconds via streaming. You opt in per-route with export const experimental_ppr = true (or globally in next.config.js), and you mark dynamic regions by wrapping them in <Suspense>. The Suspense fallback is what gets prerendered into the static shell, and the actual dynamic content streams in once the data resolves.

The implementation under the hood uses HTTP streaming with a partial response, the browser receives the shell as one chunk and the dynamic regions as additional chunks. PPR matters in India specifically because most users on 4G feel the latency of TTFB more than US/EU users, the round-trip from a Mumbai user to a US-hosted origin is 200-300ms, so anything that can be served from a Cloudflare or Vercel edge in Mumbai instead is a massive perceived-speed win. Shaving 500ms off LCP is a real Core Web Vitals improvement that Google factors into search rankings.

💡 Pro Tip: PPR is the answer interviewers in 2026 want when they ask 'how do you keep a personalised page fast?'. Mention it.
Q19

How do you optimize fonts in Next.js?

IntermediatePerformance

Answer

Use next/font, which downloads and self-hosts Google Fonts (or custom fonts) at build time. The advantages: (1) zero network round-trips to fonts.googleapis.com at runtime (saves 100-300ms in India where Google fonts latency is high), (2) automatic font-display: swap is set so the page does not block on font load, (3) preloads only the subset of the font your page actually uses, (4) generates a fallback font with matching metrics to eliminate Cumulative Layout Shift (CLS) when the real font loads. next/font/google for Google fonts, next/font/local for custom .woff2 files in /public. Always set a CSS variable so you can use the font across the tree, and only declare fonts in the root layout, duplicating them in nested layouts loads the same font twice.

// /app/layout.tsx
import { Inter } from 'next/font/google';

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-inter',
});

export default function RootLayout({ children }) {
  return <html className={inter.variable}><body>{children}</body></html>;
}

// In CSS:
// body { font-family: var(--font-inter), system-ui, sans-serif; }
Q20

How do you handle authentication in a Next.js app?

IntermediateAuthentication

Answer

Three production patterns in 2026. (1) Auth.js (formerly NextAuth), the most popular library, supports OAuth (Google, GitHub, Apple, GitLab), email magic links, credentials, and JWT or database sessions. Wires into App Router via the auth() helper which works in Server Components, Route Handlers, server actions, and middleware. It is open-source and free, which makes it the default for Indian startups watching costs. (2) Roll your own, sign a JWT on login (using jose or jsonwebtoken), store it in an HttpOnly cookie, validate in middleware on every protected route.

More work but full control over claims, refresh logic, multi-tenant scoping. Many Indian fintechs go this route for compliance and audit reasons. (3) Hosted (Clerk, WorkOS, Stack Auth), buy the auth, integrate with their React components. Cleanest for SaaS but adds vendor cost (Clerk pricing scales fast above 10k MAU; for an Indian startup at growth stage this matters, at ₹500-1000 per 1000 MAU the bill can hit ₹50k/month before you notice).

Common gotchas: never put auth checks only in middleware, middleware can be bypassed if matchers are wrong, and it does not run for all routes. Always re-check on the server inside the protected route or layout. Server actions also need auth checks, they are public POST endpoints under the hood and a malicious actor can call them directly without going through your UI.

For Indian companies handling user PII or financial data (KYC, payments, healthcare), use HttpOnly + Secure + SameSite=Lax cookies, rotate session tokens on privilege change, and consider short access-token + long refresh-token patterns. For SOC 2 or RBI compliance, log every auth event (login, logout, token refresh, failed attempts) to your observability stack. Finally, do not use localStorage for tokens, it is accessible to any JS on the page including third-party scripts, which makes it an XSS-and-you-are-toast vector.

// /app/dashboard/page.tsx
import { auth } from '@/lib/auth';
import { redirect } from 'next/navigation';

export default async function Dashboard() {
  const session = await auth();
  if (!session) redirect('/login');
  return <div>Hello {session.user.name}</div>;
}

Key Points

  • Auth.js for OAuth/credentials, Clerk for hosted
  • Cookies: HttpOnly + Secure + SameSite=Lax
  • Re-check on the server, never trust middleware alone
  • Server actions are public POST, always validate session
Q21

How do you migrate from Pages Router to App Router?

IntermediateMigration

Answer

Incremental migration is the only realistic strategy for a production app, a big-bang rewrite of a 200-route app is months of work and freezes feature delivery. Step by step: (1) Enable App Router by creating /app/layout.tsx, Pages Router keeps working alongside, route by route. (2) Pick a leaf route with no shared state (a marketing page, a blog post, an about page) and recreate it under /app. Verify everything works in staging, then ship. (3) Move data fetching: getStaticProps → fetch with cache: 'force-cache', getServerSideProps → fetch with no-store, getStaticPaths → generateStaticParams.

The data layer often stays unchanged; only the wrappers change. (4) Replace useRouter from next/router with useRouter, usePathname, useSearchParams from next/navigation in Client Components, the APIs are different (no more router.query, you read params from props and searchParams from a hook). (5) Replace _app.tsx logic with the root layout, and _document.tsx with the html/body tags in the root layout. Global providers (theme, store, query client) become Client Components that wrap children in the root layout. (6) Replace API routes in /pages/api with Route Handlers in /app/api gradually, they have nearly the same logic, just different signatures. The two routers can call into shared utilities (database client, validation schemas, business logic functions), but they cannot share React context, a Pages Router page and an App Router page do not share the same React tree, so a Redux store mounted in _app.tsx is not visible to /app/*.

Common pain points: Redux/Zustand stores that assumed a single root (refactor to React Server Component-aware patterns or accept that state is duplicated during the migration window), useEffect-based auth that needs to be redone with middleware and server-side checks, and CSS-in-JS libraries that did not support RSC until late 2024 (use Tailwind, CSS Modules, or vanilla-extract instead of styled-components or emotion if you are starting fresh). Set a target, e.g. 'migrate one route per sprint, finish in 12 months', and track progress publicly so the team commits to it.

💡 Pro Tip: Interviewers ask migration questions to test whether you have actually shipped App Router to production. Drop a real example, 'I migrated our /pricing page first because it had no auth state', and you instantly read as senior.
Q22

How do you debug hydration mismatches in Next.js?

IntermediateDebugging

Answer

A hydration mismatch happens when the HTML the server rendered does not match what React tries to render on the client during hydration. The error in dev mode looks like 'Hydration failed because the initial UI does not match what was rendered on the server.' In production, the page silently falls back to a full client-side re-render, which destroys the SSR benefit.

The five most common causes: (1) Date.now() or new Date() called in a component, the server time differs from the client time, especially if the user's clock is wrong or in a different timezone. (2) Math.random() used during render to generate IDs or keys. (3) navigator, window, document, or localStorage references in a Server Component or in the body of a Client Component (these are undefined on the server). (4) Locale-dependent formatting (toLocaleString without an explicit locale renders one way on a Mumbai server and another way on a Bengaluru client, especially for currency, dates, and number formatting). (5) Conditional rendering based on isClient or isMobile that flips after hydration. (6) Browser extensions modifying the DOM before React hydrates, Grammarly, dark-mode extensions, ad blockers, though these are usually a false alarm. The fixes: wrap the offending logic in useEffect (so it runs only on the client after hydration completes), use suppressHydrationWarning on a specific element (only for true time/random values where you accept the mismatch), guard with typeof window !== 'undefined' for code that must check, or use the next/dynamic import with ssr: false for components that genuinely cannot SSR (like charts using browser-only canvas APIs or maps using Leaflet/Mapbox). Never just suppress the warning globally, it is hiding real bugs. A pragmatic debugging strategy: turn on React strict mode in dev, look at the diff in the browser console (React 18+ shows exactly which DOM nodes differ), and binary-search by commenting out chunks of the component until the warning disappears.

// Bad, different on server and client
function Now() { return <span>{new Date().toLocaleString()}</span>; }

// Good, render on client after mount
'use client';
import { useEffect, useState } from 'react';
function Now() {
  const [now, setNow] = useState('');
  useEffect(() => { setNow(new Date().toLocaleString()); }, []);
  return <span>{now}</span>;
}
Q23

What is the difference between revalidatePath and revalidateTag?

IntermediateCaching

Answer

Both invalidate the Next.js data cache and trigger a re-render on the next request, but they work at different granularities. revalidatePath('/blog') invalidates all data and the full-route cache for that specific path. Use it when you know exactly which page changed, e.g. after a user edits a blog post, revalidatePath(`/blog/${slug}`). revalidateTag('posts') invalidates every fetch that was tagged with 'posts', regardless of which page it was called from. Use it for cross-cutting invalidations, e.g. after publishing a new post, revalidateTag('posts') so every page that lists posts (home, archive, sidebar widget) regenerates on the next visit.

Both functions are called from Server Actions or Route Handlers; they do nothing in Server Components rendered for normal requests. Gotcha in Next 15: tags are now scoped to specific fetches, not whole routes, you tag a fetch with next: { tags: ['posts'] }, and only that fetch is invalidated when you call revalidateTag('posts'). For self-hosted deployments, the default cache handler is the file system; for multi-instance setups behind a load balancer you need a shared cache handler (Redis, Vercel Data Cache, or a custom one) so revalidations propagate.

// In a server action
import { revalidatePath, revalidateTag } from 'next/cache';

'use server';
export async function publishPost(slug: string) {
  await db.post.update({ where: { slug }, data: { published: true } });
  revalidatePath(`/blog/${slug}`);  // refresh this post page
  revalidateTag('posts');           // refresh every page tagged with 'posts'
}

// In your fetch
await fetch('/api/posts', { next: { tags: ['posts'] } });
Q24

How do you handle environment variables in Next.js?

IntermediateConfiguration

Answer

Next.js loads environment variables from .env files in a specific order: .env.local (always loaded, should be gitignored, this is where secrets live during local development), .env.development or .env.production (loaded based on NODE_ENV), and .env (loaded last as cross-environment defaults). Variables are server-only by default, if you reference process.env.DATABASE_URL in a Client Component or in the browser, it is undefined. To expose a variable to the browser, prefix it with NEXT_PUBLIC_, these are inlined at build time and bundled into the client JavaScript.

Critical security gotcha: NEXT_PUBLIC_ variables are visible to anyone who downloads your JS bundle, which means they end up in browser developer tools, error monitoring stacktraces, and anywhere your bundle is mirrored. NEVER put secrets there (API keys, database URLs, JWT secrets, payment gateway secrets). For preview deploys on Vercel, set NEXT_PUBLIC_API_URL to the staging URL via Vercel's environment configuration, scoped to Preview only, Vercel lets you set different values for Production, Preview, and Development.

For self-hosted apps in India running behind a corporate proxy or on-premise, prefer Infisical, Doppler, HashiCorp Vault, or AWS Secrets Manager over committing .env files, Razorpay and Postman both run this pattern. A second gotcha specific to Next.js: NEXT_PUBLIC_ variables are baked in at BUILD time, not runtime, so changing them in your deployment environment without rebuilding does nothing. For runtime configurability of public values, use a /api/config endpoint or pass values from the server through props. Finally, for type safety, validate env vars at startup with Zod (a t3-env-style pattern) so a missing variable causes a clear error message at boot instead of an undefined-at-runtime mystery.

// .env.local
DATABASE_URL=postgres://...           # server-only, secret
NEXT_PUBLIC_API_URL=https://api.example.com  # exposed to browser

// Server Component or Route Handler
const db = await connect(process.env.DATABASE_URL);  // OK

// Client Component
'use client';
fetch(process.env.NEXT_PUBLIC_API_URL + '/me');     // OK
fetch(process.env.DATABASE_URL);                     // undefined!
Q25

How do you handle internationalization (i18n) in Next.js?

IntermediateInternationalization

Answer

The Pages Router has built-in i18n routing (i18n config in next.config.js with locales and defaultLocale), but the App Router does NOT, you build it yourself. The recommended pattern is to use a dynamic [locale] segment at the top of your tree: /app/[locale]/page.tsx, /app/[locale]/blog/[slug]/page.tsx. Middleware detects the user's locale (Accept-Language header, cookie, or geolocation) and rewrites to the correct prefix.

Libraries: next-intl is the most popular for App Router, it handles message catalogs, plural rules, date/number formatting, and gives you Server-Component-safe translation helpers. next-i18next is the older Pages Router choice. For Indian apps, common locale needs: English, Hindi, regional languages (Tamil, Bengali, Telugu), the harder part is usually right-to-left layout (Arabic, Urdu) and Indic font subsetting. Pair next/font with the regional subset you need to keep font payloads under 50 KB per locale. Don't forget: hreflang tags in your <head> for each language version, and a sitemap per locale for Google to discover translations.

// /middleware.ts
import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
  locales: ['en', 'hi', 'ta'],
  defaultLocale: 'en',
  localePrefix: 'always',
});
export const config = { matcher: ['/((?!api|_next|.*\\..*).*)'] };

// /app/[locale]/page.tsx
import { useTranslations } from 'next-intl';
export default function Home() {
  const t = useTranslations('home');
  return <h1>{t('title')}</h1>;
}
Q26

How do you optimise a Next.js app for the lowest possible Largest Contentful Paint (LCP) in India?

AdvancedPerformance

Answer

LCP under 2.5s on a typical Indian 4G connection (300-500 KB/s, 100-200ms RTT to Mumbai) takes deliberate work. The wins, in order of impact: (1) Prerender the LCP element. Whether it is a hero image or a headline, it should be in the static HTML of the first byte, use Server Components and avoid CSR for the hero. (2) Use next/image with priority on the hero so it skips lazy loading, with explicit sizes so the browser picks the smallest variant, and serve AVIF/WebP. (3) Self-host fonts via next/font so the page does not wait for fonts.gstatic.com (which routes through Singapore from most of India). (4) Inline critical CSS, Next.js does this automatically for Tailwind and CSS Modules.

Avoid heavy CSS-in-JS that needs runtime. (5) Move third-party scripts (analytics, chat widgets, Hotjar) to next/script with strategy='lazyOnload' or 'afterInteractive'. Many Indian sites tank LCP because they load 800 KB of Google Tag Manager up-front. (6) Use Partial Prerendering: the LCP region in the static shell, dynamic personalisation streamed in via Suspense. (7) CDN with Indian PoPs, Vercel has Mumbai (BOM1) in 2026, Cloudflare has 5 Indian PoPs. (8) Profile with WebPageTest from a Mumbai 4G location, NOT Chrome DevTools throttling, actual TLS handshake time to a US-only server can add 600ms LCP that DevTools simulates badly.

Key Points

  • Prerender the LCP element with priority + AVIF
  • Self-host fonts via next/font
  • Defer third-party scripts with next/script strategy
  • Use Partial Prerendering for personalized but fast pages
  • Test with WebPageTest from Mumbai 4G, not DevTools throttle
Q27

How would you architect a self-hosted Next.js deployment for an Indian SaaS at scale (1M+ MAU)?

AdvancedArchitecture

Answer

Vercel works fine at this scale but the bill becomes hard to justify in INR, most Indian SaaS at this stage self-host. A production architecture: (1) Build a standalone Docker image (output: 'standalone' in next.config.js), the slim build is ~150 MB instead of ~500 MB. Bun as the runtime cuts memory by another 30% over Node, though it is still not 100% compatible with every npm package. (2) Run 3+ replicas behind an nginx or Envoy load balancer, in an Indian region (AWS ap-south-1 Mumbai, GCP asia-south1 Mumbai).

At least 1 GB RAM per replica because the image optimizer (sharp) is memory-hungry. (3) Set up a custom incremental cache handler (Redis or Vercel KV) so ISR revalidations propagate across replicas, without it, each replica has its own file-system cache and you get inconsistent revalidations. The Next.js docs cover @neshca/cache-handler as the most popular community option. (4) Front everything with Cloudflare or BunnyCDN, both have Indian PoPs and cost a fraction of CloudFront. Configure cache rules so static assets (/_next/static, /_next/image) cache for 1 year, HTML caches based on Cache-Control headers Next.js emits. (5) Image optimization at the edge: either delegate to Cloudflare Image Resizing, or run a dedicated image-optimization service so the Next.js servers do not get hammered by sharp. (6) Observability: OpenTelemetry → SignOz (cheaper than Datadog and runs in India), Sentry for errors. (7) CI/CD via GitHub Actions → Docker registry → blue-green deploy. Database (RDS Postgres) in the same region as the app servers, cross-region adds 30-50ms per query and kills SSR latency.

Key Points

  • Standalone Docker build (output: 'standalone')
  • Shared cache handler (Redis) for multi-replica ISR
  • Cloudflare/BunnyCDN for India edge
  • Image optimization at the edge or dedicated service
  • SignOz over Datadog for cost in INR
Q28

How does the dynamicIO mode in Next.js 15+ change the way you think about caching?

AdvancedCaching

Answer

dynamicIO (experimental, opt-in via experimental.dynamicIO in next.config.js) is the most aggressive caching model Next.js has shipped, and it is the architectural direction the framework is moving in for the long term. Under dynamicIO, EVERYTHING is dynamic by default, fetch calls, cache reads, even time-based logic like Date.now(), unless you explicitly wrap it in 'use cache'. This inverts the old model where you opted out of caching with cache: 'no-store'.

Why was this change made: in practice, the old defaults caused subtle staleness bugs because developers forgot to mark dynamic data as no-store, and users would see stale data after deploys until the cache happened to expire. There were also security bugs where user-specific data leaked across users because a per-user fetch was accidentally cached at the route level. The new model forces you to be explicit about what should be cached. 'use cache' can be applied at the function, component, or file level: a function annotated 'use cache' becomes a cached computation, the cached value is keyed by the function arguments (so different inputs get different cache entries); a component annotated 'use cache' has its rendered output cached and reused across requests.

You combine it with cacheLife() to set expiry (e.g., cacheLife('hours') or a custom Object) and cacheTag() to invalidate on demand via revalidateTag. The mental model: anything cached has to be explicitly marked, the framework can statically analyse what is dynamic vs cached at build time, and partial prerendering becomes more powerful because the boundary between static and dynamic is explicit at the code level rather than inferred from fetch options. The downside: it is a big migration, every cached value has to be annotated, and it requires React 19's caching primitives plus tight Next.js coupling.

Most teams in 2026 are evaluating it for new greenfield apps but not retro-fitting it into existing production apps yet. When asked about it in interviews, the right answer is: 'I have read the RFC and tracked the experimental status; for new apps starting today I would consider it, but for our existing app the migration cost is not justified yet.'

💡 Pro Tip: If asked 'where is Next.js caching heading?', dynamicIO is the answer. Mention you have read the RFC even if you have not adopted it yet.
Q29

How would you implement on-demand ISR with a webhook from a headless CMS, in a multi-instance self-hosted setup?

AdvancedArchitecture

Answer

Scenario: editors publish in Sanity / Contentful / Strapi, you want the affected /blog/[slug] page to refresh within seconds, and you run 4 replicas behind a load balancer. The architecture: (1) Expose a Route Handler /api/revalidate that takes a webhook payload with the slug and a shared secret. Validate the secret to prevent abuse. (2) Inside the handler, call revalidatePath(`/blog/${slug}`) and/or revalidateTag('posts'). (3) For a multi-instance setup, the critical step is the shared cache handler, without it, only the replica that received the webhook revalidates, and the other replicas still serve stale HTML.

Configure cacheHandler in next.config.js to point at @neshca/cache-handler backed by Redis. Now revalidatePath broadcasts the invalidation through Redis pub/sub, and every replica drops the stale entry. (4) For Vercel, this is handled automatically by the Vercel Data Cache. (5) Edge case: the CMS webhook can fire multiple times for a single edit. Debounce with a Redis SETEX (key = slug, expire = 5s) to coalesce bursts. (6) Monitor revalidation latency in SignOz, if it creeps above the SLA, the bottleneck is usually Redis or sharp regenerating the og:image for the post. (7) For SEO content where freshness matters within seconds (live news, scores, stock prices), use export const dynamic = 'force-dynamic' with a short Vercel/Cloudflare CDN TTL instead of ISR, ISR's eventual-consistency window is too wide.

// /app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';
import { NextResponse } from 'next/server';

export async function POST(request: Request) {
  const secret = request.headers.get('x-webhook-secret');
  if (secret !== process.env.REVALIDATE_SECRET) {
    return NextResponse.json({ error: 'unauthorized' }, { status: 401 });
  }
  const { slug } = await request.json();
  revalidatePath(`/blog/${slug}`);
  revalidateTag('posts');
  return NextResponse.json({ revalidated: true, slug });
}

// next.config.js
module.exports = {
  cacheHandler: require.resolve('./cache-handler.js'),
  cacheMaxMemorySize: 0,  // disable in-memory cache, use Redis only
};
Q30

What are the trade-offs between Next.js, Remix, and Astro for a content-heavy SEO product in India?

AdvancedArchitecture

Answer

All three can serve content-heavy SEO sites, the differences are in defaults and ecosystem fit. Next.js (App Router) is the default for content-and-product hybrid apps, the same codebase ships your marketing pages, blog, and authenticated dashboard. Strengths: massive ecosystem (every CMS, every auth, every payment provider has a Next.js SDK), best-in-class SSR with RSC, partial prerendering, and Vercel's edge network has a Mumbai PoP.

Weaknesses: complex caching model, big framework, JavaScript-heavy by default (even with RSC, hydration ships React). Remix (now React Router 7) is more 'web fundamentals', strong on forms, nested routes, error boundaries, and progressive enhancement. Smaller ecosystem, lighter JavaScript footprint, but no RSC equivalent yet in stable.

Good fit for highly form-driven apps. Astro is the SEO-pure choice, by default it ships ZERO JavaScript. Pages are static HTML, and you 'island' interactivity (a React/Vue/Svelte component) only where needed.

For a pure marketing site, blog, or documentation in India, Astro is hard to beat for LCP, 100% HTML over the wire, often <50 KB total. Weaknesses: if you also need an authenticated app behind /dashboard, you need a second framework or you complicate Astro with too many islands. For GoodSpace's interview-questions pages specifically, Next.js makes sense because the rest of the product is also Next.js, duplicating the team's framework knowledge in Astro would not pay off unless the SEO impact is huge.

Key Points

  • Next.js: dominant ecosystem, RSC, partial prerendering
  • Remix: lighter, forms-first, no RSC yet
  • Astro: zero-JS by default, best LCP for pure content sites
  • Mixed stacks are usually a mistake, pick one and own it

Companies Hiring Next.js

Vercel
Netflix
TikTok
Hulu
Razorpay
Postman
CRED

Salary Insights

Average in India
₹7-24 LPA

Frequently Asked Questions

Is Next.js worth learning in 2026 if I already know React?

Yes, almost every senior frontend job posting in India (Razorpay, CRED, Postman, Meesho, Vercel India) lists Next.js as required or strongly preferred. A pure React (Vite) developer in 2026 caps out around ₹15 LPA; adding Next.js, RSC, and the App Router pushes that to ₹18-24 LPA at the mid-senior level and over ₹30 LPA at staff.

Should I learn the Pages Router or jump straight into the App Router?

Start with the App Router, it is what new projects use and what interviewers expect. But spend a few hours understanding Pages Router (getStaticProps, getServerSideProps, _app.tsx, _document.tsx, /pages/api) because most production codebases in 2026 still maintain Pages Router routes you will need to read and migrate.

How much does a Next.js developer earn in India?

₹7-24 LPA in 2026 for mid-to-senior frontend developers with strong Next.js skills. Junior roles (0-2 years) start at ₹5-9 LPA; staff and principal levels at unicorns (Razorpay, CRED, Postman) cross ₹30 LPA. Specialised performance/SEO roles at e-commerce (Meesho, Nykaa) and EdTech (Unacademy, GeeksforGeeks) pay at the upper end.

Can I run Next.js without Vercel?

Yes, Next.js is fully open source and runs anywhere Node.js or Bun runs. Self-hosting on AWS, GCP, DigitalOcean, or Coolify is common. The main thing you lose is automatic image optimization scaling (you need to size your server for sharp) and a shared ISR cache (you need to wire up @neshca/cache-handler with Redis). Vercel's pricing in India starts free (Hobby), then $20/month (~₹1,700) for Pro, for many Indian startups this is cheaper than the engineering time to self-host.

What is the most asked Next.js interview question in 2026?

'Explain the difference between Server Components and Client Components, and when you would use each.' Variants probe caching ('what are the four Next.js cache layers?'), hydration ('why does my useState throw?'), and architecture ('why is your form a server action instead of a fetch?'). If you can answer those four families clearly with real examples, you will pass most senior frontend interviews.

Which companies in India hire heavily for Next.js?

Razorpay, CRED, Postman, Meesho, Nykaa, Cure.fit, Unacademy, GeeksforGeeks, Vercel India, Hashnode, and most YC-backed Indian SaaS (Zoho subsidiaries are an exception, they still mostly use Vue / Ember). Even Tata Neu and ICICI's newer customer-facing surfaces use Next.js. The single biggest demand cluster is fintech and EdTech, where SEO and Core Web Vitals directly drive business. International companies with significant Indian engineering presence, Netflix, TikTok, Hulu, Vercel itself, also hire Next.js engineers from India for both India-based and remote roles, and these typically pay 1.5-2x the local market rate. If you are early-career and targeting Next.js specifically, prioritise TypeScript fluency alongside it, every senior interview will assume it, and starting in TypeScript from day one saves weeks of rework later.

Introduction

Next.js has become the default React meta-framework for production teams in 2026. Since the App Router stabilised in Next.js 13.4 and partial prerendering shipped in 15, the framework has shifted from a 'React with SSR' library to a full hybrid runtime that mixes static, streaming, and server components in a single response. If you have used Next.js commercially in the past two years, you have lived through Pages Router, App Router, React Server Components, server actions, and the new caching model, and an interviewer will probe all of them.

In India, Next.js is the dominant choice for senior frontend roles at Razorpay, CRED, Postman, Meesho, Cure.fit, and most YC-backed Indian SaaS startups. Salary expectations for mid-to-senior frontend engineers with strong Next.js experience sit in the ₹7-24 LPA range in 2026, with staff-level roles at unicorns crossing ₹30 LPA. Even GeeksforGeeks rebuilt its own learning surface on Next.js, which tells you how mainstream the stack has become for Indian content-heavy products.

This guide covers the 30 most-asked Next.js interview questions in 2026, grouped by difficulty. Every answer is grounded in the App Router as the default, but Pages Router context is called out because most production apps still ship a hybrid of the two. Each question includes the underlying concept, common gotchas, code examples where they add clarity, and tips on what interviewers really want to hear.

Ready to practice Next.js interviews?

Don't just read, practice these Next.js questions live with an AI interviewer that asks follow-ups and scores your answers.

AI-powered practice
Instant feedback
Free to start
Start Free Mock Interview