Top 45 Next.js Interview Questions & Answers (2026)
About Next.js
Top 100 Next.js interview questions covering App Router, Server Components, SSR, SSG, ISR, API routes, data fetching, middleware, performance optimization, and deployment. Companies hiring for Next.js roles test this knowledge at every stage — from first-round screens that check the basics to final-round conversations about real-world trade-offs.
What to Expect in a Next.js Interview
Expect a mix of conceptual and practical Next.js questions: clear definitions and core-concept checks for junior roles, hands-on scenario questions for mid-level roles, and architecture or trade-off discussions for senior roles. Interviewers usually move from foundational topics toward the kind of problems you'd actually face on the job.
How to Use This Guide
Work through the Next.js questions in order — Beginner, then Intermediate, then Advanced — so each concept builds on the last. Every question has its own page; bookmark the ones that trip you up and revisit them the day before your interview.
Curated by Tech Baithak Editorial Team · Last updated: May 2026
Core concepts every Next.js developer must know.
01
What is Next.js?
Next.js is an open-source React meta-framework created by Vercel (formerly Zeit) that adds server-side capabilities, file-based routing, and powerful build optimizations on top of React. It enables building full-stack web applications with React. Key features: (1) Multiple rendering strategies: Server-Side Rendering (SSR), Static Site Generation (SSG), Incremental Static Regeneration (ISR), and Client-Side Rendering (CSR) — per page or even per component; (2) File-based routing: the directory structure automatically defines routes — no manual router configuration; (3) Server Components: React Server Components allow rendering on the server with zero JavaScript sent to the client; (4) API Routes: create backend API endpoints within the Next.js project; (5) Automatic code splitting: each page loads only the JavaScript it needs; (6) Image, Font, and Script optimization; (7) TypeScript and Tailwind CSS: built-in support out of the box. Next.js is the most popular React framework, used by Vercel, TikTok, Hulu, Twitch, Nike, and thousands of other companies. Latest major version: Next.js 14/15 with the App Router as the stable primary routing approach.
02
What is the difference between the Pages Router and App Router in Next.js?
Next.js has two routing systems: Pages Router (Next.js <13, legacy): files in the pages/ directory become routes. pages/index.js → /; pages/about.js → /about; pages/users/[id].js → /users/123. Features: getServerSideProps (SSR), getStaticProps (SSG), getStaticPaths (static paths for dynamic routes), API routes in pages/api/. All components are React Client Components by default. App Router (Next.js 13+, recommended): files in the app/ directory. Each folder represents a route segment. Special files: page.tsx (route UI), layout.tsx (shared layout), loading.tsx (loading UI), error.tsx (error UI), not-found.tsx. All components are React Server Components by default (zero client-side JS). Uses React 18 features: Server Components, Streaming, Suspense. Data fetching happens directly in components with async/await. More powerful and flexible. Key differences: App Router supports Server Components (Pages Router doesn't); App Router uses layouts (Pages Router uses _app.js); App Router fetches data differently (async components vs getServerSideProps); App Router supports React 18 Suspense streaming. Migrate from Pages Router to App Router progressively — they can coexist.
03
What is file-based routing in Next.js?
Next.js uses the filesystem as the router — the directory structure automatically defines application routes. App Router routing (app/ directory): each folder represents a route segment. The URL path corresponds to nested folder names. A page.tsx file makes the route publicly accessible. Examples: app/page.tsx → / (home); app/about/page.tsx → /about; app/blog/[slug]/page.tsx → /blog/my-first-post (dynamic segment); app/shop/[...category]/page.tsx → /shop/electronics/phones (catch-all segment); app/shop/[[...category]]/page.tsx → /shop AND /shop/electronics (optional catch-all). Route groups: app/(marketing)/about/page.tsx — parentheses group routes without adding to URL. Used for shared layouts within a group. Parallel routes: app/@modal/(.)photo/[id]/page.tsx — render multiple pages simultaneously. Intercepting routes: (.)path, (..)path, (...)path — intercept routes without full page navigation. Private folders: _components/ — underscore prefix opts out of routing. Pages Router: pages/ directory with .js/.tsx files directly. Dynamic: pages/posts/[id].tsx.
04
What are React Server Components (RSC)?
React Server Components (RSC) are components that render exclusively on the server and send HTML to the browser — they never include JavaScript in the client bundle. This is a React 18 feature, and Next.js App Router uses them by default. Key properties: (1) Zero client JavaScript: Server Components don't add to the JavaScript bundle — they render to HTML on the server; (2) Direct backend access: can access databases, filesystems, and secret environment variables directly — no need for API routes; (3) No interactivity: cannot use useState, useEffect, event handlers, browser APIs, or any hooks — they are pure server-rendering; (4) Async by default: can use async/await directly: async function UserList() { const users = await db.query("SELECT * FROM users"); return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>; }. Benefits: faster page loads (less JavaScript), better SEO (rendered HTML), keeps secrets on server (no env var leakage), reduced bundle size. Limitations: no event handlers, no browser-only APIs, no React hooks, no class components. Client Components: add "use client" directive at the top. Can use all React features. Can be nested inside Server Components. Composition: Server Components pass data to Client Components via props. Client Components can import other Client Components but can't import Server Components.
05
What is the "use client" directive in Next.js?
The "use client" directive is a string literal placed at the top of a file that marks all components in that module (and everything it imports) as Client Components. Without this directive, components in the App Router are Server Components by default. Usage: "use client"; import { useState } from "react"; export function Counter() { const [count, setCount] = useState(0); return <button onClick={() => setCount(c => c + 1)}>{count}</button>; }. When to use "use client": when the component uses: useState, useEffect, useReducer, or any other hooks; event handlers (onClick, onChange, onSubmit); browser-only APIs (window, localStorage, navigator); third-party libraries that depend on browser APIs; context that uses client state. Important nuances: (1) "use client" marks a boundary in the component tree — it and all its imports become client components; (2) It doesn't mean the component is NOT server-rendered — Client Components are still pre-rendered on the server (SSR) but also have JavaScript shipped to the client for hydration and interactivity; (3) Keep "use client" components as leaf nodes when possible — push them down the tree so more of the tree can remain Server Components. vs Server Components: Server Components = server only, no JS bundle; Client Components = server pre-rendered + client hydrated, JS bundle included. Use Server Components by default; add "use client" only when you need interactivity.
06
What is the layout.tsx file in Next.js App Router?
A layout.tsx (or layout.js) file defines a UI that is shared across routes at its level and below. Unlike pages, layouts preserve state and don't re-render when the user navigates between routes sharing that layout — only the page content changes. Root layout (required): // app/layout.tsx export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body> <Navigation /> {children} <Footer /> </body> </html> ); }. Must contain <html> and <body> tags. Wraps all routes. Nested layouts: app/dashboard/layout.tsx wraps all routes under /dashboard/*. The layout only renders when navigating within its segment. State persistence: since layouts don't unmount during navigation, state (sidebar open/closed, user preferences) persists automatically. Metadata: layouts can export metadata: export const metadata = { title: "My App", description: "..." };. Layout nesting: layouts nest automatically — root layout wraps dashboard layout which wraps the page. All receive the children prop. Template.tsx: similar to layout but re-creates the component on every navigation (state is reset) — use for features that must reset between routes (form state, error boundaries, animation per-page). Passing data to layouts: layouts can fetch their own data. No need to pass from page — layout fetches independently.
07
What is page.tsx in Next.js App Router?
A page.tsx (or page.js) file defines the unique UI for a specific route — it makes the route segment publicly accessible. Without a page.tsx, a folder doesn't create a publicly accessible route (it can still contain layouts, loading states, etc.). Basic page: // app/about/page.tsx export default function AboutPage() { return <h1>About us</h1>; }. Server Component page with data fetching: // app/users/page.tsx export default async function UsersPage() { const users = await fetch("https://api.example.com/users").then(r => r.json()); return ( <main> <h1>Users</h1> {users.map(u => <UserCard key={u.id} user={u} />)} </main> ); }. Dynamic route page: // app/posts/[id]/page.tsx export default async function PostPage({ params }: { params: { id: string } }) { const post = await getPost(params.id); return <article>{post.content}</article>; }. Props a page receives: params — route segment dynamic params; searchParams — URL query string params (only available in page.tsx, not layouts). generateMetadata: export async function generateMetadata({ params }) to generate dynamic metadata for the page. Combining with layouts: the page is rendered inside its ancestor layouts. The layout wraps the page content via the children prop.
08
What is SSR (Server-Side Rendering) in Next.js?
Server-Side Rendering (SSR) generates the HTML for a page on the server for each request. The server fetches data, renders the React component to HTML, and sends it to the browser. The browser then hydrates the HTML with React for interactivity. Pages Router SSR: export getServerSideProps from a page: export async function getServerSideProps(context) { const { params, req, res, query } = context; const data = await fetchData(params.id); if (!data) return { notFound: true }; return { props: { data } }; } export default function Page({ data }) { return <div>{data.title}</div>; }. App Router SSR: by default, App Router fetches data in Server Components (which IS SSR) — just use async/await in the component. Dynamic rendering (per-request) vs static rendering (at build time) is determined by the data fetching options. Force dynamic: export const dynamic = "force-dynamic". Benefits of SSR: always fresh data (fetched per request); can use request-specific data (cookies, headers, user session); good SEO (fully rendered HTML); secure (data fetching on server). Drawbacks: slower than static — every request triggers server rendering; higher server load; Time to First Byte (TTFB) is longer than static files. Use SSR for: personalized pages (user dashboard), real-time data (stock prices), pages where fresh data is critical. Use SSG for: blogs, marketing pages, documentation.
09
What is SSG (Static Site Generation) in Next.js?
Static Site Generation (SSG) pre-renders pages to HTML at build time. The generated HTML is served from a CDN — ultra-fast with no server processing per request. Pages Router SSG: export async function getStaticProps() { const posts = await fetchPosts(); return { props: { posts } }; } export default function Blog({ posts }) { return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>; }. For dynamic routes, export getStaticPaths to tell Next.js which paths to pre-build: export async function getStaticPaths() { const posts = await fetchPosts(); return { paths: posts.map(p => ({ params: { id: p.id } })), fallback: "blocking" }; }. App Router SSG: Server Components that fetch data without dynamic/revalidate options are statically rendered at build time by default. fetch("https://api/posts") in a Server Component = SSG. fallback options: false — 404 for paths not in getStaticPaths; true — shows fallback HTML, then builds on first request; "blocking" — SSR on first request, then cached as static. Benefits: fastest performance (static files, CDN-served), cheapest hosting, best for SEO. Drawbacks: data must be known at build time; rebuilding needed for content updates (mitigated by ISR). Best for: blogs, documentation, marketing pages, any content-driven site where data doesn't change per-user.
10
What is Incremental Static Regeneration (ISR) in Next.js?
Incremental Static Regeneration (ISR) combines SSG and SSR — pages are generated at build time (like SSG) but can be regenerated in the background after a specified revalidation period without rebuilding the entire site. Pages Router ISR: add revalidate to getStaticProps: export async function getStaticProps() { const data = await fetchData(); return { props: { data }, revalidate: 60 // regenerate at most once every 60 seconds }; }. On the first request after 60 seconds, Next.js serves the cached page (stale) and regenerates in the background. The next request gets the fresh page. App Router ISR: add next.revalidate option to fetch: const data = await fetch("https://api.example.com/data", { next: { revalidate: 60 } }).then(r => r.json());. Or use route segment config: export const revalidate = 60; — applies to all fetches in the route. On-demand revalidation: trigger revalidation programmatically: revalidatePath("/posts") or revalidateTag("posts") in a Server Action or API route — useful for CMS webhooks. Benefits: static-file performance + fresh data; no rebuild needed; gradual rollout; cost-effective. stale-while-revalidate pattern: the key mechanism — serve stale content immediately while updating in background. Users always see a valid (possibly slightly stale) response without waiting for regeneration.
11
What are Next.js API routes?
Next.js API routes allow you to create backend API endpoints within the same Next.js project, without a separate server. App Router — Route Handlers: create a route.ts file in the app/ directory: // app/api/users/route.ts import { NextRequest, NextResponse } from "next/server"; export async function GET(request: NextRequest) { const users = await db.getUsers(); return NextResponse.json(users); } export async function POST(request: NextRequest) { const body = await request.json(); const user = await db.createUser(body); return NextResponse.json(user, { status: 201 }); }. Each HTTP method (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS) is exported as a named function. Pages Router API routes: // pages/api/users.ts export default function handler(req, res) { if (req.method === "GET") { res.json(users); } else if (req.method === "POST") { const user = createUser(req.body); res.status(201).json(user); } }. Route Handler features: access request via NextRequest (URL, headers, cookies, body); dynamic routes: app/api/users/[id]/route.ts; Edge runtime option for low-latency: export const runtime = "edge";; streaming responses; CORS handling; cookie management via cookies(). When to use: third-party API integrations, database operations (keep credentials server-side), webhooks from external services, form handling. For simple data needs, Server Actions or direct Server Component fetching is often cleaner.
12
What are Server Actions in Next.js?
Server Actions are async functions that run on the server but can be called from Client Components (and Server Components). They eliminate the need for manual API routes for form submissions and data mutations. Defining a Server Action: add "use server" directive: // In a Server Component or separate file async function createUser(formData: FormData) { "use server"; const name = formData.get("name") as string; await db.users.create({ name }); revalidatePath("/users"); } // Use directly in a form: <form action={createUser}> <input name="name" /> <button type="submit">Create</button> </form>. In a Client Component: "use client"; import { createUser } from "./actions"; export function Form() { return <form action={createUser}>...</form>; }. Calling programmatically: const result = await createUser(data) from a Client Component. Benefits: (1) No manual API route needed; (2) Progressive enhancement — forms work without JavaScript (POST request); (3) Automatic CSRF protection; (4) Co-locate data mutations with components; (5) TypeScript end-to-end type safety. With useFormState and useFormStatus (React hooks): manage form state and pending state in Client Components. Revalidation in Server Actions: call revalidatePath() or revalidateTag() to refresh cached data after mutation. Error handling: use try/catch and return objects; or let errors propagate to the nearest error boundary.
13
What is Next.js data fetching in the App Router?
The App Router introduced a new data fetching model built on Web fetch API with caching capabilities. Three data fetching approaches: (1) fetch() in Server Components (recommended): async function Page() { const data = await fetch("https://api.example.com/data", { cache: "force-cache", // SSG (default), cached indefinitely next: { revalidate: 60 }, // ISR cache: "no-store", // SSR (fresh every request) }).then(r => r.json()); return <div>{data.title}</div>; }; (2) ORM/database queries in Server Components: async function UserList() { const users = await prisma.user.findMany(); return <ul>{users.map(u => <li>{u.name}</li>)}</ul>; }; (3) Client-side data fetching: use SWR or React Query in Client Components for: user-specific data that changes frequently, real-time updates, infinite scroll. Parallel data fetching: const [users, posts] = await Promise.all([getUsers(), getPosts()]);. Sequential fetching (when needed): one fetch depends on another result. Request deduplication: Next.js automatically deduplicates fetch() requests with the same URL and options in a single render pass — even if multiple components request the same URL, only one HTTP request is made. Caching tags: fetch("...", { next: { tags: ["posts"] } }) — tag for targeted revalidation: revalidateTag("posts") revalidates all fetches with this tag.
14
What is Next.js Image optimization?
Next.js provides a built-in <Image> component (next/image) that automatically optimizes images: import Image from "next/image"; <Image src="/photo.jpg" alt="A photo" width={800} height={600} priority />. Automatic optimizations: (1) WebP/AVIF conversion: serves modern formats to browsers that support them (JPEG → WebP reduces size by ~30%); (2) Responsive sizes: automatically generates multiple image sizes and uses srcset for different device widths; (3) Lazy loading: images are lazy-loaded by default (below the fold) — add priority for LCP images (above the fold); (4) Cumulative Layout Shift (CLS) prevention: requires width and height props to reserve space; (5) On-demand optimization: images are optimized on first request and cached at the CDN edge; (6) Placeholder blur: placeholder="blur" blurDataURL="..." — shows blurred placeholder while loading. Remote images: configure allowed domains in next.config.js: images: { remotePatterns: [{ hostname: "images.unsplash.com" }] }. fill: <Image src="..." alt="..." fill className="object-cover" /> — fills parent container (parent must be position: relative). sizes prop: sizes="(max-width: 768px) 100vw, 50vw" — helps browser select the right image size. Custom loader: use a custom function to generate image URLs from a CDN.
15
What is Next.js metadata and SEO?
Next.js App Router provides a Metadata API for setting page metadata (title, description, Open Graph tags, Twitter cards, etc.) in a type-safe way. Static metadata: export a metadata object from layout.tsx or page.tsx: export const metadata = { title: "My Blog", description: "A blog about web development", openGraph: { title: "My Blog", description: "...", images: [{ url: "/og-image.jpg" }] }, twitter: { card: "summary_large_image", creator: "@username" }, robots: { index: true, follow: true }, keywords: ["nextjs", "react", "web"] };. Dynamic metadata: export async function generateMetadata({ params }) { const post = await getPost(params.id); return { title: post.title, description: post.excerpt, openGraph: { images: [post.coverImage] } }; }. Metadata inheritance: child pages inherit metadata from parent layouts and can override specific fields. Title template: export const metadata = { title: { template: "%s | My Site", default: "My Site" } }; — child pages set title: "About" → "About | My Site". Pages Router: use next/head: <Head><title>Page Title</title><meta name="description" content="..." /></Head>. Viewport, robots.txt, sitemap: App Router has built-in file conventions (app/robots.ts, app/sitemap.ts) that export functions generating these files.
16
What is Next.js middleware?
Next.js middleware runs before a request is completed — it intercepts requests and can rewrite, redirect, add headers, or send a response before the request reaches the page or API route. Middleware runs at the Edge (closest server to the user) for minimal latency. Creating middleware: create a middleware.ts file in the project root (or src/): import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; export function middleware(request: NextRequest) { const token = request.cookies.get("auth-token"); if (!token && request.nextUrl.pathname.startsWith("/dashboard")) { return NextResponse.redirect(new URL("/login", request.url)); } const response = NextResponse.next(); response.headers.set("x-custom-header", "custom-value"); return response; } export const config = { matcher: ["/dashboard/:path*", "/api/protected/:path*"] };. Middleware capabilities: read/write cookies (request.cookies, response.cookies); read request headers; redirect (NextResponse.redirect()); rewrite URL (NextResponse.rewrite()); return response early (NextResponse.json()); add/modify response headers. Matcher config: specify which paths the middleware runs on (regex patterns supported). Without matcher, runs on ALL routes (expensive). Edge runtime: middleware runs on the Edge Runtime — faster, global distribution, but limited APIs (no Node.js built-ins like fs). Use cases: authentication/authorization, i18n routing, A/B testing, bot detection, rate limiting, logging, geo-based routing.
17
What is the Next.js loading.tsx file?
The loading.tsx file in the App Router automatically wraps the page in a React Suspense boundary, showing its content as a loading state while the page is being rendered. It enables streaming SSR — the layout is sent immediately while the page content is still being fetched/generated. Creating a loading state: // app/dashboard/loading.tsx export default function Loading() { return <div className="spinner">Loading dashboard...</div>; }. When the user navigates to /dashboard, Next.js immediately shows the loading component while page.tsx is being server-rendered and data is being fetched. Once ready, the page content replaces the loading state. How it works: Next.js generates: <Layout> <Suspense fallback={<Loading />}> <Page /> </Suspense> </Layout>. The layout (including navigation) renders instantly, loading spinner shows, then page content streams in. Granular loading states: instead of page-level loading, use Suspense directly in your component tree for more granular loading states: <Suspense fallback={<Skeleton />}><UserList /></Suspense>. Streaming: each Suspense boundary streams independently — the first parts of the page show immediately, later parts stream in as they're ready. This dramatically improves perceived performance (Time to First Byte of content). Instant loading states: navigation shows loading.tsx immediately, preventing the "stuck" feeling during navigation.
18
What is error.tsx in Next.js App Router?
The error.tsx file defines a React Error Boundary for a route segment — it catches errors thrown during rendering and displays a fallback UI instead of crashing the entire application. Creating an error boundary: "use client"; // error.tsx must be a Client Component import { useEffect } from "react"; export default function Error({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) { useEffect(() => { console.error(error); }, [error]); return ( <div> <h2>Something went wrong!</h2> <p>{error.message}</p> <button onClick={reset}>Try again</button> </div> ); }. Must be a Client Component — error boundaries in React must be class components or use the "use client" directive (Next.js handles the class component part). Props: error — the Error object with a digest (server-side error ID for cross-referencing logs without exposing details to the client); reset — a function to re-attempt rendering the error boundary segment. Error scope: errors are caught by the nearest error.tsx up the route tree. An error in app/dashboard/page.tsx is caught by app/dashboard/error.tsx if it exists, otherwise app/error.tsx. The layout is NOT caught — use global-error.tsx to catch layout errors. not-found.tsx: special error UI for 404s — shown when notFound() is called.
19
What is dynamic routing in Next.js?
Dynamic routes in Next.js use brackets in folder/file names to capture URL segments as parameters. App Router: app/posts/[id]/page.tsx — matches /posts/1, /posts/abc. Access via props: export default function PostPage({ params }: { params: { id: string } }). Types of dynamic segments: (1) [slug] — single dynamic segment: /posts/hello-world; (2) [...slug] — catch-all: /posts/2024/01/hello — params.slug = ["2024", "01", "hello"]; (3) [[...slug]] — optional catch-all: matches /posts AND /posts/hello — params.slug is undefined for the empty case. generateStaticParams (App Router): for SSG with dynamic routes — tells Next.js which paths to pre-render at build time: export async function generateStaticParams() { const posts = await getPosts(); return posts.map(post => ({ id: post.id })); }. Pages Router: pages/posts/[id].tsx; access via useRouter().query.id or getServerSideProps context.params. Dynamic groups: pages/posts/[...slug].tsx. Parallel routes (App Router): app/@modal/(.)photo/[id]/page.tsx — render multiple pages simultaneously. Route interception: (.)path — intercept routes from current level, enabling modal patterns without URL changes.
20
What is the next.config.js file?
The next.config.js (or next.config.ts for TypeScript) file configures Next.js behavior. It's a regular Node.js module that exports a configuration object. Key configuration options: const nextConfig = { // Redirect rules redirects: async () => [{ source: "/old-path", destination: "/new-path", permanent: true }], // URL rewriting rewrites: async () => [{ source: "/api/:path*", destination: "https://backend.com/:path*" }], // Image domains images: { remotePatterns: [{ hostname: "images.unsplash.com" }] }, // Custom headers headers: async () => [{ source: "/(.*)", headers: [{ key: "X-Content-Type-Options", value: "nosniff" }] }], // Environment variables (build-time) env: { NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL }, // i18n internationalization i18n: { locales: ["en", "fr", "es"], defaultLocale: "en" }, // React strict mode reactStrictMode: true, // Experimental features experimental: { serverActions: { allowedOrigins: ["my-proxy.com"] } }, // Webpack customization webpack: (config, { buildId, dev }) => { config.module.rules.push(...); return config; }, // Output modes output: "standalone", // for Docker deployments // Transpile packages transpilePackages: ["@company/ui-lib"] }; export default nextConfig;.
21
What is the difference between client-side navigation and full page reload in Next.js?
Next.js handles navigation differently depending on how it's initiated: Client-side navigation (Link component / router.push): import Link from "next/link"; <Link href="/about">About</Link>. Uses JavaScript to swap out only the changed parts of the page — no full browser reload. The URL changes, only the new page's JavaScript is fetched (code splitting), layouts that are shared are NOT re-rendered (state preserved). React handles the DOM update. Faster, smoother user experience. In App Router: only the changed route segments are re-rendered; shared layouts maintain their state. Full page reload (browser navigation, window.location): browser fetches the entire HTML from the server — all React state is lost, full re-initialization occurs. Much slower. Only appropriate for: logging out (clearing all state), critical security redirects. Prefetching: <Link> automatically prefetches the linked page when it enters the viewport (in production). By the time the user clicks, the JavaScript bundle and data are already loaded — instant navigation. Disable: <Link prefetch={false}>. Programmatic navigation: import { useRouter } from "next/navigation"; // App Router const router = useRouter(); router.push("/about"); router.replace("/login"); router.back(); router.refresh(); // re-renders current route, revalidating Server Components. router.refresh(): unique to App Router — re-renders Server Components with fresh data without a full page reload or losing client state.
22
What is the Next.js Font optimization?
Next.js provides a built-in Font optimization system via next/font that automatically optimizes Google Fonts and custom fonts with zero layout shift and no external network requests. Google Fonts: import { Inter, Roboto_Mono } from "next/font/google"; const inter = Inter({ subsets: ["latin"], variable: "--font-inter", display: "swap" }); const robotoMono = Roboto_Mono({ subsets: ["latin"], weight: ["400", "700"] }); // In root layout: <html className={inter.variable}><body className={robotoMono.className}>. How it works: at build time, Next.js downloads the font files from Google Fonts and self-hosts them alongside your app. The browser never makes a request to Google — privacy benefit + performance (no DNS lookup, no external request). Automatic font declaration with CSS custom properties for CSS variables or direct className. Local fonts: import localFont from "next/font/local"; const myFont = localFont({ src: "./fonts/MyFont.woff2", variable: "--font-my-font" });. Multiple weight files: src: [{ path: "./Regular.woff2", weight: "400" }, { path: "./Bold.woff2", weight: "700" }]. Benefits: (1) No external network requests (no Google tracking); (2) Zero layout shift — fonts are loaded with CSS size-adjust to match fallback fonts; (3) Subset only what you need; (4) Automatic preloading; (5) Works with CSS variables for flexible theming. display: "swap" — shows fallback font immediately while loading the custom font.
23
What is Next.js environment variables?
Next.js has a specific system for managing environment variables across different environments. Files: .env.local — local development, git-ignored (secrets); .env.development — development environment; .env.production — production environment; .env — all environments. Precedence (highest to lowest): .env.local → .env.development/.env.production → .env. Server-only variables: only accessible in Server Components, API routes, middleware, and server-side functions. Never exposed to the browser: DATABASE_URL=postgres://...; process.env.DATABASE_URL. Public (browser-accessible) variables: prefix with NEXT_PUBLIC_ — bundled into client-side JavaScript: NEXT_PUBLIC_API_URL=https://api.example.com; process.env.NEXT_PUBLIC_API_URL in both server and client code. Important security note: never put secrets in NEXT_PUBLIC_ variables — they appear in the JavaScript bundle and are visible to anyone. Type safety with @t3-oss/env-nextjs: validate environment variables at build time with Zod schemas — fail build if required vars are missing. In next.config.js env option: hardcode build-time values in config (not recommended for secrets — use .env instead). Runtime env (edge/serverless): use platform-specific secret management (Vercel Environment Variables, AWS Secrets Manager) for production secrets.
24
What is the Next.js Script component?
Next.js provides a <Script> component (next/script) that controls when and how third-party scripts are loaded, improving performance by avoiding render-blocking scripts. Loading strategies: strategy="beforeInteractive" — loads before the page becomes interactive (blocks). Use for: critical scripts needed before hydration (chat widget that must load immediately). strategy="afterInteractive" (default) — loads after the page becomes interactive. Use for: analytics, tag managers (Google Analytics, GTM). strategy="lazyOnload" — loads during browser idle time after all resources load. Use for: social media widgets, low-priority scripts. strategy="worker" — experimental, loads script in a Web Worker (doesn't block main thread). Usage: import Script from "next/script"; // In layout.tsx or page.tsx: <Script src="https://analytics.example.com/script.js" strategy="afterInteractive" onLoad={() => console.log("Script loaded")} onError={(e) => console.error("Script failed", e)} id="analytics-script" // required for inline scripts />. Inline scripts: <Script id="my-inline-script" strategy="afterInteractive">{`console.log("inline")`}</Script>. Benefits over raw script tags: prevents duplicate script loading across navigation, deduplicates identical scripts, provides loading strategy control, works correctly with Next.js page transitions (script doesn't re-execute on client navigation).
25
What is the difference between useRouter in Pages Router vs App Router?
Next.js has different router hooks for the Pages Router and App Router — they come from different imports. Pages Router useRouter: import { useRouter } from "next/router"; — single hook for all routing needs. Provides: router.push("/path") (navigate), router.replace("/path") (no history), router.back(), router.reload(), router.prefetch("/path"), router.query (route params + query string as one object), router.pathname, router.asPath (actual URL with query), router.isFallback, router.events (navigation events). App Router hooks (from "next/navigation"): import { useRouter, usePathname, useSearchParams, useParams } from "next/navigation";. useRouter: navigation only — router.push("/path"), router.replace(), router.back(), router.forward(), router.refresh() (re-renders Server Components). NO query/params — use separate hooks. usePathname(): current pathname string only. useSearchParams(): URL search params as URLSearchParams object (must be wrapped in Suspense). useParams(): dynamic route params. Why the split? App Router's architecture separates concerns — params, path, and search params are distinct reactive values. Also, useSearchParams needs Suspense because it can cause client-side rendering. redirect() and notFound(): server-side navigation functions imported from "next/navigation" — usable in Server Components and Server Actions.
26
What is the Next.js Link component?
The <Link> component from next/link is Next.js's primary navigation tool. It extends the HTML <a> tag with client-side navigation and automatic prefetching. Basic usage: import Link from "next/link"; <Link href="/about">About</Link> <Link href={{ pathname: "/users/[id]", query: { id: "123" } }}>User 123</Link>. Key features: (1) Client-side navigation: navigates without a full page reload, preserving React state; (2) Automatic prefetching: in production, when a Link is visible in the viewport, Next.js prefetches the destination's code and data. By the time the user clicks, navigation is instant; (3) Active link styling: use usePathname() to check if the link's href matches the current path and conditionally apply active styles; (4) Scroll behavior: by default, scrolls to top on navigation. scroll={false} prevents scroll. Props: href (required — string or object), replace (replace history instead of push), scroll (default true), prefetch (default true in prod, disable with false), legacyBehavior (wrap child <a> tag — for compatibility). App Router change: in Next.js 13+ App Router, Link no longer requires a child <a> tag — it renders <a> directly. All standard anchor attributes (className, onClick, etc.) can be passed directly to Link. Custom components: for UI library buttons as links, use router.push() in onClick instead of Link.
27
What is Next.js Turbopack?
Turbopack is Vercel's Rust-based build tool for JavaScript and TypeScript that is bundled with Next.js as an alternative to Webpack. Introduced in Next.js 13 as alpha, it became the default dev server bundler in Next.js 15. Why Turbopack? Webpack is fast but has limitations — large projects see slow cold starts (10-60 seconds) and HMR (Hot Module Replacement) times of several seconds. Turbopack is designed for significantly faster performance: (1) Rust implementation: Rust is much faster than JavaScript for CPU-intensive build tasks; (2) Incremental computation: Turbopack only recomputes what has changed — caches previous work at the function level; (3) Parallelism: takes full advantage of multi-core CPUs. Benchmarks: Up to 700x faster than Webpack in cold start, 10x faster in HMR for large Next.js apps. Enable in development: next dev --turbopack (or default in Next.js 15). Current status: Turbopack is stable for development (next dev) and working on production builds (next build). Production Turbopack is in beta. Limitations vs Webpack: not all Webpack loaders/plugins are supported in Turbopack (most common ones work); some advanced configurations may not be available. SWC (Speedy Web Compiler): separate from Turbopack — the Rust-based JavaScript/TypeScript compiler that replaced Babel in Next.js 12. SWC is used for code transformation; Turbopack is the bundler.
28
What is Partial Prerendering (PPR) in Next.js?
Partial Prerendering (PPR) is an experimental Next.js 14+ feature that combines static and dynamic rendering in a single request, per page. With PPR, a route's static "shell" is pre-rendered and served from the edge instantly, while dynamic content streams in asynchronously. Problem it solves: traditionally, you choose between static (fast, stale) or dynamic (fresh, slower). PPR enables: serve the static layout/skeleton instantly → stream in the dynamic personalized parts. How it works: the route is analyzed at build time. Static parts (no dynamic functions, no uncached data) are pre-rendered as HTML. Dynamic parts are wrapped in <Suspense> boundaries — their fallbacks are included in the static shell. At request time: static shell is served instantly from edge cache → dynamic parts render on server and stream to client while the user already sees the page. Example: export const experimental_ppr = true; // Enable PPR for this route export default function Page() { return ( <main> <StaticHeader /> {/* pre-rendered */} <Suspense fallback={<Skeleton />}> <DynamicUserData /> {/* streamed */} </Suspense> </main> ); }. Enable globally: experimental: { ppr: true } in next.config.js. PPR requires the App Router and Suspense boundaries.
Practical knowledge for developers with hands-on experience.
01
How does Next.js caching work in the App Router?
Next.js App Router has a multi-layer caching system: 1. Request Memoization (in-memory, per-render): React deduplicates identical fetch() calls made during a single render tree traversal. If two Server Components call fetch("/api/user"), only one HTTP request is made. Duration: single server request, then cleared. 2. Data Cache (persistent, server-side): Next.js extends fetch with a server-side data cache. fetch("...", { cache: "force-cache" }) caches the response across requests and deployments until manually revalidated. { cache: "no-store" } skips cache. { next: { revalidate: 60 } } revalidates every 60 seconds. Cache persisted across requests, shared between users. 3. Full Route Cache (server-side, static): statically rendered routes are cached as HTML+RSC payload on the server at build time (or at first request). Persists across deployments if not revalidated. 4. Router Cache (client-side): the client stores visited route segments in memory. Avoids re-fetching Server Component data when navigating back. Duration: 30 seconds for pages, 5 minutes for layouts (default, configurable). Opting out: export const dynamic = "force-dynamic" — makes route fully dynamic. noStore() from next/cache — opt a data fetch out of cache. Revalidating: revalidatePath("/blog") or revalidateTag("posts") in Server Actions or Route Handlers. cookies() and headers(): using these functions in a Server Component automatically opts it into dynamic rendering (can't be cached since they depend on the request).
02
What are Server Components vs Client Components trade-offs?
Choosing between Server Components (RSC) and Client Components requires understanding the trade-offs: Server Components — Strengths: zero JavaScript sent to client (huge bundle reduction); direct database/file system access without API layer; can use secrets (API keys, DB credentials); automatic code splitting for RSC; better initial page load performance; data fetching collocated with rendering. Server Components — Limitations: no interactivity (no event handlers); no React state (useState, useReducer); no lifecycle hooks (useEffect); no browser APIs (window, localStorage); no context that depends on client state; cannot use class components with client state. Client Components — Strengths: full React interactivity; access to browser APIs; React hooks (useState, useEffect, useRef, etc.); real-time features, subscriptions; immediate UI feedback (no server round-trip). Client Components — Limitations: increase JavaScript bundle; secrets must be handled via API calls; can't directly access database. Decision framework: start Server Component, add "use client" only when you need: interactivity, browser APIs, third-party client libraries. Composition pattern: Server Components can render Client Components (passing server-fetched data as props). Client Components cannot render Server Components (they're already on the client). BUT: Client Components CAN receive Server Components as children via the children prop (the Server Component renders server-side and its HTML is passed through). Minimize "use client" boundaries — push them as far down the tree as possible.
03
What is React Suspense and streaming in Next.js?
Streaming with Suspense allows Next.js to progressively send HTML to the browser as it's rendered, rather than waiting for the entire page to finish before sending anything. Traditional SSR problem: the entire page must be fetched and rendered before ANY HTML is sent. One slow component (user profile fetch) blocks the entire page. Streaming solution: HTML is sent in chunks. The "shell" (layout, non-dynamic content) is sent immediately. Suspended components send their content when ready — users see and can interact with parts of the page before others load. Implementation: wrap async components in Suspense with a fallback: export default function Dashboard() { return ( <main> <Suspense fallback={<HeaderSkeleton />}> <UserHeader /> {/* slow API call */} </Suspense> <Suspense fallback={<MetricsSkeleton />}> <DashboardMetrics /> {/* database query */} </Suspense> </main> ); }. UserHeader and DashboardMetrics can stream in independently — whichever finishes first appears first. loading.tsx automates this — wraps the entire page.tsx in a Suspense boundary. Benefits: (1) First Contentful Paint is dramatically faster; (2) Time to Interactive improved; (3) Slow components don't block fast ones; (4) Better perceived performance. Streaming HTTP: Next.js uses chunked transfer encoding to stream HTML. The HTTP connection stays open, sending chunks as they're ready. TTFB (Time to First Byte): remains fast because the shell HTML is sent immediately.
04
How do you implement authentication in Next.js?
Authentication in Next.js requires careful consideration of the App Router's server/client split. Recommended: NextAuth.js (Auth.js): the most popular authentication library for Next.js with built-in providers (Google, GitHub, credentials, etc.). Setup: // app/api/auth/[...nextauth]/route.ts import NextAuth from "next-auth"; import Google from "next-auth/providers/google"; const handler = NextAuth({ providers: [Google({ clientId: process.env.GOOGLE_ID, clientSecret: process.env.GOOGLE_SECRET })], callbacks: { session: ({ session, token }) => ({ ...session, userId: token.sub }) } }); export { handler as GET, handler as POST };. Session access in Server Components: import { getServerSession } from "next-auth"; const session = await getServerSession(authOptions);. Session access in Client Components: wrap with SessionProvider, use useSession() hook. Protecting routes with Middleware: // middleware.ts export function middleware(req) { const token = req.cookies.get("next-auth.session-token"); if (!token && req.nextUrl.pathname.startsWith("/dashboard")) { return NextResponse.redirect(new URL("/login", req.url)); } }. Custom auth: JWT tokens stored in httpOnly cookies; read in middleware and Server Components via cookies(). Clerk, Supabase Auth, Firebase Auth: third-party auth services with Next.js SDK support. Security considerations: always validate session on the server (not just client); use httpOnly cookies for session tokens; implement CSRF protection for Server Actions; rate-limit login attempts.
05
What is Next.js App Router data fetching patterns?
The App Router introduces several data fetching patterns: 1. Sequential fetching: requests execute one after another. Use when data depends on previous data: async function Page({ params }) { const user = await getUser(params.id); const posts = await getUserPosts(user.id); // depends on user return <...>; }. 2. Parallel fetching: initiate multiple requests simultaneously — faster when independent: async function Page() { const [users, products] = await Promise.all([getUsers(), getProducts()]); return <...>; }. 3. Preloading pattern: start a fetch before an await: function preloadUser(id: string) { void getUser(id); } async function Page({ params }) { preloadUser(params.id); // starts fetch immediately const layout = await getLayout(); // while this runs const user = await getUser(params.id); // already cached return <...>; }. React's Request Memoization deduplicates the getUser call. 4. Waterfall avoidance with Suspense: use Suspense boundaries to start multiple fetches independently: <Suspense><UserStats userId={id} /></Suspense> <Suspense><UserPosts userId={id} /></Suspense>. Each component fetches its own data in parallel, regardless of rendering order. 5. Client-side fetching with SWR/React Query: for real-time data, user-specific data, or after-load enrichment: const { data, isLoading } = useSWR("/api/realtime-data", fetcher, { refreshInterval: 3000 });. 6. Route segment config: export const dynamic = "force-dynamic" or export const revalidate = 60 — apply to all fetches in a route.
06
What is the Next.js App Router folder structure and special files?
The App Router uses specific file conventions within the app/ directory: Route files: page.tsx — unique UI for a route (makes it publicly accessible); layout.tsx — shared UI, persists across navigations within its segment; template.tsx — like layout but re-mounts on each navigation (for animations, per-route state); loading.tsx — Suspense fallback; error.tsx — error boundary; not-found.tsx — 404 UI (triggered by notFound()); route.ts — API endpoint (HTTP method exports). Metadata files: icon.png / apple-icon.png — favicon; opengraph-image.png — OG image; robots.ts — robots.txt generator; sitemap.ts — sitemap.xml generator; manifest.ts — web app manifest. Folder conventions: [param] — dynamic segment; [...param] — catch-all; [[...param]] — optional catch-all; (group) — route group (no URL effect); _private — opted out of routing; @slot — named slot for parallel routes. Parallel routes: app/@modal/page.tsx — simultaneously render multiple pages; consumed in layout via { modal }: { modal: React.ReactNode }. Intercepting routes: app/(.)photo/[id]/page.tsx — intercept navigation to show a modal while keeping the current page. Global files: app/global-error.tsx — catch root layout errors; app/favicon.ico.
07
How does Next.js handle internationalization (i18n)?
Next.js provides i18n support through routing and the next-intl or next-i18next libraries. Built-in i18n (Pages Router): configure in next.config.js: i18n: { locales: ["en", "fr", "es"], defaultLocale: "en", localeDetection: true }. Next.js adds locale to URLs: /en/about, /fr/about. Access locale in pages: const { locale } = useRouter(). App Router i18n with next-intl: (most popular library for App Router) Folder structure: app/[locale]/layout.tsx and app/[locale]/page.tsx — the locale is a dynamic route segment. Middleware routes to correct locale: // middleware.ts import createMiddleware from "next-intl/middleware"; export default createMiddleware({ locales: ["en", "fr"], defaultLocale: "en" });. Translation files: messages/en.json, messages/fr.json. Server Components: const t = await getTranslations("HomePage"); <h1>{t("title")}</h1>. Client Components: const t = useTranslations("HomePage");. URL strategies: path prefix (/en/about), domain (en.example.com), cookie/Accept-Language (no URL change). Currency/date formatting: use Intl API with the locale: new Intl.NumberFormat("fr-FR", { style: "currency", currency: "EUR" }).format(price). RTL support: detect RTL locales (Arabic, Hebrew) and set HTML dir attribute.
08
What is the difference between Static and Dynamic rendering in Next.js App Router?
Next.js App Router automatically determines whether to render a route statically or dynamically: Static rendering (default): routes are rendered at build time and cached. The result is shared among all users. No per-request work. Served from CDN edge. Routes are static when: no dynamic functions are used (cookies(), headers(), searchParams), all fetch() calls are cached (force-cache or revalidate). Dynamic rendering (per-request): routes are rendered on the server for each request. Enables user-specific data, real-time data. Routes become dynamic when: cookies() is called anywhere in the route (implies request-specific data); headers() is called; searchParams is accessed in page.tsx; fetch() with { cache: "no-store" } is used; export const dynamic = "force-dynamic" route config. Automatic detection: Next.js analyzes your code at build time. If it can determine all data at build time → static. If it detects dynamic functions → dynamic. Opting in/out explicitly: export const dynamic = "force-static" (always static, error on dynamic functions), export const dynamic = "force-dynamic" (always dynamic), export const dynamic = "error" (error on dynamic functions — ensures static). Partial rendering: with Suspense, parts of a page can be static while others are dynamic (Partial Prerendering). Deployment consideration: static routes work on any CDN; dynamic routes require a Node.js server (Vercel Functions, AWS Lambda, etc.).
09
How does the Next.js build process work?
The Next.js build process (next build) compiles and optimizes the application for production: Build output directory: .next/ contains all build artifacts. Build steps: (1) TypeScript compilation and type checking; (2) ESLint linting; (3) Static analysis — Next.js analyzes each route to determine: static (pre-render at build time), ISR (pre-render + revalidation), or dynamic (render at runtime); (4) Static generation — Next.js runs generateStaticParams for dynamic routes and pre-renders all static routes; (5) JavaScript bundling with Webpack/Turbopack — code splitting, tree shaking, minification; (6) CSS optimization — PostCSS, Tailwind purging; (7) Image optimization setup; (8) Route manifest generation. Build output indicators: ○ Static, ● ISR (with revalidate), λ Serverless (dynamic). Output modes: output: "standalone" — includes only necessary files for production (no node_modules), great for Docker; output: "export" — full static export (no server required). Analyzing bundle size: ANALYZE=true next build with @next/bundle-analyzer generates a visual treemap of bundle composition. Build caching: .next/cache/ preserves incremental compilation results between builds — unchanged code reuses cached compilation. Deployment: Vercel deploys automatically; self-host with node .next/standalone/server.js or Docker. next start starts the production server.
10
What is the Next.js App Router middleware in depth?
Middleware in Next.js runs at the edge (globally distributed) before requests reach your pages or API routes. Understanding its capabilities and limitations is important: Edge Runtime constraints: middleware runs in the Edge Runtime, which is a subset of Node.js: no Node.js built-ins (fs, path, crypto), limited npm package support; but: full Web API support (fetch, URL, Request, Response, Headers, Cookies), fast startup time (no cold start like serverless). NextRequest extensions: beyond standard Request: request.nextUrl — parsed URL with Next.js-specific properties (pathname, searchParams, locale); request.geo — country, city, region (on Vercel); request.ip — client IP; request.cookies — cookie management. NextResponse extensions: NextResponse.redirect(url) — 307 redirect; NextResponse.rewrite(url) — rewrite URL without redirect (browser URL unchanged); NextResponse.next() — continue to next handler; response.cookies.set(name, value, options). Authentication pattern: verify session token → redirect to login if invalid. Geolocation-based routing: detect country from request.geo.country → redirect to locale. A/B testing: set a cookie to assign user to variant, rewrite to variant page. Rate limiting: use a Redis-backed counter (via fetch to a rate limit service — can't use Redis client directly in edge). matcher specificity: use specific matchers to avoid running on static assets: matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"].
11
What are Next.js Server Actions best practices?
Server Actions are powerful but require careful implementation: 1. Security — validate inputs: always validate server action inputs — never trust client data: async function createUser(formData: FormData) { "use server"; const name = formData.get("name"); if (typeof name !== "string" || name.length < 2) throw new Error("Invalid name"); }. Use Zod for schema validation. 2. Authentication — verify the caller: const session = await getServerSession(); if (!session) throw new Error("Unauthorized");. Never assume the caller is authenticated. 3. CSRF protection: Next.js provides built-in CSRF protection for Server Actions — they only accept POST requests with specific headers. Don't use them as GET endpoints. 4. Return consistent shapes: type ActionResult = { success: boolean; data?: any; error?: string }; async function createUser(fd: FormData): Promise<ActionResult> { try { ... return { success: true, data: user }; } catch(e) { return { success: false, error: e.message }; } }. 5. Revalidate after mutations: call revalidatePath() or revalidateTag() after data changes to refresh cached data. 6. Progressive enhancement: Server Actions work as HTML form actions without JavaScript. Design forms to work without JS first. 7. Optimistic updates: use useOptimistic hook to show immediate UI feedback while the action runs. 8. Loading states: use useFormStatus to disable submit button during pending state. 9. Error boundaries: wrap form components in error boundaries for action errors.
12
What is Next.js OpenGraph image generation?
Next.js can generate OpenGraph (OG) images dynamically server-side using the ImageResponse API from next/og. These images appear when links are shared on social media. Static OG image: place opengraph-image.png in the route folder — used automatically for all pages in that segment. Dynamic OG images: create opengraph-image.tsx (or .js) in the route folder: import { ImageResponse } from "next/og"; export const runtime = "edge"; // Edge for faster generation export const size = { width: 1200, height: 630 }; export const contentType = "image/png"; export default async function OGImage({ params }: { params: { id: string } }) { const post = await getPost(params.id); return new ImageResponse( <div style={{ display: "flex", flexDirection: "column", backgroundColor: "#1a1a2e", width: "100%", height: "100%", padding: 48 }}> <h1 style={{ color: "white", fontSize: 64 }}>{post.title}</h1> <p style={{ color: "#aaa" }}>{post.excerpt}</p> </div>, { ...size } ); }. ImageResponse: renders JSX to a PNG image using Satori (HTML/CSS to SVG to PNG). Supports subset of CSS (flexbox, basic styling). Can load fonts and images. Edge runtime: OG images are generated at the edge for minimal latency. Caching: OG images are cached by default. Twitter card images: similarly create twitter-image.tsx. Custom fonts: load custom fonts in the ImageResponse options: { fonts: [{ name: "Inter", data: fontData }] }.
Deep expertise questions for senior and lead roles.
01
How does React Server Components payload work?
React Server Components use a special RSC payload format for client-server communication that differs from traditional SSR HTML streaming. RSC Payload format: a custom binary/JSON streaming protocol. Not HTML — it describes the component tree as serialized React elements, component boundaries, and references. Contains: rendered output of Server Components (JSON-like), placeholders for Client Component boundaries (with their props), references to Client Component module chunks to load. Two distinct operations: (1) HTML streaming (initial load): for the first request, Next.js streams HTML for fast First Contentful Paint + the RSC payload in script tags for hydration; (2) RSC payload only (navigation): for client-side navigation, Next.js fetches only the RSC payload (not full HTML) — the payload updates only the changed route segment, preserving client state in unchanged segments. Why RSC payload instead of HTML for navigation? HTML re-creates the entire DOM (loses state). RSC payload lets React reconcile — preserve Client Component state while updating Server Component output. Client Components in RSC: when a Server Component renders a Client Component, the RSC payload includes: component module reference, props (serialized — must be serializable), a slot for the Client Component's rendering (which happens client-side). Boundaries: Server-to-client boundary: Server Component renders <ClientComp prop={data}/> — data is serialized into RSC payload as props. The Client Component renders with those props on the client.
02
What are Next.js deployment strategies and considerations?
Next.js applications can be deployed in multiple ways: 1. Vercel (native platform): zero-config deployment. Automatic builds on push, serverless functions for API routes and Server Components, edge network, ISR support, preview deployments per PR, analytics. Best experience for Next.js. 2. Node.js server (self-hosted): next build && next start starts a Node.js HTTP server. Use output: "standalone" in next.config.js for a minimal build including only necessary files. Docker Dockerfile: copy .next/standalone, .next/static, public → set CMD [node, "server.js"]. 3. Docker + Kubernetes: containerize with standalone output. Kubernetes for scaling, rolling deployments, health checks. 4. Static export: output: "export" — generates static HTML/CSS/JS for all routes. No server-side features (no API routes, middleware, SSR, ISR). Serve from any static host (S3, Netlify, GitHub Pages). 5. AWS: Lambda via OpenNext (open-source adapter), ECS/EKS for Docker, CloudFront for edge caching. 6. Cloudflare Pages/Workers: Edge-native deployment with Workers for middleware, D1/KV for data. ISR in self-hosted: requires Node.js server and filesystem caching. cacheHandler config to use Redis for distributed cache. Environment variables: runtime env vars via platform (Vercel env vars, K8s secrets/configmaps). CDN considerations: put CDN in front of Node.js server for static asset caching and DDoS protection.
03
How does Next.js handle bundle optimization?
Next.js applies several layers of bundle optimization automatically: 1. Automatic code splitting: each page/route gets its own JavaScript bundle. Shared code between pages is extracted into a common chunk. Lazy-loaded routes are split into separate chunks. 2. Server Components (App Router): Server Component code is NEVER included in the client bundle — massive reduction. Only Client Component code goes to the browser. 3. Tree shaking: Webpack/Turbopack removes unused exports from packages. Next.js fully supports tree-shaking. Mark side-effect-free modules in package.json: "sideEffects": false. 4. Dynamic imports (manual code splitting): const HeavyChart = dynamic(() => import("./Chart"), { loading: () => <p>Loading...</p>, ssr: false // Client-only });. Splits heavy components into separate chunks, loaded on demand. 5. Package imports optimization: optimizePackageImports: ["@mui/material", "lodash"] in next.config.js — enables tree-shaking for barrel files (index.js that re-exports everything). 6. SWC minification: uses Rust-based SWC for minification (faster + smaller output than Terser). 7. CSS optimization: CSS modules for scoped CSS, Tailwind CSS purging in production. 8. Bundle analysis: @next/bundle-analyzer — visualize what's in each bundle. Find large dependencies and replace with lighter alternatives. 9. Image and font optimization: prevent large image bundles — images should be served from CDN, not bundled. 10. Third-party scripts: use strategy="lazyOnload" for non-critical third-party scripts.
04
What is the Next.js App Router parallel routes and intercepting routes?
Parallel Routes allow simultaneously rendering multiple pages in the same layout, enabling complex UX patterns like split-pane views, modals, and dashboards with independently navigable sections. Defining parallel routes (slots): create folders prefixed with @: app/dashboard/@analytics/page.tsx, app/dashboard/@team/page.tsx. The parent layout receives them as props: // app/dashboard/layout.tsx export default function Layout({ children, analytics, team }) { return <div> <main>{children}</main> <aside>{analytics}</aside> <section>{team}</section> </div>; }. Each slot has independent navigation and loading states. Conditional rendering: render different content in a slot based on state/conditions — show modal overlay in the @modal slot. default.tsx: fallback when slot has no match at current URL — prevents 404 for unmatched slots. Intercepting Routes: intercept a route to show its content in the current context (like a modal) instead of navigating to the full page. Conventions: (.) — intercept same level; (..) — one level up; (...) — from root. Modal pattern: app/@modal/(.)photo/[id]/page.tsx — when navigating to /photo/1, if coming from /feed, show a modal overlay. If directly accessing /photo/1, show the full photo page. Implementation: slot @modal has a default.tsx that returns null; when modal route is intercepted, shows the modal component in the slot. Full page navigation unaffected.
05
How do you optimize Core Web Vitals in Next.js?
Next.js is designed for excellent Core Web Vitals but requires correct implementation: LCP (Largest Contentful Paint — target: <2.5s): (1) Add priority prop to the LCP image — disables lazy loading, adds preload hint; (2) Serve images via CDN with next/image; (3) Use SSR/SSG for fast initial HTML; (4) Streaming SSR — send HTML immediately; (5) Preconnect to important third-party origins. FID/INP (Interaction to Next Paint — target: <200ms): (1) Minimize JavaScript with Server Components; (2) Code split with dynamic imports; (3) Avoid long tasks blocking main thread; (4) Use web workers for CPU-heavy client work; (5) Optimize third-party scripts with afterInteractive/lazyOnload. CLS (Cumulative Layout Shift — target: <0.1): (1) Always specify width/height on images (next/image enforces this); (2) Reserve space for embeds, ads, dynamic content; (3) Avoid inserting content above existing content; (4) Use CSS aspect-ratio for responsive media; (5) Don't use animations that move layout (prefer opacity, transform). TTFB (Time to First Byte — target: <800ms): (1) Use static generation where possible; (2) ISR for frequently-updated content; (3) Edge caching (Vercel Edge Network); (4) Server-close-to-user deployment; (5) Efficient database queries in SSR. Measuring: Next.js Analytics (Vercel), Chrome DevTools Performance tab, Lighthouse, web-vitals library: import { onCLS, onINP, onLCP } from "web-vitals";.