Toolmingo
Guides17 min read

50 Next.js Interview Questions (With Answers)

Top Next.js interview questions with clear answers and code examples — covering App Router, Server Components, data fetching, performance, and deployment.

Next.js interviews cover App Router architecture, React Server Components, data fetching strategies, performance optimisation, and deployment. This guide covers 50 of the most common questions — with concise answers and code examples.

Quick reference

Topic Key areas
App Router RSC vs Client Components, layouts, loading, error
Data Fetching fetch, caching, revalidation, streaming
Rendering SSR, SSG, ISR, PPR
Performance Image, Font, Script optimisation, bundle splitting
API Routes Route Handlers, Server Actions, middleware

Architecture & App Router

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

The Pages Router (pre–Next.js 13) uses pages/ directory, React class/function components, and getServerSideProps/getStaticProps for data fetching.

The App Router (Next.js 13+, stable in 14) uses app/ directory, React Server Components by default, nested layouts, and async/await directly in components.

pages/             →  app/
pages/index.tsx    →  app/page.tsx
pages/_app.tsx     →  app/layout.tsx
pages/api/foo.ts   →  app/api/foo/route.ts

2. What are React Server Components (RSC) and how does Next.js use them?

RSC are components that render on the server only — they never ship their JS to the client. In app/ directory, every component is a Server Component by default.

Benefits:

  • Zero client-side JS for the component
  • Direct database/file-system access
  • No need for useEffect/useState for initial data
