0% found this document useful (0 votes)
2 views47 pages

NextJS Interview Questions Guide

This document is a comprehensive guide to Next.js interview questions, covering various topics such as App Router, Server Components, rendering strategies, and data-fetching methods. It includes 99 curated questions with explanations, examples, and potential pitfalls to watch for. The guide aims to prepare candidates for interviews by providing in-depth knowledge about Next.js features and best practices.

Uploaded by

atharvninave18
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views47 pages

NextJS Interview Questions Guide

This document is a comprehensive guide to Next.js interview questions, covering various topics such as App Router, Server Components, rendering strategies, and data-fetching methods. It includes 99 curated questions with explanations, examples, and potential pitfalls to watch for. The guide aims to prepare candidates for interviews by providing in-depth knowledge about Next.js features and best practices.

Uploaded by

atharvninave18
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

NEXT.

JS
INTERVIEW QUESTIONS
A Complete Guide with Explanations, Examples & Gotchas

99 CURATED QUESTIONS

Covering App Router, Server Components, Rendering Strategies,


Caching, Routing, Performance, Testing & [Link] 15
Q1. What is [Link] and how does it differ from plain React?

[Link] is a full-stack React framework built on top of React that adds file-based routing, server-side
rendering (SSR), static site generation (SSG), API routes, image/font optimization, and bundling out of
the box. Plain React (via Create React App or Vite) is only a UI library — it renders in the browser and
leaves routing, data fetching, SEO, and build tooling entirely up to you. [Link] essentially wraps React
with the missing 'application' layer: a router, a server, and a build pipeline.
Example:

// Plain React: you wire up your own router, no server rendering


// import { BrowserRouter, Route } from 'react-router-dom'

// [Link]: routing is just the file system


// app/[Link] -> "/"
// app/about/[Link] -> "/about"
export default function Home() {
return <h1>Hello from [Link]</h1>;
}

Gotchas / Things to watch for:


⚠ [Link] is opinionated — you trade some flexibility for a lot of built-in performance best practices.
⚠ React alone has no concept of a 'page' or a server; every [Link] app is still 'just React' under the hood.
🔗 Read more: [Link] official docs

Q2. What is the difference between pages router and app router?

The Pages Router (the original, in a `pages/` directory) uses `getStaticProps`/`getServerSideProps` for
data fetching and React Class/Function components rendered entirely as Client Components. The App
Router (in an `app/` directory, stable since [Link] 13) is built on React Server Components by default,
supports nested layouts, streaming, and colocated data fetching with plain `async/await` inside
components. The App Router is the recommended approach for new projects; Pages Router is still
supported for legacy apps.
Example:

// Pages Router
// pages/[Link]
export async function getServerSideProps() {
return { props: { time: [Link]() } };
}
export default function Home({ time }) { return <p>{time}</p>; }

// App Router (Server Component by default)


// app/[Link]
export default async function Home() {
const time = [Link]();

2
return <p>{time}</p>;
}

Gotchas / Things to watch for:


⚠ You can migrate incrementally — both routers can coexist in the same project.
⚠ Data-fetching functions like getStaticProps do NOT work inside app/; use async components or fetch()
instead.
🔗 Read more: [Link]: App Router vs Pages Router

Q3. What is file-based routing in [Link]?

[Link] automatically creates a route for every file placed in the `pages/` (Pages Router) or `app/` (App
Router) directory — there's no manual route configuration. A file at `app/blog/[Link]` automatically
becomes accessible at `/blog`. Folder nesting maps directly to URL path nesting, which keeps routing
declarative and colocated with the UI code.
Example:

app/
[Link] -> /
about/[Link] -> /about
blog/[slug]/[Link] -> /blog/:slug

Gotchas / Things to watch for:


⚠ In the App Router, only files literally named [Link] (or [Link]) are routable — other files in the same
folder (components, utils) are not exposed as routes.

Q4. What is getStaticProps and when do you use it?

getStaticProps is a Pages-Router data-fetching function that runs at build time (or on revalidation) to
generate a page's props ahead of time, producing static HTML. Use it when the data doesn't change per-
request — marketing pages, blogs, documentation — because it gives the best performance (served
from a CDN with no server compute per request).
Example:

// pages/posts/[id].js
export async function getStaticProps({ params }) {
const post = await getPostById([Link]);
return { props: { post }, revalidate: 60 }; // ISR every 60s
}

Gotchas / Things to watch for:


⚠ Only available in the Pages Router — the App Router equivalent is fetch() with { cache: 'force-cache' }
(the default).

3
⚠ Runs only on the server/build machine, never in the browser, so it's safe to use secrets and direct DB
calls.

Q5. What is getServerSideProps and when do you use it?

getServerSideProps is a Pages-Router function that runs on every request on the server, right before
rendering, letting you fetch fresh, request-specific data (e.g., based on cookies or query params). Use it
for pages that must always show up-to-date or personalized data, at the cost of a slower response than
static generation since work happens per-request.
Example:

export async function getServerSideProps(context) {


const { req } = context;
const user = await getUserFromSession(req);
return { props: { user } };
}

Gotchas / Things to watch for:


⚠ Every request hits the server — no CDN caching by default, so it can become a bottleneck at scale.
⚠ In the App Router, the equivalent is fetch() with { cache: 'no-store' } inside a Server Component.

Q6. What is getStaticPaths?

getStaticPaths is used alongside getStaticProps on dynamic Pages-Router routes (e.g., `[id].js`) to tell
[Link] which dynamic values should be pre-rendered at build time. It returns a list of params plus a
`fallback` strategy for paths not included in that list.
Example:

export async function getStaticPaths() {


const posts = await getAllPostIds();
return {
paths: [Link]((id) => ({ params: { id } })),
fallback: 'blocking' // or true / false
};
}

Gotchas / Things to watch for:


⚠ fallback: false 404s on any path not returned; fallback: true serves a loading state then generates on
demand; 'blocking' waits server-side before responding (SEO-friendly).
⚠ App Router equivalent is generateStaticParams().

Q7. What is Incremental Static Regeneration (ISR)?

4
ISR lets you update statically generated pages after the site has been built, without a full rebuild. By
setting a `revalidate` time, [Link] will regenerate a page in the background after that many seconds
have passed since the last request, then swap in the fresh version — combining the speed of static
pages with data freshness.
Example:

// pages/products/[id].js
export async function getStaticProps() {
return { props: { data: await getData() }, revalidate: 120 };
}

// App Router equivalent


export const revalidate = 120;

Gotchas / Things to watch for:


⚠ The first request after expiry still gets the stale page (stale-while-revalidate); the *next* request gets
the fresh one.
⚠ On-demand revalidation (revalidatePath/revalidateTag) lets you bust the cache instantly instead of
waiting for the timer.
🔗 Read more: [Link] ISR docs

Q8. What are Server Components in [Link] App Router?

Server Components (RSC) are the default component type in the App Router. They render entirely on
the server, never ship their JavaScript to the browser, and can directly access backend resources
(databases, file system, secrets) using async/await. This shrinks client bundle size and speeds up initial
load since only the rendered output (a special RSC payload/HTML) is sent to the browser.
Example:

// app/products/[Link] (Server Component by default)


export default async function Products() {
const products = await [Link]();
return <ul>{[Link](p => <li key={[Link]}>{[Link]}</li>)}</ul>;
}

Gotchas / Things to watch for:


⚠ Server Components can't use hooks like useState/useEffect, or browser-only APIs — those require a
Client Component.
⚠ You can't pass functions (e.g., event handlers) as props from a Server Component to a Client
Component, only serializable data.
🔗 Read more: React docs: Server Components

Q9. What are Client Components and when do you need them?

5
Client Components are opted into with the 'use client' directive at the top of a file. They render on the
server for the initial HTML but are then hydrated and run in the browser, so they support interactivity:
state, effects, event handlers, and browser-only APIs (localStorage, window, etc.). Use them for anything
interactive — forms, dropdowns, modals, real-time UI.
Example:

'use client';
import { useState } from 'react';

