Next.js App Router

Next.js Interview Questions With Model Answers

Rendering, caching, Server Components, actions, and the traps interviewers like, with model answers.

Lesson 0916 min read#nextjs#interview

On this page

  1. Words used in this lesson
  2. How to answer well
  3. Interview questions
  4. Rapid fire: one-line answers
  5. Try it: a mock interview
  6. Key takeaways
  7. What's next

In one line: A Next.js interview is not a test of how many APIs you remember. It is a test of whether you can say where each piece runs and when it is made, and where the checks, the caches and the secrets live. This lesson has 30 questions with model answers. Many are traps that I ran and proved, so you can say "I tested it" and mean it.

You have finished nine lessons, so you know the App Router from the folders to the Docker image. Now turn that into answers. Interviewers like a few kinds of question: the basics ("what is a Server Component?"), the trap ("a page that does not exist returns 200, why?"), the design question ("how would you build a login-protected dashboard?"), and the debugging question ("my page shows old data in production"). I wrote the answers in the first person, in the length you would say aloud. You can say them in about a minute each.

This is lesson 9 of ten, the last one. If you want more questions about React, TypeScript and the rest of a full-stack interview, go to Next.js Interview Questions in the full-stack series, which has a different set (Next.js 14 to 16 changes, use cache keys, migration and testing). This lesson focuses on the traps from this series. The two do not repeat each other.