// app/users/page.tsx — Server Component (default)
async function UsersPage() {
  const users = await db.query('SELECT * FROM users'); // direct DB access
  return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

3. How do you create a Client Component?

Add "use client" directive at the top of the file. Client Components can use hooks, browser APIs, and event handlers.

'use client';

import { useState } from 'react';

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

Rule: Keep Client Components as leaf nodes. Pass server-fetched data as props from Server Components.

4. What is a layout in the App Router?

A layout.tsx file wraps all pages in its directory and preserves state across navigations (no remount).

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

Layouts nest — app/layout.tsx (root) → app/dashboard/layout.tsxapp/dashboard/settings/page.tsx.

5. What is the difference between layout.tsx and template.tsx?

Feature layout.tsx template.tsx
Remounts on navigation? No (preserves state) Yes
Use case Persistent UI (nav, sidebar) Fresh mount needed (animations, useEffect per page)

6. What is the purpose of loading.tsx?

loading.tsx creates a Suspense boundary around the page segment. It renders instantly while the page/data loads.

// app/dashboard/loading.tsx
export default function Loading() {
  return <Skeleton />;
}

Next.js wraps the page.tsx in <Suspense fallback={<Loading />}> automatically.

7. What is error.tsx and how does it work?

error.tsx is an Error Boundary for a route segment. It must be a Client Component.

'use client';

export default function Error({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={reset}>Try again</button>
    </div>
  );
}

8. What are parallel routes and how are they useful?

Parallel routes allow multiple pages to render simultaneously in the same layout using @folder slots.

app/
  @team/
    page.tsx
  @analytics/
    page.tsx
  layout.tsx
// app/layout.tsx
export default function Layout({ team, analytics }: {
  team: React.ReactNode;
  analytics: React.ReactNode;
}) {
  return <>{team}{analytics}</>;
}

Use case: dashboard with independent sections, modals with intercepted routes.

9. What are intercepting routes?

Intercepting routes let you load a route from one part of the app into another — e.g., open a photo in a modal while the URL changes.

Convention: (.), (..), (..)(..), (...) prefixes mirror file-system relative navigation.

app/
  photos/
    [id]/
      page.tsx          ← full page at /photos/123
  @modal/
    (.)photos/
      [id]/
        page.tsx        ← intercepted: shows modal

Data Fetching

10. How do you fetch data in a Server Component?

Use async/await directly — no useEffect needed.

async function ProductPage({ params }: { params: { id: string } }) {
  const product = await fetch(`https://api.example.com/products/${params.id}`, {
    next: { revalidate: 60 }, // ISR: revalidate every 60s
  }).then(r => r.json());

  return <h1>{product.name}</h1>;
}

11. What are the caching options for fetch in Next.js?

// Default: cached (equivalent to SSG)
fetch(url);
fetch(url, { cache: 'force-cache' });

// No cache (SSR every request)
fetch(url, { cache: 'no-store' });

// ISR: revalidate after N seconds
fetch(url, { next: { revalidate: 60 } });

// Tag-based revalidation
fetch(url, { next: { tags: ['products'] } });

12. What is revalidatePath and revalidateTag?

On-demand revalidation functions called from Server Actions or Route Handlers.

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

// After a mutation:
revalidatePath('/products');         // revalidate specific path
revalidateTag('products');           // revalidate all fetches tagged 'products'

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

Strategy How When data fetched
SSG fetch with force-cache (default) Build time
SSR fetch with cache: 'no-store' Every request
ISR fetch with next: { revalidate: N } Build + background refresh
PPR Partial Prerendering (Next.js 14+) Static shell + dynamic streaming

14. What is Partial Prerendering (PPR)?

PPR combines static and dynamic rendering in a single route:

  • Static HTML shell is served instantly from CDN
  • Dynamic parts stream in via Suspense
// next.config.js
const nextConfig = { experimental: { ppr: true } };

// page.tsx
export default async function Page() {
  return (
    <>
      <StaticHeader />           {/* served immediately */}
      <Suspense fallback={<Skeleton />}>
        <DynamicFeed />          {/* streams in */}
      </Suspense>
    </>
  );
}

15. How do you stream data with loading.tsx and Suspense?

// app/dashboard/page.tsx
import { Suspense } from 'react';
import { RevenueCard, LatestInvoices } from '@/components';

export default function Dashboard() {
  return (
    <>
      <h1>Dashboard</h1>
      <Suspense fallback={<RevenueSkeleton />}>
        <RevenueCard />        {/* fetches its own data */}
      </Suspense>
      <Suspense fallback={<InvoiceSkeleton />}>
        <LatestInvoices />
      </Suspense>
    </>
  );
}

Each Suspense boundary resolves independently — no waterfall.


Server Actions

16. What are Server Actions?

Server Actions are async functions that run on the server, called directly from Client or Server Components. They replace API routes for mutations.

// app/actions.ts
'use server';

import { db } from '@/lib/db';
import { revalidatePath } from 'next/cache';

export async function createTodo(formData: FormData) {
  const title = formData.get('title') as string;
  await db.todo.create({ data: { title } });
  revalidatePath('/todos');
}
// app/todos/page.tsx (Server Component)
import { createTodo } from '../actions';

export default function TodoPage() {
  return (
    <form action={createTodo}>
      <input name="title" />
      <button type="submit">Add</button>
    </form>
  );
}

17. How do you call a Server Action from a Client Component?

'use client';

import { useTransition } from 'react';
import { createTodo } from '../actions';

export function AddTodoForm() {
  const [isPending, startTransition] = useTransition();

  function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    startTransition(() => createTodo(formData));
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="title" disabled={isPending} />
      <button disabled={isPending}>Add</button>
    </form>
  );
}

18. How do you handle validation and errors in Server Actions?

'use server';

import { z } from 'zod';

const schema = z.object({ title: z.string().min(1).max(200) });

export async function createTodo(prevState: any, formData: FormData) {
  const parsed = schema.safeParse({ title: formData.get('title') });
  if (!parsed.success) {
    return { error: parsed.error.flatten().fieldErrors };
  }
  await db.todo.create({ data: parsed.data });
  revalidatePath('/todos');
  return { success: true };
}

Use with useActionState (React 19 / Next.js 14.3+) for progressive enhancement.


Routing

19. What are dynamic route segments?

Folder names in brackets create dynamic segments:

app/blog/[slug]/page.tsx   →  /blog/hello-world  (params.slug = "hello-world")
app/shop/[...slugs]/page.tsx  →  /shop/a/b/c  (params.slugs = ["a","b","c"])
app/[[...slugs]]/page.tsx  →  / or /a/b  (optional catch-all)

20. What does generateStaticParams do?

Pre-generates dynamic routes at build time (SSG equivalent for App Router).

export async function generateStaticParams() {
  const posts = await fetch('https://api.example.com/posts').then(r => r.json());
  return posts.map((post: { slug: string }) => ({ slug: post.slug }));
}

21. How does middleware work in Next.js?