export default function Counter() {


const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

Gotchas / Things to watch for:


⚠ 'use client' marks the boundary — everything imported into that file becomes part of the client bundle,
so keep the boundary as low/leaf-level as possible for performance.
⚠ A Client Component can still receive Server Components as children via the children prop pattern.

Q10. What is the difference between 'use client' and 'use server' directives?

'use client' marks a module boundary telling [Link] a component (and its imports) must be bundled and
run in the browser. 'use server' marks a function as a Server Action — code that always runs on the
server but can be called directly from Client Components (e.g., on form submission) without manually
creating an API route.
Example:

// Client Component
'use client';
export default function Form({ action }) {
return <form action={action}><button>Submit</button></form>;
}

// Server Action
'use server';
export async function createPost(formData) {
await [Link]({ data: { title: [Link]('title') } });
}

Gotchas / Things to watch for:


⚠ 'use server' functions are exposed as callable HTTP endpoints under the hood — never put unchecked
secrets/logic there without validating input.
⚠ 'use client' applies to a whole file; 'use server' can be applied per-function or file-wide.

Q11. What are Server Actions in [Link]?

6
Server Actions are async functions marked with 'use server' that run exclusively on the server but can be
invoked directly from Client or Server Components — most commonly as a form's action prop, or called
from an onClick handler. They replace the need to hand-roll an API route + fetch call for simple
mutations like creating, updating, or deleting data.
Example:

// app/[Link]
'use server';
export async function addTodo(formData: FormData) {
await [Link]({ data: { title: [Link]('title') } });
revalidatePath('/todos');
}

// app/todos/[Link]
import { addTodo } from '../actions';
export default function Page() {
return <form action={addTodo}><input name="title" /><button>Add</button></form>;
}

Gotchas / Things to watch for:


⚠ Server Actions run inside a POST request under the hood — always validate/authorize input server-side;
never trust the client.
⚠ Pair with revalidatePath/revalidateTag to refresh cached data after a mutation.
🔗 Read more: [Link] Server Actions docs

Q12. What is the [Link] file in App Router?

A [Link]/tsx file wraps a segment and all of its nested children with shared UI (navbars, sidebars,
footers) that persists across navigation without re-rendering or losing state. Layouts are nested
automatically based on folder structure, and the root [Link] (required) defines the <html> and
<body> tags for the whole app.
Example:

// app/[Link] (root layout)


export default function RootLayout({ children }: { children: [Link] }) {
return (
<html lang="en">
<body><Navbar />{children}</body>
</html>
);
}

Gotchas / Things to watch for:


⚠ Layouts don't receive the current route's searchParams — only pages do.
⚠ Because layouts persist across navigations, client-side state inside them (e.g., a sidebar's open/closed
state) is preserved when moving between child pages.

7
Q13. What is [Link] and how does it enable streaming?

[Link] defines an instant loading UI (usually a skeleton or spinner) for a route segment. [Link]
automatically wraps the page in a React Suspense boundary, so while async Server Components are still
fetching data, the loading UI streams to the browser immediately, and the real content streams in and
replaces it once ready — without blocking the whole page.
Example:

// app/dashboard/[Link]
export default function Loading() {
return <p>Loading dashboard…</p>;
}

Gotchas / Things to watch for:


⚠ [Link] applies to the whole segment; for more granular control, wrap individual components in your
own <Suspense> boundaries.
⚠ It only shows during the initial navigation to that segment, not on every re-render.

Q14. What is [Link] in [Link]?

[Link] defines a Client Component error boundary automatically wrapped around a route segment,
catching runtime errors in that segment (and its children) and rendering a fallback UI instead of crashing
the whole app. It receives an `error` object and a `reset()` function to attempt re-rendering the segment.
Example:

'use client';
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return (
<div>
<p>Something went wrong: {[Link]}</p>
<button onClick={() => reset()}>Try again</button>
</div>
);
}

Gotchas / Things to watch for:


⚠ [Link] must be a Client Component ('use client') since error boundaries rely on React class-component
lifecycle internally.
⚠ It does not catch errors thrown in the root layout — for that, use [Link].

Q15. What is [Link]?

8
[Link] renders the UI shown when the `notFound()` function is called inside a route segment, or
when a URL doesn't match any route. It lets you customize the 404 experience per-section of your app
instead of a single generic 404 page.
Example:

// app/blog/[slug]/[Link]
export default function NotFound() {
return <h2>This blog post could not be found.</h2>;
}

// Triggering it
import { notFound } from 'next/navigation';
if (!post) notFound();

Gotchas / Things to watch for:


⚠ A [Link] only catches notFound() calls or unmatched routes within its own segment tree; the
closest one up the tree is used.

Q16. What is the [Link] file?

[Link] is similar to [Link] but creates a brand-new instance of its component (and resets state,
re-runs effects) on every navigation, instead of persisting like a layout does. Useful for enter/exit
animations or features that must re-run per navigation (e.g., logging a page view).
Example:

// app/[Link]
export default function Template({ children }: { children: [Link] }) {
return <div className="fade-in">{children}</div>;
}

Gotchas / Things to watch for:


⚠ Because it remounts on every navigation, using a [Link] everywhere hurts performance compared
to [Link] — use it sparingly, only when remounting is actually needed.

Q17. How does [Link] handle metadata (SEO)?

The App Router provides a Metadata API: export a static `metadata` object or an async
`generateMetadata()` function from any page/layout to set title, description, Open Graph tags, canonical
URLs, and more. [Link] merges metadata from parent to child segments automatically, so you only
override what changes.
Example:

// app/blog/[slug]/[Link]
export async function generateMetadata({ params }) {

9
const post = await getPost([Link]);
return { title: [Link], description: [Link] };
}

Gotchas / Things to watch for:


⚠ generateMetadata runs on the server and can fetch data — avoid duplicate fetches by using React's
cache() or fetch's built-in dedup.
⚠ Dynamic metadata blocks streaming for the <head> tags until it resolves; keep it fast.
🔗 Read more: [Link] Metadata API docs

Q18. What is the next/image component and its benefits?

next/image is an enhanced <img> replacement that automatically optimizes images: resizing, serving
modern formats (WebP/AVIF), lazy-loading offscreen images, and preventing layout shift by requiring
width/height (or `fill`). It significantly improves Core Web Vitals like LCP and CLS with no manual work.
Example:

import Image from 'next/image';

export default function Avatar() {


return <Image src="/[Link]" alt="Profile photo" width={200} height={200} priority />;
}

Gotchas / Things to watch for:


⚠ Remote images need their domain allow-listed in [Link] under [Link].
⚠ Use the `priority` prop only for above-the-fold images (like a hero image) — it disables lazy loading.
🔗 Read more: next/image API reference

Q19. What is the next/link component?

next/link renders a client-side navigable <a> tag that enables fast transitions between routes without a
full page reload. It automatically prefetches the linked page's code (and, in the App Router, its RSC
payload) when it scrolls into the viewport, making navigations feel instant.
Example:

import Link from 'next/link';

export default function Nav() {


return <Link href="/about">About</Link>;
}

Gotchas / Things to watch for:

10
⚠ Prefetching only happens automatically in production builds, not in next dev.
⚠ For fully static routes prefetch downloads the whole page; for dynamic routes it only prefetches shared
layouts, to save bandwidth.

Q20. What is the next/font system?

