pencils ready ✎

Next.js MCQs multiple-choice questions with answers & explanations

All 20 Next.js quiz questions on one page. Pick an answer in your head, then open Show answer to check it and read why. Want a score and a timer? Take them as a quiz instead.

20 questions
  1. 1.

    In Next.js 15, how does next build classify this page, which uses no other request APIs?

    mid
    // app/prices/page.tsx
    export default async function Page() {
      const res = await fetch('https://api.example.com/prices');
      const prices = await res.json();
      return <p>{prices.length} prices</p>;
    }
    1. A○ Static: prerendered at build
    2. Bƒ Dynamic: rendered per request
    3. C● SSG with generated params
    4. DBuild error: fetch needs a cache option
    Show answer

    Answer: A (○ Static: prerendered at build)

    A plain fetch is not stored in the Data Cache in Next.js 15, but it does not opt the route into dynamic rendering either. The fetch runs once at build time and its result is frozen into the prerendered page until the route is revalidated. Use cache: 'no-store', connection() or another request API to make it dynamic.

  2. 2.

    Which of these, used inside a page Server Component, makes the route dynamically rendered?

    easy
    1. Aawait cookies()
    2. Bnew Date().toISOString() in the JSX
    3. CImporting a Client Component
    4. Dexport const revalidate = 60
    Show answer

    Answer: A (await cookies())

    cookies() reads data that only exists per request, so Next renders the route on demand (ƒ). Calling new Date() does not opt out: the timestamp is simply captured at build time. revalidate = 60 keeps the route static and adds background regeneration (ISR).

  3. 3.

    Components A and B both render on the same page. How many times does the backend receive a request per page view?

    hard
    async function getTime() {
      const res = await fetch('https://api.example.com/time', { cache: 'no-store' });
      return (await res.json()).now;
    }
    async function A() { return <p>a {await getTime()}</p>; }
    async function B() { return <p>b {await getTime()}</p>; }
    export default function Page() { return <><A /><B /></>; }
    1. AOnce
    2. BTwice
    3. CZero: the page is static
    4. DOnce per build, then never again
    Show answer

    Answer: A (Once)

    Next.js memoizes GET fetches with the same URL and options for the duration of one server render, even with no-store. Both components get the same value from one request. no-store only means nothing is kept across requests, so the next page view fetches again.

  4. 4.

    Clicker is a 'use client' component. What happens when this Server Component renders?

    mid
    import { Clicker } from './Clicker';
    
    export default function Page() {
      return <Clicker onClick={() => console.log('clicked')} />;
    }
    1. AWorks: the handler is sent to the browser
    2. BRender error: event handlers cannot be passed to Client Component props
    3. CThe handler runs on the server when clicked
    4. DThe prop is silently dropped
    Show answer

    Answer: B (Render error: event handlers cannot be passed to Client Component props)

    Props that cross into a Client Component must be serializable, and an ordinary function is not. Next reports that event handlers cannot be passed to Client Component props. Define the handler inside the Client Component, or pass a Server Action, which is the one kind of function allowed across the boundary.

  5. 5.

    You add app/dashboard/error.tsx without a 'use client' directive. What happens?

    easy
    1. AIt works as a server error page
    2. BThe build fails: error.tsx must be a Client Component
    3. CIt is ignored and the root error page is used
    4. DIt only catches errors in production
    Show answer

    Answer: B (The build fails: error.tsx must be a Client Component)

    error.tsx becomes a React error boundary, and error boundaries rely on client-side lifecycle, so Next fails the build and asks for the directive. It also needs to be a Client Component to call reset() and retry rendering in the browser.

  6. 6.

    app/dashboard/layout.tsx throws during render. Both app/error.tsx and app/dashboard/error.tsx exist. Which one renders?

    mid
    1. Aapp/dashboard/error.tsx
    2. Bapp/error.tsx
    3. Capp/global-error.tsx only
    4. DNeither: layouts cannot throw
    Show answer

    Answer: B (app/error.tsx)

    A segment's error.tsx wraps that segment's page and children, but sits inside its layout.tsx. An error in the dashboard layout therefore bubbles to the parent boundary, app/error.tsx. Errors in the root layout need app/global-error.tsx.

  7. 7.

    The form is submitted. What does the user see, and what is logged on the server?

    hard
    async function save() {
      'use server';
      try {
        await db.save();
        redirect('/done');
      } catch (e) {
        console.log('caught', (e as Error).message);
      }
    }
    1. ARedirect to /done; nothing logged
    2. BNo redirect; logs caught NEXT_REDIRECT
    3. CRedirect to /done; logs caught NEXT_REDIRECT
    4. DA 500 error page
    Show answer

    Answer: B (No redirect; logs caught NEXT_REDIRECT)

    redirect() works by throwing a special error with the message NEXT_REDIRECT, which Next intercepts. The catch swallows it, so the redirect never happens. Call redirect() after the try/catch, or rethrow with unstable_rethrow(e) from next/navigation.

  8. 8.

    A static page renders a Client Component that calls useSearchParams(), with no Suspense boundary around it. What does next build do?

    mid
    1. ABuilds fine; params are read on the client
    2. BFails: useSearchParams() should be wrapped in a suspense boundary
    3. CMarks the page as dynamic (ƒ)
    4. DPrerenders it with an empty query string and warns
    Show answer

    Answer: B (Fails: useSearchParams() should be wrapped in a suspense boundary)

    Search params are unknown at build time, so the component must bail out to client rendering. Without a Suspense boundary the whole page would bail out, and Next fails the prerender with a missing-suspense error. Wrap the component in Suspense so the rest of the page stays static.

  9. 9.

    In Next.js 15, what happens when a Server Component page contains this line?

    mid
    const Chart = dynamic(() => import('./Chart'), { ssr: false });
    1. AChart loads only in the browser
    2. BBuild error: ssr: false is not allowed in Server Components
    3. CThe option is ignored and Chart is server-rendered
    4. DChart is never rendered
    Show answer

    Answer: B (Build error: ssr: false is not allowed in Server Components)

    Server Components never run in the browser, so "skip the server" has no meaning there, and Next.js 15 reports an error. Move the dynamic(..., { ssr: false }) call into a small Client Component and render that from the page.

  10. 10.

    How is this Route Handler rendered by default in Next.js 15?

    easy
    // app/api/time/route.ts
    export async function GET() {
      return Response.json({ now: Date.now() });
    }
    1. AStatic: evaluated once at build
    2. BDynamic: runs on every request
    3. CCached for 60 seconds
    4. DCached until the next deployment
    Show answer

    Answer: B (Dynamic: runs on every request)

    Next.js 15 made GET Route Handlers dynamic by default; in Next.js 14 this handler would have been prerendered at build time. Add export const dynamic = 'force-static' if you want a static response.

  11. 11.

    A page has export const revalidate = 60. The cached copy was generated 90 seconds ago. What does the next visitor get?

    mid
    1. AThe stale cached page, while a fresh one is generated in the background
    2. BThey wait while the page is regenerated
    3. CA 503 until regeneration finishes
    4. DThe page as of the last deployment, forever
    Show answer

    Answer: A (The stale cached page, while a fresh one is generated in the background)

    Time-based revalidation is stale-while-revalidate. The first request after the window still gets the cached page and triggers a background regeneration; later visitors get the new version once it succeeds. If regeneration fails, the last good page keeps being served.

  12. 12.

    You change NEXT_PUBLIC_API_URL in the hosting environment and restart next start without rebuilding. What URL does client-side code use?

    mid
    1. AThe new value
    2. BThe value from build time
    3. Cundefined
    4. DWhichever value the server reads first
    Show answer

    Answer: B (The value from build time)

    NEXT_PUBLIC_ variables are inlined into the JavaScript bundle as literal strings when you run next build. Restarting the server does not change already-built files, so the browser keeps the old value until you rebuild.

  13. 13.

    A 'use client' component renders process.env.DATABASE_URL. What does the browser show?

    easy
    1. AThe database URL
    2. BNothing: the value is undefined
    3. CA build error
    4. DThe string process.env.DATABASE_URL
    Show answer

    Answer: B (Nothing: the value is undefined)

    Only variables prefixed with NEXT_PUBLIC_ are inlined into client bundles. Others stay server-only, so in the browser the expression evaluates to undefined. That is a safety feature: secrets do not leak by accident.

  14. 14.

    In Next.js 15, what happens when a page reads params.id without awaiting params?

    mid
    export default function Page({ params }: any) {
      return <p>id {params.id}</p>;
    }
    1. AIt renders id undefined
    2. BIt works, but development logs that params should be awaited
    3. CBuild error
    4. DTypeError: cannot read properties of a Promise
    Show answer

    Answer: B (It works, but development logs that params should be awaited)

    Next.js 15 made params a Promise but keeps temporary synchronous access for migration. The page renders the id, and next dev logs that params should be awaited before using its properties. Write const { id } = await params in an async page.

  15. 15.

    With this middleware config, which request does middleware not run for?

    mid
    export const config = { matcher: ['/dashboard/:path*'] };
    1. A/dashboard
    2. B/dashboard/settings
    3. C/dashboard/a/b
    4. D/dashboards
    Show answer

    Answer: D (/dashboards)

    :path* matches zero or more segments after /dashboard/, so /dashboard itself and any depth below it match. /dashboards is a different first segment and is skipped. A matcher that misses a route is a classic way to leave a page unprotected.

  16. 16.

    What is the response for /posts/c?

    mid
    // app/posts/[slug]/page.tsx
    export const dynamicParams = false;
    export async function generateStaticParams() {
      return [{ slug: 'a' }, { slug: 'b' }];
    }
    1. A200, rendered on demand and cached
    2. B404
    3. C500
    4. DA redirect to /posts/a
    Show answer

    Answer: B (404)

    With dynamicParams = false, only the params returned by generateStaticParams exist; anything else is a 404, like fallback: false in the Pages Router. With the default true, /posts/c would be rendered on its first request.

  17. 17.

    The delete button is only rendered for admins. Can a non-admin trigger deletePost?

    hard
    async function deletePost(formData: FormData) {
      'use server';
      await db.post.delete({ where: { id: formData.get('id') as string } });
    }
    1. ANo: the action ID is only sent to admins
    2. BYes: an action is a POST endpoint anyone can call once they have its ID
    3. CNo: middleware blocks direct action calls
    4. DOnly if they disable JavaScript
    Show answer

    Answer: B (Yes: an action is a POST endpoint anyone can call once they have its ID)

    A Server Action is invoked with a plain POST that carries its action ID, and any client can send that request. Hiding UI is not access control. Check the session and the user's role inside the action before touching the database.

  18. 18.

    A component in app/ calls useState but the file has no directive. What happens?

    easy
    1. AIt works; hooks run on the server
    2. BCompile error: the hook only works in a Client Component
    3. CState resets on every render, without errors
    4. DNext adds use client automatically
    Show answer

    Answer: B (Compile error: the hook only works in a Client Component)

    Files in app/ are Server Components by default, and Server Components cannot hold state. Next fails with an error saying the hook only works in a Client Component. Add 'use client' to that file, ideally a small interactive leaf rather than the whole page.

  19. 19.

    This Client Component is rendered on a dynamic page. What happens in a production build?

    mid
    'use client';
    export function Clock() {
      return <p>{Date.now()}</p>;
    }
    1. ANothing unusual: the server value is kept
    2. BReact reports a hydration mismatch (minified error #418) and re-renders the tree on the client
    3. CThe build fails
    4. DThe component renders only on the client
    Show answer

    Answer: B (React reports a hydration mismatch (minified error #418) and re-renders the tree on the client)

    Client Components are server-rendered first, then hydrated. The server and the browser call Date.now() at different moments, so the text differs and React 19 throws a hydration error (#418 in production) and regenerates that tree on the client. Render a stable value first and update it in useEffect.

  20. 20.

    What does router.refresh() from next/navigation do?

    hard
    1. AReloads the browser tab and resets all state
    2. BRe-requests the current route from the server and merges it, keeping client state
    3. CPurges the server Data Cache for the route
    4. DClears every cache layer, including ISR pages
    Show answer

    Answer: B (Re-requests the current route from the server and merges it, keeping client state)

    router.refresh() clears the client Router Cache for the current route and asks the server for a fresh RSC payload, merging it without losing useState or scroll position. It does not invalidate the Data Cache or Full Route Cache: if the server data is cached, you get the same cached result. Use revalidatePath or revalidateTag for that.

esc