middleware.ts at the project root runs before every matched request — ideal for auth, redirects, A/B testing.

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

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

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

22. What is the difference between redirect() and NextResponse.redirect()?

redirect() NextResponse.redirect()
Where Server Components, Server Actions, Route Handlers Middleware only
Throws Yes (throws NEXT_REDIRECT) No
Status 307 (temporary) by default Configurable

23. How do Route Handlers work?

Route Handlers replace API routes (pages/api/) in the App Router.

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

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

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

Performance

24. How does next/image optimise images?

<Image> from next/image:

  • Converts to WebP/AVIF automatically
  • Serves correct size per device (responsive srcset)
  • Lazy loads by default (loading="lazy")
  • Prevents CLS by requiring width/height or fill
import Image from 'next/image';

<Image
  src="/hero.jpg"
  alt="Hero"
  width={1200}
  height={600}
  priority          // LCP image — disables lazy loading
  placeholder="blur"
  blurDataURL="data:image/jpeg;base64,..."
/>

25. How does next/font work?

next/font downloads fonts at build time, self-hosts them, and eliminates layout shift (no external network request at runtime).

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

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

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className={inter.className}>
      <body>{children}</body>
    </html>
  );
}

26. How does next/script work?

Controls when third-party scripts load:

import Script from 'next/script';

// beforeInteractive — critical scripts (blocks page)
// afterInteractive — default, loads after hydration
// lazyOnload — loads during browser idle
// worker — loads in Web Worker (Partytown)
<Script src="https://analytics.io/script.js" strategy="lazyOnload" />

27. What is code splitting in Next.js?

Next.js automatically splits bundles by route. You can also split manually:

import dynamic from 'next/dynamic';

// Component-level code splitting
const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
  loading: () => <p>Loading chart...</p>,
  ssr: false, // disable SSR for browser-only components
});

28. What is the unstable_cache function?

Caches the result of any async function (not just fetch), useful for database queries.

import { unstable_cache } from 'next/cache';

const getCachedUser = unstable_cache(
  async (id: string) => db.user.findUnique({ where: { id } }),
  ['user'],
  { revalidate: 3600, tags: ['user'] }
);

Metadata & SEO

29. How do you define metadata in Next.js App Router?

// Static metadata
export const metadata = {
  title: 'My App',
  description: 'Description here',
  openGraph: {
    title: 'My App',
    images: ['/og.png'],
  },
};

// Dynamic metadata
export async function generateMetadata({ params }: { params: { id: string } }) {
  const product = await fetchProduct(params.id);
  return {
    title: product.name,
    description: product.description,
  };
}

30. How does the title template work?

// app/layout.tsx
export const metadata = {
  title: {
    template: '%s | My App',
    default: 'My App',
  },
};

// app/about/page.tsx
export const metadata = { title: 'About' };
// Result: "About | My App"

Authentication

31. How do you implement authentication in Next.js?

Common approaches:

  1. NextAuth.js / Auth.js — OAuth, credentials, JWT/sessions
  2. Clerk / Supabase Auth — managed auth
  3. Custom JWT in middleware — manual token validation
// middleware.ts with NextAuth
export { auth as middleware } from '@/auth';

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

32. How do you protect a Server Component?

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

export default async function DashboardPage() {
  const session = await auth();
  if (!session) redirect('/login');

  return <Dashboard user={session.user} />;
}

Deployment & Configuration

33. What output modes does Next.js support?

Mode Config Description
Node.js server default Full Next.js server — required for ISR, Server Actions
Standalone output: 'standalone' Minimal Docker image (includes only needed files)
Static export output: 'export' Pure HTML/CSS/JS — no server needed (no SSR/ISR)

34. How do you set up environment variables in Next.js?

.env.local           — local dev, not committed
.env.development     — dev only
.env.production      — production only
.env                 — all environments (committed)

Exposing to the browser: prefix with NEXT_PUBLIC_.

process.env.SECRET_KEY        // server only
process.env.NEXT_PUBLIC_API   // available on client

35. What is next.config.js used for?

/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [{ hostname: 'cdn.example.com' }],
  },
  redirects: async () => [
    { source: '/old', destination: '/new', permanent: true },
  ],
  headers: async () => [
    {
      source: '/(.*)',
      headers: [{ key: 'X-Frame-Options', value: 'DENY' }],
    },
  ],
  experimental: { ppr: true },
};