next/font automatically self-hosts any font (including Google Fonts) at build time, removing the extra
network request to Google's CDN and eliminating layout shift from font swapping (via automatic `size-
adjust`). Fonts are loaded with zero external requests, improving both privacy and performance.
Example:

import { Inter } from 'next/font/google';


const inter = Inter({ subsets: ['latin'] });

export default function Layout({ children }) {


return <html className={[Link]}>{children}</html>;
}

Gotchas / Things to watch for:


⚠ Local custom fonts use next/font/local instead of next/font/google.
⚠ Fonts are downloaded once at build time and cached — no runtime calls to Google's servers ever
happen.
🔗 Read more: next/font docs

Q21. What is the next/script component?

next/script optimizes loading of third-party scripts (analytics, ads, chat widgets) by giving you control
over *when* they load via the `strategy` prop: beforeInteractive, afterInteractive (default), lazyOnload,
or worker (experimental, runs off the main thread). This avoids third-party scripts blocking rendering or
hurting Core Web Vitals.
Example:

import Script from 'next/script';

export default function Page() {


return <Script src="[Link] strategy="lazyOnload" />;
}

Gotchas / Things to watch for:


⚠ beforeInteractive should be reserved for scripts truly needed before hydration (e.g., bot detection); it
can delay interactivity if overused.
🔗 Read more: next/script docs

11
Q22. How does [Link] handle environment variables?

[Link] loads variables from .env, .[Link], .[Link], etc. automatically. Variables are server-
only by default; to expose one to the browser bundle, prefix it with NEXT_PUBLIC_. This prevents
accidentally leaking secrets like API keys to the client.
Example:

# .[Link]
DATABASE_URL=postgres://...
NEXT_PUBLIC_ANALYTICS_ID=abc123

// usage
const dbUrl = [Link].DATABASE_URL; // server only
const id = [Link].NEXT_PUBLIC_ANALYTICS_ID; // available in browser too

Gotchas / Things to watch for:


⚠ .[Link] is gitignored by default and overrides other .env files — great for secrets that shouldn't be
committed.
⚠ NEXT_PUBLIC_ variables are inlined at build time, so changing them requires a rebuild, not just a
redeploy of the same build.
🔗 Read more: Environment variables docs

Q23. What is the public directory in [Link]?

The /public folder at the project root serves static assets (images, fonts, favicon, [Link]) directly from
the base URL. Anything placed there is accessible at the root path without any import, e.g.,
public/[Link] is served at /[Link].
Example:

<img src="/[Link]" alt="Logo" /> // resolves to /public/[Link]

Gotchas / Things to watch for:


⚠ Files in /public are not processed/optimized by Webpack/Turbopack — for images prefer next/image
(which can still point at /public paths).

Q24. What is the [Link] file?

[Link] is the central configuration file for a [Link] project — controlling things like image
domains, redirects/rewrites, custom headers, environment variable exposure, experimental features,
Webpack/Turbopack customization, and build output mode (standalone/export).
Example:

12
/** @type {import('next').NextConfig} */
const nextConfig = {
images: { remotePatterns: [{ hostname: '[Link]' }] },
async redirects() {
return [{ source: '/old', destination: '/new', permanent: true }];
}
};
[Link] = nextConfig;

Gotchas / Things to watch for:


⚠ Changes to [Link] require a dev server restart — they aren't hot-reloaded.
🔗 Read more: [Link] reference

Q25. How do you create API routes in [Link]?

In the Pages Router, any file under pages/api/ exports a handler function and becomes a serverless API
endpoint (e.g., pages/api/[Link] -> /api/hello). In the App Router, this is done with Route Handlers
([Link] files) exporting functions named after HTTP methods.
Example:

// pages/api/[Link] (Pages Router)


export default function handler(req, res) {
[Link](200).json({ message: 'Hello' });
}

Gotchas / Things to watch for:


⚠ API routes run as serverless functions on most hosts — cold starts can add latency for infrequently hit
endpoints.

Q26. What is Route Handlers in App Router ([Link])?

Route Handlers are the App Router's replacement for API routes. A [Link] file inside app/ exports
functions named GET, POST, PUT, DELETE, etc., using the Web standard Request/Response APIs instead
of Node's req/res, making them portable to edge runtimes.
Example:

// app/api/users/[Link]
export async function GET() {
const users = await [Link]();
return [Link](users);
}

export async function POST(request: Request) {


const body = await [Link]();
const user = await [Link]({ data: body });

13
return [Link](user, { status: 201 });
}

Gotchas / Things to watch for:


⚠ A [Link] cannot coexist with a [Link] in the same route segment — a segment is either a page or an
API route, not both.
🔗 Read more: Route Handlers docs

Q27. What is middleware in [Link]?

Middleware is code defined in a single [Link] file at the project root that runs before a request
completes, on the Edge Runtime. It's used for authentication checks, redirects, rewrites, A/B testing,
geolocation-based logic, and setting/reading cookies — all before a page renders.
Example:

// [Link]
import { NextResponse } from 'next/server';

export function middleware(request) {


const isLoggedIn = [Link]('session');
if (!isLoggedIn && [Link]('/dashboard')) {
return [Link](new URL('/login', [Link]));
}
return [Link]();
}

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

Gotchas / Things to watch for:


⚠ Middleware runs on the Edge Runtime by default, which doesn't support all [Link] APIs (no fs, limited
crypto).
⚠ Keep middleware lightweight — it runs on every matched request and adds latency to each one.
🔗 Read more: [Link] Middleware docs

Q28. How does [Link] handle authentication?

[Link] has no built-in auth system — it provides the primitives (middleware, cookies(), Server Actions,
Route Handlers) to build one, or you plug in a library like [Link]/[Link], Clerk, or Lucia. A typical
pattern: middleware checks a session cookie/JWT for route protection, while Server Components read
the session server-side to conditionally render UI.
Example:

// Reading a session in a Server Component

14
import { cookies } from 'next/headers';

export default async function Dashboard() {


const token = cookies().get('session')?.value;
const user = await verifySession(token);
if (!user) redirect('/login');
return <p>Welcome, {[Link]}</p>;
}

Gotchas / Things to watch for:


⚠ Never rely solely on client-side checks (hiding a button) for protecting sensitive data or actions —
always re-verify on the server.
🔗 Read more: [Link] Authentication docs

Q29. What is [Link]?

[Link] (now [Link]) is a widely used open-source authentication library for [Link] providing pre-
built support for OAuth providers (Google, GitHub, etc.), email/passwordless login, JWT or database
sessions, and easy integration with Route Handlers/middleware — saving you from building auth flows
from scratch.
Example:

// app/api/auth/[...nextauth]/[Link]
import NextAuth from 'next-auth';
import GitHub from 'next-auth/providers/github';

const handler = NextAuth({ providers: [GitHub({ clientId: '...', clientSecret: '...' })]
});
export { handler as GET, handler as POST };

Gotchas / Things to watch for:


⚠ The project renamed to [Link] and expanded beyond [Link] — check current docs since APIs have
shifted across major versions.
🔗 Read more: [Link] (NextAuth) documentation

Q30. What is the difference between static and dynamic rendering in [Link]?

Static rendering pre-renders a route's HTML at build time (or via ISR), so it's cached and served instantly
to every user — ideal for content that's the same for everyone. Dynamic rendering renders the route on
the server for every incoming request, needed when the output depends on request-time data (cookies,
headers, search params, or uncached data fetches).
Example:

// Forces dynamic rendering

15
export const dynamic = 'force-dynamic';

// Forces static rendering


export const dynamic = 'force-static';

Gotchas / Things to watch for:


⚠ [Link] auto-detects which mode to use based on what APIs you call (e.g., using cookies() or headers()
opts a route into dynamic rendering automatically).
🔗 Read more: Rendering docs

Q31. What is Partial Prerendering (PPR)?

Partial Prerendering (experimental) lets a single route serve a static shell instantly while dynamic parts
stream in afterward — combining the speed of static generation with per-request personalization in one
page, without you needing to choose one rendering mode for the whole route. Static content is
wrapped implicitly; dynamic parts are wrapped in <Suspense>.
Example:

export const experimental_ppr = true;

export default function Page() {


return (
<>
<StaticHeader />
<Suspense fallback={<Skeleton />}>
<DynamicCart /> {/* streamed in per-request */}
</Suspense>
</>
);
}

Gotchas / Things to watch for:


⚠ Still experimental as of [Link] 15 — requires enabling the flag and understanding it can change before
stabilizing.
🔗 Read more: Partial Prerendering docs

Q32. What is Streaming in [Link] and how does Suspense enable it?

Streaming sends HTML to the browser in chunks as it becomes ready, instead of waiting for the entire
page's data to resolve. Wrapping a slow async Server Component in React's <Suspense> tells [Link] to
render a fallback immediately and stream in the real content once the data resolves — improving
perceived performance (Time to First Byte, First Contentful Paint).
Example:

16
import { Suspense } from 'react';

export default function Page() {


return (
<Suspense fallback={<p>Loading comments…</p>}>
<Comments /> {/* async Server Component */}
</Suspense>
);
}

Gotchas / Things to watch for:


⚠ Requires a [Link] server or platform that supports streaming responses — some serverless hosts
buffer output, negating streaming benefits.

Q33. What is the fetch() caching behavior in [Link] App Router?

[Link] extends the native fetch() with automatic caching: by default, `fetch(url)` results are cached
indefinitely (like getStaticProps), acting as static data. Passing `{ cache: 'no-store' }` opts out entirely (like
getServerSideProps), and `{ next: { revalidate: N } }` gives time-based ISR-style revalidation for that
specific fetch call.
Example:

// Cached indefinitely (static)


fetch('[Link]

// Never cached (dynamic, per-request)


fetch('[Link] { cache: 'no-store' });

// Revalidate every 60 seconds


fetch('[Link] { next: { revalidate: 60 } });

Gotchas / Things to watch for:


⚠ As of [Link] 15, the default caching behavior changed to be less aggressive by default in some contexts
— always check the version's docs before assuming caching semantics.
🔗 Read more: Data fetching & caching docs

Q34. What are cache tags and revalidation in [Link]?

Cache tags let you label a fetch's cached response with one or more strings via `{ next: { tags: ['posts'] }
}`, so you can later invalidate exactly that cached data (and anything sharing the tag) with
`revalidateTag('posts')`, without waiting for a time-based revalidation window.
Example:

fetch(url, { next: { tags: ['posts'] } });

17
// Later, e.g. inside a Server Action after a mutation:
import { revalidateTag } from 'next/cache';
revalidateTag('posts');

Gotchas / Things to watch for:


⚠ Tags only invalidate the [Link] Data Cache — they don't automatically bust CDN-level caches if you
have one in front of your app.

Q35. What is the revalidatePath and revalidateTag function?

Both are on-demand cache invalidation functions callable from Server Actions or Route Handlers.
revalidatePath('/blog') purges the cached render for that specific route (and optionally its layout), while
revalidateTag('posts') purges every cached fetch call tagged with that string, regardless of which route it
lives in.
Example:

'use server';
import { revalidatePath, revalidateTag } from 'next/cache';

export async function publishPost(id: string) {


await [Link]({ where: { id }, data: { published: true } });
revalidatePath('/blog');
revalidateTag('posts');
}

Gotchas / Things to watch for:


⚠ revalidatePath re-renders the route on the next visit; it doesn't proactively push updates to already-
open browser tabs.

Q36. What is unstable_cache in [Link]?

unstable_cache wraps an arbitrary async function (e.g., a database query) so its result is cached in the
[Link] Data Cache, similar to how fetch() is cached automatically — useful because raw database/ORM
calls aren't cached by fetch's mechanism by default.
Example:

import { unstable_cache } from 'next/cache';

const getCachedUser = unstable_cache(


async (id) => [Link]({ where: { id } }),
['user'],
{ revalidate: 3600, tags: ['user'] }
);

18
Gotchas / Things to watch for:
⚠ Still prefixed 'unstable_' — the API may change in future [Link] releases, so pin your [Link] version
carefully in production.

Q37. How does [Link] handle redirects and rewrites?

Redirects (permanent 308 or temporary 307) send the browser to a new URL and update the address
bar; rewrites proxy a request to a different internal path while keeping the original URL visible. Both can
be configured statically in [Link], or dynamically via middleware/the redirect() function.
Example:

// [Link]
[Link] = {
async redirects() {
return [{ source: '/old-blog/:slug', destination: '/blog/:slug', permanent: true }];
},
async rewrites() {
return [{ source: '/docs/:path*', destination: '[Link] }];
}
};

Gotchas / Things to watch for:


⚠ Rewrites are invisible to the user (URL stays the same) which is great for proxying a separate
backend/CMS without exposing it.
🔗 Read more: Redirects docs

Q38. What are dynamic routes in [Link]?

Dynamic routes use square-bracket folder/file names to match variable URL segments, e.g.,
app/blog/[slug]/[Link] matches /blog/anything, with the matched value available via the `params`
prop.
Example:

// app/blog/[slug]/[Link]
export default function Post({ params }: { params: { slug: string } }) {
return <h1>{[Link]}</h1>;
}

Q39. What are catch-all routes ([...slug])?

A catch-all route, written as [...slug], matches any number of remaining path segments as an array, e.g.,
app/docs/[...slug]/[Link] matches /docs/a, /docs/a/b, /docs/a/b/c, etc. Useful for nested content
structures like documentation trees.

19
Example:

// app/docs/[...slug]/[Link]
export default function Docs({ params }: { params: { slug: string[] } }) {
// /docs/a/b/c -> [Link] === ['a', 'b', 'c']
return <p>{[Link]('/')}</p>;
}

Q40. What are optional catch-all routes ([[...slug]])?

Adding a second set of brackets, [[...slug]], makes the catch-all segment optional, so the route also
matches the base path with zero segments (e.g., /docs itself), not just /docs/a/b — the regular catch-all
requires at least one segment.
Example:

// app/docs/[[...slug]]/[Link]
// Matches /docs, /docs/a, /docs/a/b, etc.
export default function Docs({ params }: { params: { slug?: string[] } }) {
return <p>{[Link]?.join('/') ?? 'Home'}</p>;
}

Q41. What are parallel routes in [Link]?

Parallel routes let you render two or more independent pages in the same layout simultaneously, using
named 'slots' created with an @folder convention (e.g., @team, @analytics). Each slot has its own
loading/error state and can navigate independently — useful for dashboards with multiple independent
panels, or modals.
Example:

app/
[Link]
@team/[Link]
@analytics/[Link]

// app/[Link]
export default function Layout({ children, team, analytics }) {
return (
<>
{children}
{team}
{analytics}
</>
);
}

Gotchas / Things to watch for:

20
⚠ Each slot needs its own [Link] as a fallback for when it doesn't match the current URL, to avoid a
404 for that slot.
🔗 Read more: Parallel Routes docs

Q42. What are intercepting routes?

Intercepting routes let you load a different route within the current layout while keeping the URL as if
you'd navigated to a full separate route — the classic use case is a photo feed where clicking a photo
opens it in a modal (intercepted), but a hard refresh or direct link loads the full standalone page.
Denoted with (.), (..), (..)(..), or (...) prefixes indicating how many segment levels to match against.
Example:

app/
feed/[Link]
feed/(..)photo/[id]/[Link] // intercepts /photo/[id] when navigated from within
/feed
photo/[id]/[Link] // the actual full page

Gotchas / Things to watch for:


⚠ Commonly paired with parallel routes (an @modal slot) to implement the modal-that's-also-a-real-
page pattern seen on sites like Instagram.
🔗 Read more: Intercepting Routes docs

Q43. What is the @folder convention in App Router?

An @folder (e.g., @analytics) defines a named slot for parallel routes. It's not itself part of the URL path
— it exists purely to organize independently renderable sections that get passed as props into the
parent layout.
Example:

app/
@analytics/
[Link]
[Link]
[Link]

Gotchas / Things to watch for:


⚠ @folders are invisible in the URL — don't confuse them with regular route segments.

Q44. How do you implement internationalization (i18n) in [Link]?

The App Router doesn't include built-in i18n routing (unlike the older Pages Router's i18n config) —
instead, you implement it yourself, typically with a [locale] dynamic segment at the root of app/,

21
middleware to detect/redirect based on the Accept-Language header or a cookie, and a library like next-
intl or next-i18next for translation strings.
Example:

app/
[locale]/
[Link]
[Link]

// [Link] detects locale and redirects "/" -> "/en" etc.

Gotchas / Things to watch for:


⚠ The Pages Router's built-in i18n config (in [Link]) does NOT work with the App Router — you
must roll your own or use a dedicated library.
🔗 Read more: [Link] i18n docs

Q45. What is the next/navigation vs next/router?

next/router is the Pages-Router-only navigation API (useRouter with .push/.pathname/.query, etc.).


next/navigation is the App-Router-only equivalent, exposing separate hooks — useRouter (imperative
navigation only, no .pathname), usePathname, and useSearchParams — because in Server Components
there's no single 'router object' to read from.
Example:

// Pages Router
import { useRouter } from 'next/router';

// App Router
import { useRouter, usePathname, useSearchParams } from 'next/navigation';

Gotchas / Things to watch for:


⚠ Importing from the wrong package for your router type throws a runtime error — they are not
interchangeable.

Q46. What are the useRouter, usePathname, useSearchParams hooks?

In the App Router: useRouter() returns an object with imperative navigation methods (push, replace,
refresh, back). usePathname() returns the current URL's path as a string. useSearchParams() returns a
read-only URLSearchParams instance for the current query string. All three are Client Component hooks.
Example:

'use client';
import { useRouter, usePathname, useSearchParams } from 'next/navigation';

22
export default function Nav() {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
return <button onClick={() => [Link]('/dashboard')}>Go</button>;
}

Gotchas / Things to watch for:


⚠ useSearchParams() opts a component into client-side dynamic rendering — wrap it in <Suspense> to
avoid deopting the whole page to dynamic rendering.

Q47. How do you persist state across navigation in [Link]?

Because layouts persist across navigations without remounting, state kept in a layout (or a Context
Provider placed inside one) survives moving between its child pages. For state that must survive full
reloads, use URL search params, cookies, or client storage (localStorage) instead of in-memory React
state.
Example:

// app/dashboard/[Link] keeps SidebarProvider's state alive


// across navigation between /dashboard/settings and /dashboard/billing
export default function Layout({ children }) {
return <SidebarProvider>{children}</SidebarProvider>;
}

Gotchas / Things to watch for:


⚠ Navigating to a route outside that layout's subtree unmounts it, losing any in-memory state — persist
to the URL or storage if it needs to survive that.

Q48. What is the Link prefetching behavior?

By default, <Link> prefetches the linked route's code and (for static routes) data as soon as it appears in
the viewport in production. This means clicking most links results in a near-instant navigation since
assets are already downloaded ahead of time. Prefetching can be disabled per-link with
`prefetch={false}`.
Example:

<Link href="/heavy-page" prefetch={false}>Heavy Page</Link>

Gotchas / Things to watch for:


⚠ Prefetching is disabled entirely in next dev — only test perceived navigation speed against a production
build.

23
Q49. How do you handle 404 and 500 errors in [Link]?

404s are handled by a [Link] file (App Router) or a custom pages/[Link] (Pages Router), triggered
automatically for unmatched routes or manually via notFound(). 500-level errors are handled by
[Link] (App Router, a Client Component error boundary) or a custom pages/[Link] (Pages Router).
Example:

// app/[Link]
export default function NotFound() { return <h1>404 - Page Not Found</h1>; }

// app/[Link]
'use client';
export default function Error({ error, reset }) {
return <button onClick={reset}>Something broke — retry</button>;
}

Gotchas / Things to watch for:


⚠ A root-level error affecting the layout itself needs [Link], which must include its own <html>
and <body> tags.

Q50. What is [Link] middleware and how does it run at the edge?

Middleware runs on Vercel's (or your host's) Edge Runtime — a lightweight, V8-isolate-based runtime
distributed globally close to users, rather than a traditional single-region [Link] server. This lets checks
like auth/geolocation/redirects execute with very low latency before a request even reaches your main
rendering logic.
Example:

export function middleware(request) {


const country = [Link]?.country ?? 'US';
return [Link]({ headers: { 'x-country': country } });
}

Gotchas / Things to watch for:


⚠ The Edge Runtime is a subset of [Link] APIs — no direct filesystem or many native npm packages
designed for [Link] will work there.

Q51. What is the Edge Runtime in [Link]?

The Edge Runtime is a minimal JavaScript runtime (based on V8 isolates, similar to Cloudflare Workers)
that [Link] can use for middleware and select Route Handlers/pages. It boots near-instantly (no cold-
start like traditional serverless) and runs geographically close to the user, but only supports a subset of
Web-standard APIs — no [Link]-specific modules like fs or net.
Example:

24
// app/api/hello/[Link]
export const runtime = 'edge';

export async function GET() {


return new Response('Hello from the edge');
}

Gotchas / Things to watch for:


⚠ Many npm packages (especially ones using Node built-ins) will fail to build/run on the Edge Runtime —
check package compatibility first.
🔗 Read more: Edge Runtime docs

Q52. What is the [Link] Runtime vs Edge Runtime?

The [Link] Runtime is the full, traditional [Link] environment — supports all npm packages, native
modules, and long-running processes, but has (comparatively) slower cold starts on serverless
platforms. The Edge Runtime trades full [Link] compatibility for near-zero cold starts and global
distribution. Choose based on whether your code needs Node-specific APIs/packages (use [Link]) or
needs to run at very low latency globally with simpler logic (use Edge).
Example:

export const runtime = 'nodejs'; // default


// or
export const runtime = 'edge';

Gotchas / Things to watch for:


⚠ Middleware always runs on the Edge Runtime — you cannot switch it to [Link].

Q53. What is Turbopack and how does it compare to Webpack?

Turbopack is [Link]'s Rust-based successor to Webpack, built by the creators of Webpack for much
faster incremental builds and Hot Module Replacement (HMR), especially in large codebases. As of
recent [Link] versions it's stable for `next dev` and increasingly for production builds, offering large
speedups over Webpack while aiming for compatible plugin/config support over time.
Example:

// [Link]
"scripts": { "dev": "next dev --turbo" }

Gotchas / Things to watch for:


⚠ Not every Webpack loader/plugin has a Turbopack equivalent yet — check compatibility before
migrating a Webpack-heavy config.

25
🔗 Read more: Turbopack docs

Q54. How does [Link] optimize images automatically?

next/image resizes images on demand to the exact dimensions/device pixel ratio requested, converts
them to modern formats like WebP/AVIF when the browser supports it, lazy-loads offscreen images, and
serves a blurred placeholder (placeholder='blur') to prevent layout shift — all without you manually
generating multiple image sizes.
Example:

<Image src="/[Link]" alt="Hero" width={1200} height={600} placeholder="blur"


blurDataURL="data:..." />

Gotchas / Things to watch for:


⚠ Optimization happens per-request on first load (then cached) via a built-in image optimization API —
self-hosted deployments need this endpoint enabled or a custom loader configured.

Q55. What is the Vercel Image Optimization API?

On Vercel, next/image requests are automatically routed through Vercel's managed image optimization
infrastructure, which resizes/transcodes images at the CDN edge and caches results globally — you don't
need to run or scale this service yourself when deployed there.
Example:

// Works out of the box on Vercel, no config needed:


<Image src="/[Link]" width={400} height={300} alt="Photo" />

Gotchas / Things to watch for:


⚠ On non-Vercel hosts, you either need a custom `loader` function pointing at your own image service, or
use `output: 'standalone'`/self-hosted image optimization, since Vercel's specific service isn't available
elsewhere.
🔗 Read more: Image Optimization docs

Q56. How do you deploy [Link] outside of Vercel?

[Link] can be self-hosted on any [Link] server (via `next start`), as a static export (`output: 'export'`)
served from any static host/CDN, in a Docker container (with `output: 'standalone'` for a minimal
image), or on other platforms with [Link] adapters/support (Netlify, AWS Amplify, Cloudflare, Railway,
etc.).
Example:

26
// Dockerfile snippet using standalone output
FROM node:20-alpine
COPY .next/standalone ./
COPY .next/static ./.next/static
COPY public ./public
CMD ["node", "[Link]"]

Gotchas / Things to watch for:


⚠ Some App Router features (Image Optimization API, ISR via on-demand ISR) need extra configuration or
a compatible adapter outside of Vercel's managed infrastructure.
🔗 Read more: Self-hosting docs

Q57. What is the output: 'export' option in [Link]?

Setting `output: 'export'` in [Link] produces a fully static HTML/CSS/JS export (an `out/` folder)
with no [Link] server required at all — deployable to any static file host (S3, GitHub Pages, Netlify). It
disables any feature that requires a server at runtime.
Example:

// [Link]
[Link] = { output: 'export' };

Gotchas / Things to watch for:


⚠ Incompatible with Server Actions, Route Handlers with dynamic behavior, Image Optimization API, ISR,
and middleware — anything needing a live server.
🔗 Read more: Static Exports docs

Q58. What is output: 'standalone' mode?

`output: 'standalone'` produces a minimal, self-contained server bundle (only the files actually needed
to run `next start`, with node_modules pruned to just what's used) — ideal for building small Docker
images since you don't need to `npm install` the entire dependency tree in the final image.
Example:

// [Link]
[Link] = { output: 'standalone' };
// produces .next/standalone/[Link]

Gotchas / Things to watch for:


⚠ You must manually copy the public/ and .next/static/ folders into the standalone output — they aren't
included automatically.

27
Q59. How do you implement dark mode in [Link]?

The common approach uses a library like next-themes: it stores the user's theme preference
(light/dark/system) in localStorage, applies a class or data-attribute to <html>, and avoids hydration
mismatch flicker by injecting a small blocking script before React hydrates. Tailwind's `dark:` variant then
styles based on that class.
Example:

// app/[Link]
import { ThemeProvider } from 'next-themes';

export default function RootLayout({ children }) {


return (
<html suppressHydrationWarning>
<body>
<ThemeProvider attribute="class"><body>{children}</body></ThemeProvider>
</body>
</html>
);
}

Gotchas / Things to watch for:


⚠ Forgetting suppressHydrationWarning on <html> causes a hydration mismatch warning since the
theme class is set client-side before/after server render differ.
🔗 Read more: next-themes library

Q60. What is the cookies() and headers() API in App Router?

cookies() and headers(), from next/headers, are server-only functions that let Server Components,
Server Actions, and Route Handlers read (and, for cookies, write) the incoming request's cookies and
headers — replacing the need to read them off `req` like in the Pages Router.
Example:

import { cookies, headers } from 'next/headers';

export default function Page() {


const theme = cookies().get('theme')?.value;
const userAgent = headers().get('user-agent');
return <p>Theme: {theme}</p>;
}

Gotchas / Things to watch for:


⚠ Calling cookies() or headers() in a component automatically opts that route into dynamic rendering,
since the response now depends on request-specific data.
⚠ You can only set/delete cookies from a Server Action or Route Handler, not from a plain Server
Component render.

28
Q61. What is the connection() function in [Link] 15?

connection() (from next/server) is a new [Link] 15 API you can call inside a Server Component to
explicitly opt that component into dynamic rendering — signaling 'this needs a real per-request
connection' — without depending on side-effects of calling cookies() or headers(). It's a more
intentional, self-documenting way to force dynamic behavior.
Example:

import { connection } from 'next/server';

export default async function Page() {


await connection();
const data = await getRealtimeData();
return <p>{data}</p>;
}

Gotchas / Things to watch for:


⚠ New/less commonly known API — many teams still use the older 'force-dynamic' export or reading
cookies()/headers() for the same effect.
🔗 Read more: [Link] 15 release notes

Q62. What are React cache() and use() in [Link] context?

React's cache() memoizes the result of a function (like a data fetch) per-request, so calling the same
function from multiple components during one render only executes it once — useful for deduplicating
a DB query fetched in both a layout and a page. React's use() hook lets a component read a Promise or
Context conditionally, including inside Server Components, unlocking patterns like awaiting a promise
passed down as a prop.
Example:

import { cache } from 'react';

export const getUser = cache(async (id: string) => {


return [Link]({ where: { id } });
});
// Calling getUser('1') in both a layout and page hits the DB only once per request

Gotchas / Things to watch for:


⚠ cache() dedupes only within a single server render pass — it does not persist across separate requests
like the Data Cache does.
🔗 Read more: React cache() docs

Q63. How do you implement search params in App Router?

29
A [Link] (Server Component) automatically receives a `searchParams` prop containing the current
URL's query string as a plain object, letting you read filters/pagination directly server-side without client
JS. For Client Components, use the useSearchParams() hook instead.
Example:

// app/products/[Link]
export default function Products({ searchParams }: { searchParams: { category?: string }
}) {
return <p>Filtering by: {[Link] ?? 'all'}</p>;
}

Gotchas / Things to watch for:


⚠ Reading searchParams in a page automatically opts that route into dynamic rendering, since the
output depends on the query string.

Q64. What is the difference between searchParams prop and useSearchParams hook?

The `searchParams` prop is available only on [Link] Server Components and is provided directly by
[Link] as plain data (no extra JS needed). useSearchParams() is a Client Component hook from
next/navigation that reads the same data reactively in the browser — needed when a Client Component
(e.g., a filter UI updating without full reload) needs access to query params.
Example:

// Server Component: prop


export default function Page({ searchParams }) { ... }

// Client Component: hook


'use client';
import { useSearchParams } from 'next/navigation';
function Filters() {
const params = useSearchParams();
return <p>{[Link]('category')}</p>;
}

Gotchas / Things to watch for:


⚠ useSearchParams() should be wrapped in <Suspense> at the call site to avoid forcing the entire route to
render dynamically on the client.

Q65. How do you handle form submissions with Server Actions?

A form's `action` prop can point directly to a Server Action function — no onSubmit handler, no manual
fetch/JSON serialization needed. [Link] automatically serializes the FormData and invokes the server
function, which can then mutate data, redirect, or return a result, with progressive enhancement (it
works even before JS hydrates).
Example:

30
// [Link]
'use server';
export async function subscribe(formData: FormData) {
const email = [Link]('email');
await [Link]({ data: { email } });
}

// [Link]
<form action={subscribe}>
<input name="email" type="email" />
<button type="submit">Subscribe</button>
</form>

Gotchas / Things to watch for:


⚠ Because it's progressively enhanced, the form still works if JavaScript fails to load — a big
accessibility/resilience win over purely client-side form handling.

Q66. What is useFormState and useFormStatus?

useFormState (React) lets a Server Action return state (like validation errors) back to the calling Client
Component, re-rendering with that result after submission. useFormStatus reads the
pending/submitting status of the nearest parent <form>, letting you disable a submit button or show a
spinner without manual state wiring. (Note: useFormState was renamed useActionState in newer React
versions.)
Example:

'use client';
import { useFormState, useFormStatus } from 'react-dom';
import { subscribe } from './actions';

function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? 'Submitting…' : 'Subscribe'}</button>;
}

export default function Form() {


const [state, formAction] = useFormState(subscribe, { error: null });
return <form action={formAction}><input name="email" /><SubmitButton /></form>;
}

Gotchas / Things to watch for:


⚠ useFormStatus must be called from a component nested INSIDE the <form>, not the same component
that renders the <form> tag itself.
🔗 Read more: React useFormStatus docs

Q67. What is the optimistic UI pattern with useOptimistic?

31
useOptimistic (React) lets you immediately render an expected/optimistic result of an action (e.g., a new
chat message appearing instantly) while the real Server Action is still in flight, then automatically
reconciles with the real server response once it resolves — making UI feel instant despite network
latency.
Example:

'use client';
import { useOptimistic } from 'react';

function MessageList({ messages, sendMessage }) {


const [optimisticMessages, addOptimistic] = useOptimistic(messages, (state, newMsg) =>
[...state, newMsg]);
async function formAction(formData) {
addOptimistic({ text: [Link]('text'), sending: true });
await sendMessage(formData);
}
return <form action={formAction}>{/* render optimisticMessages */}</form>;
}

Gotchas / Things to watch for:


⚠ If the Server Action fails, you need explicit error handling/rollback logic — useOptimistic doesn't
automatically undo the optimistic update on failure.
🔗 Read more: React useOptimistic docs

Q68. How do you implement file uploads in [Link]?

File uploads can be handled via a Server Action or Route Handler reading multipart FormData directly
([Link]/Web APIs support this natively), then streaming/saving the file to disk, a database, or (more
commonly in production) directly to object storage like S3/Cloudinary/UploadThing using a signed URL
to avoid routing large files through your own server.
Example:

'use server';
export async function uploadFile(formData: FormData) {
const file = [Link]('file') as File;
const buffer = [Link](await [Link]());
await [Link]({ Bucket: 'my-bucket', Key: [Link], Body: buffer });
}

Gotchas / Things to watch for:


⚠ Server Actions have a default body size limit (1MB) — increase it via the [Link]
config for larger uploads, or upload directly to storage client-side with a signed URL.

Q69. What is the [Link] [Link] file?

32
[Link] is a special file at the project root with a `register()` function that runs once when the
server starts (before any request), used to initialize monitoring/observability tools (e.g., Sentry,
OpenTelemetry) so instrumentation is set up before the app begins handling traffic.
Example:

// [Link]
export async function register() {
if ([Link].NEXT_RUNTIME === 'nodejs') {
await import('./[Link]');
}
}

Gotchas / Things to watch for:


⚠ Must be enabled explicitly in older [Link] versions via [Link]; stable by
default from [Link] 15 onward.
🔗 Read more: [Link] docs

Q70. What is OpenTelemetry support in [Link]?

[Link] has built-in support for OpenTelemetry tracing, automatically instrumenting things like Server
Component renders, fetch() calls, and Route Handler executions, letting you export traces to backends
like Datadog, Honeycomb, or Jaeger to debug performance issues across the request lifecycle.
Example:

// [Link]
import { registerOTel } from '@vercel/otel';
export function register() {
registerOTel({ serviceName: 'my-next-app' });
}

Gotchas / Things to watch for:


⚠ Requires the @vercel/otel package (or manual OpenTelemetry SDK setup) and is still marked
experimental in some [Link] versions.
🔗 Read more: OpenTelemetry docs

Q71. How do you configure Content Security Policy in [Link]?

A Content Security Policy (CSP) is typically set via a custom header returned from middleware or
[Link]'s headers() function, restricting which sources scripts/styles/images can load from to
mitigate XSS attacks. For inline scripts (like next/script), a per-request nonce is generated and threaded
through middleware into the page.
Example:

33
// [Link]
export function middleware(request) {
const nonce = [Link]([Link]()).toString('base64');
const csp = `script-src 'self' 'nonce-${nonce}';`;
const response = [Link]();
[Link]('Content-Security-Policy', csp);
return response;
}

Gotchas / Things to watch for:


⚠ A strict CSP can silently break third-party scripts/styles that aren't allow-listed — test thoroughly in a
report-only mode first.
🔗 Read more: CSP docs

Q72. How does [Link] handle TypeScript configuration?

[Link] has built-in, zero-config TypeScript support — running `next dev` or `next build` in a project with
a .ts/.tsx file automatically creates a [Link] with recommended settings and installs the needed
type packages if missing. It also generates [Link] for framework-specific ambient types.
Example:

// Just add a .tsx file and run next dev — [Link] scaffolds [Link] automatically

Gotchas / Things to watch for:


⚠ [Link] is auto-generated and should not be manually edited or removed from .gitignore
exclusions handling.
🔗 Read more: TypeScript docs

Q73. What is the tsconfig paths alias in [Link]?

TypeScript path aliases, configured under `[Link]` in [Link], let you import
modules using short, absolute-style paths (e.g., @/components/Button) instead of long relative paths
(../../../components/Button), and [Link]'s bundler respects these automatically.
Example:

// [Link]
{
"compilerOptions": {
"baseUrl": ".",
"paths": { "@/*": ["./src/*"] }
}
}

// usage
import Button from '@/components/Button';

34
Gotchas / Things to watch for:
⚠ Path aliases are a compile-time/bundler feature only — plain [Link] scripts run outside [Link]'s
bundler (e.g., a standalone seed script) won't resolve them without extra tooling like tsconfig-paths.

Q74. How do you use CSS Modules in [Link]?

Any file named *.[Link] is automatically treated as a CSS Module — class names are scoped locally
(hashed) at build time to avoid global collisions, and [Link] supports this with zero configuration,
importing the file as an object of class names.
Example:

/* [Link] */
.button { background: blue; color: white; }

// [Link]
import styles from './[Link]';
export default function Button() {
return <button className={[Link]}>Click</button>;
}

Gotchas / Things to watch for:


⚠ CSS Modules only work with the .[Link] (or .[Link]) naming convention — a plain .css import
is treated as global CSS.

Q75. How do you use Tailwind CSS in [Link]?

Tailwind integrates via a standard PostCSS setup: install tailwindcss and its peer dependencies, generate
a config file, point its `content` array at your app/components folders so unused classes are purged, and
import Tailwind's base/components/utilities layers once in your global CSS file.
Example:

// [Link]
[Link] = {
content: ['./app/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
};

/* app/[Link] */
@tailwind base;
@tailwind components;
@tailwind utilities;

Gotchas / Things to watch for:


⚠ If the `content` glob doesn't cover every folder using Tailwind classes, those classes get purged from the
production build and silently stop working.

35
🔗 Read more: [Link] + Tailwind guide

Q76. What is the global CSS file in [Link]?

A global stylesheet (conventionally app/[Link]) is imported once — typically in the root [Link] —
and applies unscoped styles across the entire application, unlike CSS Modules which are scoped per-
component. It's the right place for resets, CSS variables, and Tailwind's base layers.
Example:

// app/[Link]
import './[Link]';

export default function RootLayout({ children }) {


return <html><body>{children}</body></html>;
}

Gotchas / Things to watch for:


⚠ Global CSS can only be imported from the root layout (or a component imported into it) in the App
Router — importing it inside a nested page throws a build error.

Q77. How do you use Sass in [Link]?

[Link] has built-in Sass support — just install the `sass` package and use .scss/.sass file extensions
(including .[Link] for scoped modules); no additional Webpack configuration is required.
Example:

npm install sass

/* [Link] */
.button {
background: blue;
&:hover { background: darkblue; }
}

Gotchas / Things to watch for:


⚠ Only the sass package needs installing — [Link] compiles it internally, you don't need node-sass or a
manual Webpack rule.
🔗 Read more: Sass support docs

Q78. What is the React Server Component payload?

The RSC payload is a special serialized format (not plain HTML/JSON) that [Link] streams from server to
client, describing the rendered tree of Server Components plus references to where Client Components

36
need to be hydrated. It's what powers navigation in the App Router — subsequent client-side
navigations fetch a new RSC payload instead of a full HTML document.
Example:

// You don't write this manually — it's produced internally when


// a Server Component tree is rendered and sent to the client router.

Gotchas / Things to watch for:


⚠ You can see the raw RSC payload in DevTools' Network tab as requests with a special Next-Router-
State-Tree/RSC header — useful for debugging what's actually being streamed.
🔗 Read more: React Server Components docs

Q79. How does the App Router handle data mutations?

Mutations in the App Router are typically done through Server Actions (form actions or direct calls from
event handlers) rather than manually POSTing to an API route and refetching. After mutating, you call
revalidatePath/revalidateTag (or return data directly) so [Link] refreshes the relevant cached UI
automatically.
Example:

'use server';
export async function deletePost(id: string) {
await [Link]({ where: { id } });
revalidatePath('/posts');
}

Gotchas / Things to watch for:


⚠ Forgetting to revalidate after a mutation is a very common bug — the UI will keep showing stale
cached data until the next natural revalidation window.

Q80. What is the difference between push() and replace() in useRouter?

[Link](url) navigates to a new URL and adds a new entry to the browser's history stack (so the back
button returns to the previous page). [Link](url) navigates but replaces the current history entry
instead of adding a new one, so the back button skips over it — useful after actions like login redirects
where you don't want 'back' to return to the login form.
Example:

'use client';
import { useRouter } from 'next/navigation';

function LoginForm() {
const router = useRouter();
async function onSubmit() {

37
await login();
[Link]('/dashboard'); // back button won't return to /login
}
}

Q81. What is next/dynamic?

next/dynamic lets you lazily import a component so its code is split into a separate chunk and only
loaded when needed (e.g., on interaction, or below the fold) rather than in the initial bundle — reducing
initial JavaScript payload size. It also supports disabling server-side rendering for a component via `ssr:
false`.
Example:

import dynamic from 'next/dynamic';

const HeavyChart = dynamic(() => import('../components/HeavyChart'), {


loading: () => <p>Loading chart…</p>,
ssr: false,
});

Gotchas / Things to watch for:


⚠ ssr: false can only be used in a Client Component in the App Router — Server Components can't opt out
of SSR for a child this way.
🔗 Read more: next/dynamic docs

Q82. When would you use dynamic() over [Link]()?

next/dynamic is [Link]-aware: it supports SSR (rendering the component's fallback or real output on the
server, unlike [Link] which only works with Suspense on the client), integrates with [Link]'s build-
time code splitting, and offers the `ssr: false` escape hatch for browser-only components (e.g., ones
using `window`). Use [Link]() only for simple client-only lazy loading where SSR/Next-specific
integration isn't a concern.
Example:

// next/dynamic: works with SSR


const Chart = dynamic(() => import('./Chart'));

// [Link]: client-only, must be wrapped in <Suspense> yourself


const Chart2 = [Link](() => import('./Chart'));

Gotchas / Things to watch for:


⚠ Using [Link]() directly in a Server Component tree without a Suspense boundary can throw —
next/dynamic handles this more gracefully in [Link] apps.

38
Q83. How do you implement a loading skeleton in [Link]?

The idiomatic way is a [Link] file per route segment (automatically wrapped in Suspense by [Link])
rendering skeleton placeholder UI matching the real content's layout, so users see structure
immediately while data streams in — reducing perceived load time and layout shift.
Example:

// app/products/[Link]
export default function Loading() {
return (
<div className="animate-pulse space-y-2">
<div className="h-6 bg-gray-200 rounded w-1/3" />
<div className="h-6 bg-gray-200 rounded w-2/3" />
</div>
);
}

Gotchas / Things to watch for:


⚠ For finer-grained skeletons (only part of a page, not the whole route), wrap specific components in your
own <Suspense fallback={...}> instead of relying solely on [Link].

Q84. What is the [Link] headers() function?

The async headers() function in [Link] lets you attach custom HTTP response headers (security
headers like X-Frame-Options, caching headers, CORS headers) to matched routes, applied at
build/serve time without needing middleware for simple, static header rules.
Example:

// [Link]
[Link] = {
async headers() {
return [
{
source: '/:path*',
headers: [{ key: 'X-Frame-Options', value: 'DENY' }],
},
];
},
};

Gotchas / Things to watch for:


⚠ Static headers() rules apply at build time to matched paths; for header logic that depends on the
request (like reading a cookie), use middleware instead.
🔗 Read more: headers() config docs

Q85. What is Multi-Zone architecture in [Link]?

39
Multi-Zones let you compose several independent [Link] applications, each deployed and built
separately, so they appear as a single app to the end user under one domain (e.g., /marketing served by
one app, /app served by another) — useful for large orgs where different teams own different sections
and want independent deploys.
Example:

// [Link] in the "shell" app


[Link] = {
async rewrites() {
return [{ source: '/blog/:path*', destination: '[Link]
}];
},
};

Gotchas / Things to watch for:


⚠ Each zone is a fully separate [Link] build/deploy — shared UI (like a header) must be duplicated or
published as a shared package across zones, since they don't share a single build.
🔗 Read more: Multi-Zones docs

Q86. What is the _app.js equivalent in App Router?

In the Pages Router, _app.js wraps every page with shared providers/layout and persists state across
page changes. In the App Router, this role is filled by the root app/[Link] (required for every project)
combined with any nested [Link] files for section-specific wrapping — there's no single _app.js file
anymore.
Example:

// Pages Router: pages/_app.js


export default function MyApp({ Component, pageProps }) {
return <Layout><Component {...pageProps} /></Layout>;
}

// App Router: app/[Link]


export default function RootLayout({ children }) {
return <html><body><Layout>{children}</Layout></body></html>;
}

Gotchas / Things to watch for:


⚠ _app.js also traditionally handled global CSS imports and error boundaries — in App Router these are
split across [Link], [Link] imports, and [Link] respectively.

Q87. What is a root layout in [Link]?

40
The root layout (app/[Link]) is the mandatory, top-level layout wrapping every route in an App
Router project. It must define the <html> and <body> tags exactly once (they can't be repeated in
nested layouts), and is the ideal place for global providers, fonts, and metadata defaults.
Example:

// app/[Link]
export const metadata = { title: 'My App' };

export default function RootLayout({ children }: { children: [Link] }) {


return (
<html lang="en">
<body>{children}</body>
</html>
);
}

Gotchas / Things to watch for:


⚠ Every App Router project needs exactly one root layout — omitting it is a build error.

Q88. What is a nested layout and how does it improve performance?

Nested layouts are [Link] files placed in subfolders, wrapping only that segment and its children
(e.g., app/dashboard/[Link] wraps everything under /dashboard). Because layouts persist and don't
remount on navigation between their own children, shared UI (sidebars, tab bars) avoids re-rendering
and re-fetching on every navigation, only the changing `page` content re-renders.
Example:

app/
[Link] // wraps entire app
dashboard/
[Link] // wraps everything under /dashboard
[Link]
settings/[Link]

Gotchas / Things to watch for:


⚠ Because a layout persists, any data it fetches is only fetched once per session in that segment, not on
every child navigation — great for performance, but be mindful it won't automatically refresh without
explicit revalidation.

Q89. How do you measure Core Web Vitals in [Link]?

[Link] provides a built-in useReportWebVitals hook (App Router) or the reportWebVitals function
export (Pages Router) that fires with metrics like LCP, CLS, FID/INP, and TTFB, which you can send to an
analytics endpoint of your choice (Vercel Analytics, Google Analytics, or a custom logger).
Example:

41
// app/_components/[Link]
'use client';
import { useReportWebVitals } from 'next/web-vitals';

export function WebVitals() {


useReportWebVitals((metric) => {
[Link](metric); // send to analytics
});
return null;
}

Gotchas / Things to watch for:


⚠ Lab data from a local build can differ significantly from real-user field data — pair this with real
analytics for accurate Core Web Vitals scoring (which affects SEO).
🔗 Read more: useReportWebVitals docs

Q90. What is next/analytics (Vercel Analytics)?

@vercel/analytics is a lightweight package/component you add to your root layout that automatically
tracks Core Web Vitals and page views for apps deployed on Vercel, surfacing them in the Vercel
dashboard without any custom backend, code for aggregation, or third-party analytics service required.
Example:

// app/[Link]
import { Analytics } from '@vercel/analytics/react';

export default function RootLayout({ children }) {


return (
<html><body>{children}<Analytics /></body></html>
);
}

Gotchas / Things to watch for:


⚠ It's a Vercel-specific product (separate from the generic useReportWebVitals hook) — using it fully
requires deploying on Vercel to see the dashboard data.
🔗 Read more: Vercel Analytics docs

Q91. What changed in [Link] 14 vs 13?

[Link] 14 focused on stabilizing and speeding up what 13 introduced: Turbopack for `next dev` became
significantly faster and more stable, Server Actions moved from experimental to stable, partial
prerendering was previewed, and there were major performance improvements to local dev server
startup and Fast Refresh. It was more a refinement/performance release than a feature-packed one.
Example:

42
// [Link]
"next": "^14.0.0"

Gotchas / Things to watch for:


⚠ Always check the official upgrade guide before bumping major versions — caching default behaviors
and experimental flags have shifted across 13/14/15.
🔗 Read more: [Link] 14 release notes

Q92. What is new in [Link] 15?

[Link] 15 stabilized React 19 support, changed caching defaults to be less aggressive by default for
fetch requests and GET Route Handlers (opt-in caching rather than opt-out), stabilized the
[Link] hook, introduced the connection() API, added new forbidden()/unauthorized()
helpers, and improved Turbopack's production readiness.
Example:

// [Link]
"next": "^15.0.0",
"react": "^19.0.0"

Gotchas / Things to watch for:


⚠ The caching default change (fetch no longer cached by default in many cases) is a breaking behavioral
change worth re-testing carefully when upgrading existing apps.
🔗 Read more: [Link] 15 release notes

Q93. What is the use of generateStaticParams?

generateStaticParams is the App Router's replacement for getStaticPaths — an async function exported
from a dynamic route segment that returns an array of param objects, telling [Link] which versions of
that route to statically pre-render at build time.
Example:

// app/blog/[slug]/[Link]
export async function generateStaticParams() {
const posts = await getAllPosts();
return [Link]((post) => ({ slug: [Link] }));
}

export default async function Post({ params }) {


const post = await getPost([Link]);
return <article>{[Link]}</article>;
}

43
Gotchas / Things to watch for:
⚠ Any param values not returned by generateStaticParams are rendered on-demand at request time by
default (equivalent to fallback: 'blocking'), unless dynamicParams = false is set to 404 them instead.
🔗 Read more: generateStaticParams docs

Q94. How do you cache database queries in [Link]?

Since raw DB/ORM calls aren't automatically cached like fetch() is, wrap them in React's cache() (per-
request deduplication) and/or [Link]'s unstable_cache (cross-request, time/tag-based caching) to avoid
hitting the database on every render or every request for data that doesn't need to be fully fresh.
Example:

import { unstable_cache } from 'next/cache';


import { cache } from 'react';

// Per-request dedupe
export const getUser = cache((id) => [Link]({ where: { id } }));

// Cross-request cache with revalidation


export const getPopularPosts = unstable_cache(
() => [Link]({ orderBy: { views: 'desc' }, take: 10 }),
['popular-posts'],
{ revalidate: 3600 }
);

Gotchas / Things to watch for:


⚠ Caching database results means you must remember to revalidateTag/revalidatePath after any write
that changes that data, or users will see stale results.

Q95. What is the draftMode() API in [Link]?

draftMode() (from next/headers) lets you enable a special preview mode — typically triggered from a
CMS's 'preview' button via a Route Handler — that bypasses static caching for that visitor's session, so
editors can see unpublished/draft content rendered dynamically before it goes live, without affecting
regular visitors.
Example:

// app/api/draft/[Link]
import { draftMode } from 'next/headers';

export async function GET() {


draftMode().enable();
return new Response('Draft mode enabled');
}

// In a page:

44
import { draftMode } from 'next/headers';
export default async function Page() {
const { isEnabled } = draftMode();
const post = await getPost({ preview: isEnabled });
}

Gotchas / Things to watch for:


⚠ Draft mode is stored in a cookie scoped to that browser session — it doesn't affect what other visitors
see.
🔗 Read more: Draft Mode docs

Q96. How do you implement rate limiting in [Link] middleware?

Rate limiting in middleware typically uses an external store (since the Edge Runtime is
stateless/distributed) like Upstash Redis or Vercel KV to track request counts per IP/user key with a
sliding window or token bucket algorithm, returning a 429 response when a limit is exceeded.
Example:

import { Ratelimit } from '@upstash/ratelimit';


import { Redis } from '@upstash/redis';

const ratelimit = new Ratelimit({ redis: [Link](), limiter:


[Link](10, '10 s') });

export async function middleware(request) {


const ip = [Link] ?? '[Link]';
const { success } = await [Link](ip);
if (!success) return new Response('Too Many Requests', { status: 429 });
return [Link]();
}

Gotchas / Things to watch for:


⚠ In-memory counters won't work reliably in middleware since Edge functions are distributed across
many regions/instances — always use a shared external store.
🔗 Read more: Upstash Ratelimit docs

Q97. What is the forbidden() and unauthorized() response in [Link] 15?

forbidden() and unauthorized() (new experimental APIs in [Link] 15, from next/navigation) let Server
Components/Actions immediately render a 403 or 401 response using dedicated
[Link]/[Link] boundary files — similar to how notFound() works with [Link] —
giving standardized auth-error UI without manually throwing/catching errors.
Example:

45
import { forbidden, unauthorized } from 'next/navigation';

export default async function AdminPage() {


const session = await getSession();
if (!session) unauthorized();
if (![Link]) forbidden();
return <AdminDashboard />;
}

Gotchas / Things to watch for:


⚠ Marked experimental at release — requires enabling the authInterrupts experimental flag in
[Link] as of the [Link] 15 introduction.
🔗 Read more: [Link] 15 release notes

Q98. How do you test [Link] applications?

Testing typically spans multiple layers: Jest or Vitest with React Testing Library for unit/component tests,
Playwright or Cypress for end-to-end browser tests, and [Link] provides official templates/guides
(create-next-app --example with-jest, etc.) to wire up config (handling the App Router's Server
Components, path aliases, and CSS Modules in test transforms).
Example:

// Example Jest + RTL component test


import { render, screen } from '@testing-library/react';
import Button from './Button';

test('renders button text', () => {


render(<Button>Click me</Button>);
expect([Link]('Click me')).toBeInTheDocument();
});

Gotchas / Things to watch for:


⚠ Server Components (async functions) can't be directly unit-tested with React Testing Library the same
way Client Components can — they're better covered by E2E tests or by testing the underlying data
functions separately.
🔗 Read more: [Link] Testing docs

Q99. What is Playwright vs Cypress for [Link] E2E tests?

Playwright (by Microsoft) supports multiple browser engines (Chromium, Firefox, WebKit) in one API,
runs tests in true parallel across workers, and has strong built-in support for auto-waiting and network
interception. Cypress has a more mature time-travel debugging UI and huge community/plugin
ecosystem, but historically ran only in Chromium-based/Firefox browsers and had weaker true-
parallelism without a paid dashboard. Both are commonly used with [Link]; Playwright is increasingly
favored for cross-browser coverage and CI speed.

46
Example:

// Playwright example
import { test, expect } from '@playwright/test';

test('homepage has title', async ({ page }) => {


await [Link]('/');
await expect(page).toHaveTitle(/My App/);
});

Gotchas / Things to watch for:


⚠ Neither tool is objectively 'better' in all cases — the choice often comes down to team familiarity, CI
parallelism needs, and whether true multi-browser (including Safari/WebKit) coverage matters.
🔗 Read more: Playwright docs
🔗 Read more: Cypress docs

47

You might also like