What I ran / checked. Linux, Node.js v22.22.0, Next.js 16.3.5 (this repo's version), React 19.3.0, Turbopack. Most answers rest on the experiments in lessons 0 to 8. For this lesson I also ran new tests in a throwaway app: redirect() inside a try/catch, a Client Component's HTML on the server, fetch with and without cache: 'force-cache' (counting hits on a local counter route), useSearchParams with no Suspense, an event handler passed from a Server Component, useState in a Server Component, and a file named middleware.ts. I used next build, next start, curl and Chromium. Sources I read (in my own words): the bundled docs in node_modules/next/dist/docs/01-app/ (guides on authentication, data security, streaming, caching, self-hosting, environment variables and the proxy, plus the API pages for redirect, cookies, use cache, instrumentation and image). Not verified: I did not run a multi-instance deployment, a deploy to a host, or use cache: remote. Where an answer depends on the docs only, I say so. Interviews also change with the company: some still use Next.js 13 to 15, where defaults differ (for example middleware.ts and cached fetch). Always ask which version they use.

Words used in this lesson

  • Trap is a question where the common answer is wrong, or half right.
  • Must know is a question you should answer well even in a first-round screen.
  • Senior signal is a question where the depth of your answer shows experience.
  • Server Component is a component that runs only on the server. It is the default in the App Router.
  • Client Component is a component with 'use client'. It is rendered on the server for the first HTML and then runs in the browser too.
  • Hydration means React attaching event handlers to server-made HTML in the browser.
  • Server Action is an async function with 'use server' that the browser can call. It becomes an HTTP POST endpoint.
  • Static, dynamic, streaming are the three ways a page reaches the browser: built early, built per request, or sent in pieces.
  • Cache Components is the newer caching model (cacheComponents: true) with use cache and a static shell.
  • Proxy is proxy.ts, the new name for middleware.ts.
  • IDOR means being logged in and reading another user's data by changing an ID.

How to answer well

Four habits make an answer sound senior:

  1. Start with one sentence of definition, then say where it runs and when. "A Server Component runs only on the server, at build or request time, and sends no JavaScript for itself."
  2. Give the trade-off. Every Next.js feature costs something: a cache can be stale, a Suspense boundary needs a fallback, a Server Action is a public endpoint.
  3. Say how you would check it. "I would read the route table after next build" or "I would run curl -i and read Cache-Control" is a very strong sentence.
  4. Name the version. Next.js 16 changed middleware to proxy, made params a Promise, and stopped caching fetch by default.

Here is a map of the traps in this lesson. Use it as a last-minute review.

TopicThe wrong beliefWhat is true (and how I know)
Routingparams.slug worksparams is a Promise. Without await, TypeScript fails the build (lesson 0)
LayoutsA layout check protects every page belowA layout is skipped on client navigation (lesson 5)
'use client'The component renders only in the browserIt is also rendered on the server into the first HTML (Q5)
redirect()It returns, like a normal functionIt throws. A try/catch swallows it (Q13)
fetchCached by defaultNot cached by default in 16 (Q8)
Static pagesFresh, because the code says new Date()Frozen at build time (lesson 6)
StreamingnotFound() always gives a 404Inside Suspense it gives 200 plus noindex (lesson 6)
Server ActionsHidden if the button is hiddenA public POST endpoint (lesson 5)
ProxyIt is my auth systemAn optimistic filter only (lesson 5)
NEXT_PUBLIC_Read at run timeFrozen at build time (lesson 8)
MetadataLayout and page openGraph combineShallow merge replaces it (lesson 7)
middleware.tsStill the right file nameDeprecated, proxy.ts is the name (Q16)

Interview questions

Part 1: Routing, Server and Client Components
How does routing work in the App Router, and which files matter most? (Must know)

Routing is based on folders in app/. A folder is a URL segment, and it becomes public only when it has a page file or a route file. Dynamic segments use brackets, like [slug]. The files I use most are layout (shared UI that stays mounted), page, loading (a Suspense fallback), error (an error boundary that must be a Client Component), not-found, and route (an HTTP endpoint). I read the route table after next build to see what each route became.

params is a Promise in this version. What happens if you forget await? (Trap)

In Next.js 15 and 16, params and searchParams are Promises, because their values may only be known when a request arrives. If I write params.slug without await, TypeScript reports that slug does not exist on type Promise<...>, and the build fails. I tested that. The PageProps<'/blog/[slug]'> helper gives the right type. Also, reading searchParams makes the page dynamic. A tutorial that uses params.slug directly is for an older version.

What is the difference between a layout and a template? What happens to state? (Senior signal)

A layout wraps its pages and stays mounted when you navigate between them, so its state is kept and it does not re-render. A template looks the same, but it creates a new instance on each navigation. I proved the layout part with a counter button in the root layout: three clicks, a <Link> to another page, and the counter was still 3 with no new document request. A plain <a> caused a full reload and the counter went to 0. The cost is that a layout cannot react to navigation, which is why auth checks should not live there.

How do you decide between a Server Component and a Client Component? (Must know)

I start with a Server Component, since it is the default and sends no JavaScript for itself. It can read a database or a secret directly. I add 'use client' only for what needs state, effects, event handlers or browser APIs, and I make that part as small and as low in the tree as I can, so the rest stays on the server. The 'use client' line marks a boundary: everything that file imports also becomes client code. I pass data down as props, and I make sure the props are small and not secret.

Does 'use client' mean the component only renders in the browser? (Trap)

No. A Client Component is also rendered on the server, to make the first HTML, and then hydrated in the browser. I checked: a Client Component printed a marker text, and curl showed that marker in the server HTML. So window and document cannot be used during the first render, even in a Client Component. The right names are "server" and "client" components, not "server-only" and "browser-only". Code that must run only in the browser goes in useEffect.

A Server Component passes a function to a Client Component and the build fails. Why? (Trap)

Props that cross from server to client must be serializable (plain data). A function is not. My test passed onClick={() => ...} from a page to a 'use client' button, and the build stopped with:

TEXT
Error: Event handlers cannot be passed to Client Component props.  {onClick: function onClick}            ^^^^^^^^^^^^^^^^If you need interactivity, consider converting part of this to a Client Component.

The fix is to define the handler inside the Client Component, or to pass a Server Action (an async function with 'use server'), which is allowed because the framework turns it into a reference to an endpoint.

What happens if you use useState in a Server Component? (Trap)

The build fails with a clear message. When I imported useState into a page with no 'use client', the output said: "You're importing a module that depends on useState into a React Server Component module. This API is only available in Client Components. To fix, mark the file (or its parent) with the "use client" directive." The fix is to move the stateful part into its own small Client Component. A Server Component cannot use state, effects or event handlers.

Part 2: Data and caching
Is fetch cached by default in Next.js 16? (Trap)

No. fetch is not cached by default. I tested with a counter route: I called the same route with a default fetch and with cache: 'force-cache' (a different query string for the second one, so request memoization could not hide the result). Over three page requests the counter showed:

TEXT
default fetch n=1 | force-cache fetch n=2default fetch n=3 | force-cache fetch n=2default fetch n=4 | force-cache fetch n=2

The default fetch hit the API each time. The force-cache one stayed at 2. But a page that uses no request data is prerendered at build time, and then the plain fetch runs once and is frozen into the page (lesson 2). So "not cached" does not mean "runs for every visitor". Answers from the Next.js 13 and 14 days, where fetch was cached by default, are out of date.

What is request memoization, and when do you need cache()? (Senior signal)

If the same fetch (same URL and options) is called several times while one page renders, Next.js sends one real request. That is memoization, and it only lasts for that one render. It works for fetch. For a database call or any other function, I wrap it in React's cache() to get the same effect. I use it for the user lookup in a Data Access Layer (I counted one run for four calls) and for the post in generateMetadata and the page together (6 database calls became 3). It is per request, so it is not a cache shared between users.

Explain the two caching models in Next.js 16. (Senior signal)

In the default model, a route is static unless it uses request data (cookies(), headers(), searchParams, connection()), fetch is not cached unless I ask, and I control freshness with fetch options, unstable_cache and route settings such as revalidate and dynamic. With cacheComponents: true, the default flips: everything is dynamic unless I cache it with use cache and cacheLife. A page can have a static shell, cached parts and streaming parts (◐ in the build table). The build forces me to handle runtime data with Suspense. It also rejects the old dynamic export, which I confirmed. I would not flip the flag on an old project without reading the migration guide.

In production the page shows an old time or price, but in dev it is fine. Why? (Trap)

In dev, every page is rendered on demand. In production, a page that uses no request data is built once at build time, so new Date() or a plain fetch ran once and is frozen. I proved it: the same timestamp came back two seconds apart, while a force-dynamic page changed every time. The fix depends on what I want. I can make it dynamic (connection(), a request API, or no-store), give it a revalidate time, or move the changing part into a Suspense boundary. And I always test with next build and next start.

What does export const revalidate = 60 do? (Senior signal)

It makes the route static with Incremental Static Regeneration. After 60 seconds, the next request still gets the old copy and starts a rebuild in the background. The request after that gets the new copy. This is stale-while-revalidate. In my test with 5 seconds, I saw x-nextjs-cache go STALE, HIT, STALE, HIT, and the header Cache-Control: s-maxage=5, stale-while-revalidate=.... It is not a timer that rebuilds every 60 seconds. If nobody visits, nothing is rebuilt. For an instant refresh after a change, I use on-demand revalidation (revalidateTag).

What is wrong with redirect() inside a try/catch? (Trap)

redirect() works by throwing a special error. If I catch it, the redirect is swallowed. I tested a page like this:

JavaScript
try {  redirect('/somewhere-else')} catch (e) {  msg = 'caught: ' + (e as Error).message}

The page returned 200 and showed caught: NEXT_REDIRECT. No redirect happened. The same holds for notFound(), which also throws. The fix: call them outside the try, or rethrow the error (the docs have unstable_rethrow for this), or only catch what I expect. A related habit: call redirect() after the work that must happen first, because nothing after it runs.

Why does useSearchParams break a static build, and how do you fix it? (Trap)

A Client Component that reads useSearchParams() needs the query string, which does not exist at build time. If the page is prerendered and the component is not inside a <Suspense> boundary, the build fails. My real error was: useSearchParams() should be wrapped in a suspense boundary at page "/sp". The fix is to wrap that component in <Suspense fallback={...}>, so the rest of the page is built statically and only that part waits for the browser. Or, in a Server Component, I read the searchParams prop, which makes the page dynamic.

Part 3: Mutations, handlers and the proxy
What is a Server Action, and how is it different from a Route Handler? (Must know)

A Server Action is an async function marked 'use server'. I call it from a form or an event handler, and Next.js turns it into a POST endpoint behind the scenes. It is the best fit for changing data from my own UI: it works with forms and progressive enhancement, and I can revalidate caches right after it. A Route Handler (route.ts) is a real HTTP endpoint with a URL and methods. I use it when something else must call me: a webhook, a mobile app, a third party, or when I need a custom response, such as a file or a stream. Both are public doors, and both need their own auth check.

What is proxy.ts, and how is it different from middleware.ts? (Must know)

In Next.js 16, middleware was renamed proxy. The idea is the same: code that runs before a request is handled, so I can redirect, rewrite, change headers or answer directly. The file is proxy.ts at the project root, and it exports a function (default or named proxy) and an optional config.matcher. I tested a file still named middleware.ts. It worked, but the build printed The "middleware" file convention is deprecated. Please use "proxy" instead and suggested a codemod. According to the docs, the proxy uses the Node.js runtime. I keep it small and fast, since it also runs on prefetches.

Are Server Actions secure because the button is only shown to admins? (Trap)

No. A Server Action is a public POST endpoint. I captured the request from my button, and replayed it with curl and no cookie. The unsafe action deleted a document. The real action ID is visible in the page's network traffic. So inside every action I check login, check ownership, validate the arguments (they come from the browser), and return only what the caller needs. Next.js also checks that Origin matches Host for actions: a call with a fake Origin failed with "Invalid Server Actions request". That helps against CSRF, but it does not replace my checks.

Part 4: Auth and security
How would you protect a dashboard end to end? (Senior signal)

I would use a signed session cookie that the server sets in a login Server Action: HttpOnly, Secure, SameSite=Lax, an expiry. A Data Access Layer (a server-only module) has a verifySession() wrapped in React cache(), and every read goes through it and returns small DTOs, not full rows. A proxy.ts does a fast optimistic redirect for logged-out users, and nothing else. Each Server Action and Route Handler calls the same DAL function. For documents, the DAL checks ownership, not just login. If I could, I would use a maintained auth library for the hard parts and keep these rules.

A teammate put the auth check in the dashboard layout. What is wrong? (Trap)

Layouts do not re-render on client-side navigation, so the check does not run for each page change. I tested it: I logged in, removed the cookie, and clicked a <Link> to a sibling page. The layout did not run again and the page showed its data. A full page load did redirect, which is why the bug is easy to miss. I would move the check into the data access function so it runs wherever the data is read. The docs say the same.

Is proxy.ts enough to protect routes? (Trap)

No. It only has the URL and the cookie, so it can only do an optimistic check, and it cannot tell that a document belongs to someone else. Its matcher can also miss routes, and it runs on prefetch requests, so it should not call the database. In my test it did well with bad input: an expired token, a token signed with another secret and an alg: none token were all refused. But I still check again next to the data. If I removed the proxy tomorrow, my data should still be safe.

Where do secrets live, and how do you keep them out of the browser? (Must know)

In server environment variables, with no NEXT_PUBLIC_ prefix. A plain variable is undefined in the browser: I printed it from a Client Component and checked that the build output for the browser held zero copies. I add import 'server-only' to modules that use secrets, so the build fails if a Client Component imports them. I tested that and got an import trace. I keep .env*.local out of git, I never put a secret in a prop to a Client Component, and I do not log them.

Part 5: Rendering, streaming and SEO
What is streaming, and what does loading.tsx do? (Must know)

Streaming means the server sends the HTML in chunks: first the shell (layout, static content and the <Suspense> fallbacks), then each part as it becomes ready. React sends each boundary's HTML with a small script that swaps out the fallback. I measured it: shell at 25 ms, a fast box at 320 ms, a slow box at 1,520 ms, with no content-length header. loading.tsx wraps the page in a Suspense boundary for me, with the file as the fallback. I prefer my own <Suspense> boundaries near the slow parts, so the fast parts show at once.

A page that does not exist returns status 200. How, and how do you fix it? (Trap)

If notFound() is called inside a Suspense boundary, the stream has already started, so the 200 status is already sent. I tested two pages: notFound() before the boundary gave 404, and inside the boundary gave 200 plus <meta name="robots" content="noindex">, which Next.js adds to protect SEO. To get a real 404, I check that the resource exists with a fast query before the first Suspense boundary, then call notFound(). I can also reject early in proxy.ts or with config redirects.

Does streaming hurt SEO? (Trap)

No. Streamed content is server-rendered HTML, so crawlers get it. Two details matter. The status code is fixed once streaming starts (see Q23). And metadata: for HTML-limited bots such as Twitterbot, Next.js waits for generateMetadata and puts the <title> in the <head>. I measured it on a slow dynamic page: for Chrome the first byte came at 32 ms and the title came later in the body, and for Twitterbot the first byte came at about 820 ms with the title inside the head. So I keep generateMetadata fast.

How do you set metadata for a blog, and what traps are there? (Senior signal)

I set metadataBase, a title template and defaults in the root layout. In each page I export generateMetadata, which returns the title, description, canonical and openGraph. Traps: without metadataBase, the build only warns and share images point to localhost. A page's openGraph replaces the layout's whole openGraph (shallow merge), so I lost og:site_name. A canonical set in the root layout would apply to every page. I use cache() so the page and generateMetadata share one query. And I add sitemap.ts, robots.ts and an opengraph-image.tsx, and check the real HTML with curl.

Why use next/image and next/font? (Must know)

next/image serves a size that fits the screen and a smaller format (AVIF or WebP, chosen by the Accept header), lazy-loads, and reserves space with width and height. In my test, a 156 KB PNG came back as 4.5 KB AVIF at 640 px. Remote images need remotePatterns, and the optimizer refused an unknown width, a quality that was not allowed and an unknown host, each with 400. Missing sizes throw in dev but passed in my production build, so I check the dev console. next/font downloads fonts at build time and serves them from my site: the page made no request to Google, and a size-adjusted fallback font reduced layout shift. For the LCP image I use preload or fetchPriority="high". priority is deprecated.

Part 6: Config, deployment and operations
Why is my NEXTPUBLIC variable wrong in production? (Trap)

NEXT_PUBLIC_ values are replaced inside the browser JavaScript at build time. If I built with one value and ran with another, the browser still has the build value. I tested it. I built with https://api.prod.example.com, started the server with a different URL, and the browser files still held the build-time one. The same happens with docker run -e. I fix it by passing the right value as a build argument (one image per environment), or by reading the value on the server at run time and passing it to the browser, or by serving it from a small API. I also remember that dynamic lookups like process.env[name] are not inlined (from the docs).

How do you deploy a Next.js app yourself, without a platform? (Senior signal)

Three ways. next build then next start on any Node server, ideally behind a reverse proxy. The standalone output: a small folder with a server.js that I start with node, and I copy public and .next/static next to it. In my lab it went from a 402 MB node_modules to 67 MB. Or a Docker image, with three stages, a non-root user, HOSTNAME=0.0.0.0 and build args for NEXT_PUBLIC_ values. My image was 332 MB over a 238 MB base. Static export is a fourth way for sites that need no server features. I would set up a health route and error reporting through instrumentation.ts.

What changes when you run several instances of the app? (Senior signal)

Three things, from the docs (I did not run it). The default cache is per instance, so revalidateTag on one does not reach the others unless I configure a shared cache handler. All instances need the same NEXT_SERVER_ACTIONS_ENCRYPTION_KEY, or an action from one instance fails on another with "Failed to find Server Action". And rolling deploys need a deploymentId for version-skew protection. Also, any proxy or CDN in front must not buffer the response, or streaming looks like it is broken. For nginx, the header X-Accel-Buffering: no fixes that.

A page is slow or shows stale data in production. How do you find the cause? (Senior signal)

I go step by step with tools that Next.js gives me. First next build and the route table: is the route ○, ●, ƒ or ◐? Then curl -i: Cache-Control and x-nextjs-cache (HIT, MISS, STALE) tell me what the server thinks. Then the chunk timing: with curl -N or a short script I see when the shell and each Suspense part arrive, and whether a proxy buffers them. Then I check what is fetched in what order, looking for waterfalls and for the same query twice. For stale data, I ask whether the page is static, whether a revalidate or a tag is set, and whether the cache is per instance. I never test caching in next dev.

Rapid fire: one-line answers

Interviewers sometimes ask short questions. These are 10-second answers.

QuestionAnswer
What are the three symbols in the build table?○ static, ● static with generateStaticParams, ƒ dynamic. With Cache Components also ◐ partial prerender.
Which file makes an API endpoint?route.ts. It cannot live next to a page.tsx in the same folder.
How do I read cookies on the server?await cookies() from next/headers. It is async.
Which hook reads the current path in the browser?usePathname() in a Client Component.
Does <Link> reload the page?No. It navigates on the client and prefetches in the background.
Where do I put <html> and <body>?In the root layout, app/layout.tsx. It is required.
How do I make a page dynamic on purpose?await connection(), read a request API, or export const dynamic = 'force-dynamic' (not with Cache Components).
What does server-only do?Makes the build fail if a Client Component imports that module.
How do I refresh cached data after a mutation?revalidatePath, revalidateTag or updateTag, called in the Server Action.
What replaces getServerSideProps?A Server Component that reads data, on a dynamic route.
Is middleware.ts still valid?It works but is deprecated in favor of proxy.ts.
Can use cache read cookies()?Not in a plain use cache. Read the value outside and pass it in, or use use cache: private.
What does priority on <Image> do in 16?Deprecated. Use preload, loading="eager" or fetchPriority="high".
Why does error.tsx need 'use client'?Error boundaries must be Client Components.

Try it: a mock interview

Do this alone, out loud. Time yourself, one minute per answer.

  1. Draw the folder tree for a blog with a home page, a post page, and a login-protected /admin. Which files are Server Components?
  2. Explain what ○, ● and ƒ mean, then make a page ƒ in three different ways.
  3. Explain, with the word "throws", why this code does not redirect: try { redirect('/x') } catch {}.
  4. You find a Server Action deleteOrder(id) that checks only that the user is logged in. What is the bug? How do you fix it? How would you prove it with curl?
  5. Your page has a slow recommendations box. Show how you would stream it, and say what the user sees at 50 ms and at 1.5 seconds.
  6. A product page that does not exist returns 200. Find two ways it can happen and one fix.
  7. You deploy one Docker image to staging and production. Staging works. Production calls the staging API from the browser. Why? Give two fixes.
  8. Look at your own project's next build output. Pick one route and explain its symbol, its Cache-Control and what a CDN may do with it.

Answers

  1. app/page.tsx, app/blog/[slug]/page.tsx, app/login/page.tsx, app/admin/page.tsx, a root layout.tsx, and app/lib/dal.ts. All pages are Server Components unless they have 'use client'. Only the login form and other interactive parts need to be Client Components. The admin check goes in the DAL, with a proxy.ts redirect as an extra.
  2. ○ built at build time, ● also built at build time for the listed params, ƒ built per request. Dynamic: await connection(), reading cookies() or searchParams, or export const dynamic = 'force-dynamic'.
  3. redirect() works by throwing a special error that Next.js catches higher up. A catch that does not rethrow stops it, so the function continues and no redirect happens. Call it outside the try, or rethrow.
  4. The bug is a missing ownership check (IDOR): any logged-in user can delete any order. The fix is to check inside the action that the order belongs to the session user, and validate id. Prove it by capturing the next-action request in DevTools and replaying it with curl and another user's cookie.
  5. Put the box in its own <Suspense fallback={<Skeleton />}>. At 50 ms the user sees the page shell with a skeleton. At 1.5 seconds the real box replaces it. The rest of the page is not delayed.
  6. notFound() inside a Suspense boundary (status already 200), or a page that renders an empty state without calling notFound(). Fix: check existence before the first boundary and call notFound() there.
  7. NEXT_PUBLIC_ values are frozen into the JavaScript at build time, and the image was built for staging. Fix: build one image per environment with a different build arg, or read the URL on the server at run time and pass it to the browser.
  8. Use your own route. For a static route: ○, Cache-Control: s-maxage=31536000, and a CDN may keep it. For a dynamic route: ƒ, private, no-cache, no-store, and a CDN must not keep it.

Key takeaways

  • Answer in the shape: definition, where and when it runs, the trade-off, and how you would check it.
  • Server Components are the default. 'use client' still renders on the server first. Props that cross the boundary must be plain data. Functions and useState fail with clear build errors.
  • fetch is not cached by default in Next.js 16, but a page without request data is still prerendered, so a plain fetch can be frozen at build time.
  • redirect() and notFound() throw. Do not catch them by accident.
  • Server Actions and Route Handlers are public doors. Check login, ownership and arguments in each. The proxy is a filter, and a layout check is skipped on client navigation.
  • Streaming sends the shell first. After the first chunk, the status code is fixed, so decide 404 before the first Suspense boundary.
  • Metadata merges shallowly. Set metadataBase. Keep generateMetadata fast. Use cache() to share data.
  • NEXT_PUBLIC_ variables are frozen at build time. Use server-only for secrets.
  • Standalone and Docker deployments are simple, but with several instances you need a shared cache, one encryption key and a deployment ID.
  • Debug with the route table, curl -i, and chunk timing. Never test caching in dev.

What's next

This is the last lesson of the Next.js series. To keep going, read the full-stack set of Next.js interview questions for more migration and testing questions, and Patterns, Server Components and Testing React for the React ideas under it all. Then build something small and real: a login, one protected page, one Server Action with an ownership check, a streamed slow box, and a Docker image. Run each of the traps in this lesson yourself, and you will answer from experience. If you want to revisit the beginning, start again at Next.js From Zero.

Published by Jainik · Last updated · How these lessons are made

Keep learning

Related lessons

Part of the study guide

Related series