module.exports = nextConfig;

Testing

36. How do you test Next.js components?

// __tests__/page.test.tsx (Vitest or Jest)
import { render, screen } from '@testing-library/react';
import HomePage from '@/app/page';

test('renders heading', () => {
  render(<HomePage />);
  expect(screen.getByRole('heading')).toBeInTheDocument();
});

For Server Components, use Playwright or test utilities from next/experimental/testing.

37. How do you test Route Handlers?

import { GET } from '@/app/api/users/route';
import { NextRequest } from 'next/server';

test('returns users', async () => {
  const request = new NextRequest('http://localhost/api/users');
  const response = await GET(request);
  expect(response.status).toBe(200);
  const body = await response.json();
  expect(Array.isArray(body)).toBe(true);
});

Advanced Topics

38. What is the difference between cookies() and headers() in Server Components?

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

// Read cookies (opts into dynamic rendering)
const cookieStore = cookies();
const token = cookieStore.get('token')?.value;

// Read request headers
const headersList = headers();
const userAgent = headersList.get('user-agent');

Both opt the route into dynamic rendering (no static caching).

39. How do useRouter, usePathname, and useSearchParams work?

All require "use client":

'use client';

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

export function NavControls() {
  const router = useRouter();
  const pathname = usePathname();          // "/dashboard/settings"
  const searchParams = useSearchParams();  // URLSearchParams

  function navigate() {
    router.push('/about');
    router.replace('/about');
    router.back();
    router.refresh();   // re-fetch Server Components for current route
  }
}

40. What is notFound() and how is it used?

Renders the nearest not-found.tsx file.

// app/products/[id]/page.tsx
import { notFound } from 'next/navigation';

export default async function ProductPage({ params }: { params: { id: string } }) {
  const product = await db.product.findUnique({ where: { id: params.id } });
  if (!product) notFound();
  return <ProductView product={product} />;
}

Create app/not-found.tsx for custom 404 UI.

41. What is Suspense streaming and how does it improve TTFB?

Without streaming: server waits for all data → sends full HTML → large TTFB.

With streaming:

  1. Static shell is sent immediately (low TTFB)
  2. Async components stream in as HTML chunks
  3. Browser progressively renders
// Data fetches in parallel, not sequentially:
export default function Page() {
  return (
    <>
      <Suspense fallback={<S1 />}><Component1 /></Suspense>
      <Suspense fallback={<S2 />}><Component2 /></Suspense>
    </>
  );
}

42. How do you share data between Server Components without prop drilling?

Use React.cache() for request-scoped memoization:

// lib/data.ts
import { cache } from 'react';

export const getUser = cache(async (id: string) => {
  return db.user.findUnique({ where: { id } });
});

Multiple Server Components calling getUser(id) in one request only hit the DB once.

43. What is the "use server" directive?

Marks a file or function as a Server Action boundary. All exports become callable from Client Components but execute only on the server.

// Entire file is server-only:
'use server';

export async function deleteUser(id: string) { ... }
export async function updateUser(id: string, data: any) { ... }

// Or inline in a component:
export default function Page() {
  async function handleDelete(formData: FormData) {
    'use server';
    await deleteUserFromDB(formData.get('id') as string);
  }
  return <form action={handleDelete}>...</form>;
}

44. How does Next.js handle fetch deduplication?

In the same request, identical fetch calls are automatically deduplicated (React memoises them). You can also use React.cache for non-fetch async functions.

45. What is opengraph-image.tsx (or .png)?

Next.js generates Open Graph images automatically if you place opengraph-image.tsx in a route segment using the @vercel/og ImageResponse:

// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og';

export const runtime = 'edge';
export const size = { width: 1200, height: 630 };

