NextJS Interview Questions Guide
NextJS Interview Questions Guide
JS
INTERVIEW QUESTIONS
A Complete Guide with Explanations, Examples & Gotchas
99 CURATED QUESTIONS
[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:
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>; }
2
return <p>{time}</p>;
}
[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
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
}
3
⚠ Runs only on the server/build machine, never in the browser, so it's safe to use secrets and direct DB
calls.
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:
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:
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 };
}
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:
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';
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') } });
}
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>;
}
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:
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>;
}
[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>
);
}
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();
[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>;
}
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] };
}
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:
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:
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.
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:
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:
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
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:
[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;
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:
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);
}
13
return [Link](user, { status: 201 });
}
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';
[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:
14
import { cookies } from 'next/headers';
[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 };
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:
15
export const dynamic = 'force-dynamic';
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:
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';
[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:
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:
17
// Later, e.g. inside a Server Action after a mutation:
import { revalidateTag } from 'next/cache';
revalidateTag('posts');
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';
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:
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.
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] }];
}
};
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>;
}
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>;
}
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>;
}
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}
</>
);
}
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
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
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]
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]
// Pages Router
import { useRouter } from 'next/router';
// App Router
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
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>;
}
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:
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:
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>;
}
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:
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';
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:
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" }
25
🔗 Read more: Turbopack docs
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:
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:
[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]"]
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' };
`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]
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';
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:
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:
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:
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>;
}
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:
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>
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>;
}
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';
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 });
}
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]');
}
}
[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' });
}
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;
}
[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
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.
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>;
}
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;
35
🔗 Read more: [Link] + Tailwind guide
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]';
[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:
/* [Link] */
.button {
background: blue;
&:hover { background: darkblue; }
}
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:
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');
}
[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
}
}
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:
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:
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>
);
}
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' }],
},
];
},
};
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:
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:
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' };
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]
[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';
@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';
[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"
[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"
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] }));
}
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
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:
// Per-request dedupe
export const getUser = cache((id) => [Link]({ where: { id } }));
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';
// In a page:
44
import { draftMode } from 'next/headers';
export default async function Page() {
const { isEnabled } = draftMode();
const post = await getPost({ preview: isEnabled });
}
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:
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';
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:
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';
47