export default async function OGImage({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug);
  return new ImageResponse(
    <div style={{ fontSize: 60, background: 'white', width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
      {post.title}
    </div>
  );
}

46. What is the Edge Runtime and when would you use it?

Edge Runtime runs on CDN edge nodes (Vercel Edge Network, Cloudflare Workers). Faster cold starts than Node.js, but limited API surface (no fs, no native modules).

export const runtime = 'edge'; // or 'nodejs' (default)

Use for: middleware, low-latency API responses, geolocation logic.

47. What are the limitations of Static Export (output: 'export')?

Cannot use:

  • Server Actions
  • Route Handlers with dynamic logic
  • next/headers, next/cookies
  • revalidate ISR
  • Middleware
  • Image Optimization (needs external loader)

Allowed: static HTML, CSS, JS — good for CDN hosting.

48. How does Next.js handle CSS?

Approach Import
Global CSS import './globals.css' in root layout
CSS Modules import styles from './component.module.css'
Tailwind CSS PostCSS plugin, works out-of-the-box
CSS-in-JS Client Components only (some libs have RSC support)

49. How do you implement an API proxy in Next.js?

Option 1 — Route Handler:

// app/api/proxy/[...path]/route.ts
export async function GET(request: Request, { params }: { params: { path: string[] } }) {
  const upstream = `https://api.example.com/${params.path.join('/')}`;
  const response = await fetch(upstream, { headers: { Authorization: `Bearer ${process.env.API_KEY}` } });
  return new Response(response.body, { headers: response.headers });
}

Option 2 — next.config.js rewrites (simpler, no custom code):

async rewrites() {
  return [{ source: '/api/:path*', destination: 'https://api.example.com/:path*' }];
}

50. What are common Next.js performance anti-patterns?

Anti-pattern Problem Fix
fetch inside a Client Component Waterfall + no caching Move fetch to Server Component
Large third-party scripts in <head> Blocks rendering Use next/script with lazyOnload
Unoptimized images with <img> No WebP, no lazy load, CLS Use next/image
Sequential await in one component Slow TTFB Use Promise.all or split into Suspense boundaries
Skipping loading.tsx Bad perceived performance Add Suspense boundaries
Not using React.cache Repeated DB queries Wrap data fetchers in cache()
Importing server libs in Client Components Bundle bloat / error Move to Server Components or "use server"
Missing generateStaticParams for known paths SSR instead of SSG Add generateStaticParams

Common mistakes

Mistake Why it fails Fix
Using useState in a Server Component RSC has no client state Add "use client" or lift state to client
Importing a Server-only module in Client Component Client bundle error Use server-only package or move to RSC
Forgetting await on cookies() / headers() (Next.js 15) Returns a Promise in Next.js 15 const c = await cookies()
Mutating params or searchParams directly Read-only Destructure only
Using router.push without "use client" Hooks not allowed in RSC Add "use client"
Putting secrets in NEXT_PUBLIC_ Exposed to browser Use unprefixed env vars for secrets
Disabling Image width/height without fill CLS and error Provide dimensions or use fill prop

Next.js vs alternatives

Feature Next.js Remix Astro Nuxt (Vue)
RSC support Yes Partial Yes No (Vue)
Server Actions Yes Form actions Yes Yes
File-based routing Yes Yes Yes Yes
ISR Yes No (SWR) No Yes
Edge Runtime Yes Yes Yes Yes
Best for Full-stack React Form-heavy apps Content sites Vue apps

FAQ

Q: When should I use Server Actions vs Route Handlers? Server Actions for mutations triggered from UI (forms, buttons). Route Handlers for external webhooks, REST APIs consumed by mobile apps, or endpoints requiring custom headers/status codes.

Q: Can I use Next.js with a separate backend? Yes. Use fetch in Server Components to call your API, or configure rewrites in next.config.js to proxy requests. Next.js is not required to be your backend.

Q: What is the difference between app/ and pages/ in the same project? Both can coexist in Next.js 13/14. Pages Router takes precedence for conflicting routes. Migrate incrementally — but app/ is the future.

Q: Why does my Server Component still cause client-side JS? If you import a Client Component, its JS is included. Make sure RSC-only imports are not used in "use client" files. Use server-only package to guard server code.

Q: What does router.refresh() do? Re-fetches Server Components for the current route without a full page reload. Useful after a mutation to update server-rendered data.

Q: Is Next.js 15 backwards compatible with Next.js 14? Mostly. Breaking changes include cookies(), headers(), params, and searchParams becoming Promise-based (async). Codemods are available: npx @next/codemod@latest upgrade.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools