Errors
Every error in this catalogue is produced by running the code that causes it, against the vendored Next.js, and the page shows what came back.
19 entries, each a real run. The messages the panel expected were written down before the run, and the build refuses to write this page if one of them did not appear. Where a mistake is a build error, the panel shows the block of the log that belongs to it. The lessons link here when they meet an error.
One common error is missing: a hydration mismatch, when the HTML the server sent differs from what the browser renders. It can only be seen in a browser, and nothing in this course opens one, so it was not run for Next.js. The React course ran one in a browser: hydration mismatch in the React catalogue.
- 1. A server-only module imported from a client component
- 2. A hook in a server component
- 3. metadata exported from a client file
- 4. A value exported from a "use server" file
- 5. "use cache" without the cacheComponents flag
- 6. middleware.js and proxy.js in the same project
- 7. Reading cookies outside Suspense with Cache Components
- 8. Route segment config that Cache Components rejects
- 9. cookies() used without await
- 10. params read without await
- 11. A function passed from a server component
- 12. window in a server component
- 13. next/image without a width
- 14. dynamic = 'error' on a page that reads cookies
- 15. An invalid revalidate value
- 16. next/router in the App Router
- 17. A default export in a route handler
- 18. A Server Action id the server does not know
- 19. A server variable read in a client component
Builds that fail
next build exits with status 1 and nothing is served. Five of these entries are separate mistakes in one project, found by a single build, so the panels show only the block of the log that belongs to each. The other three are separate projects. The lines that start with at are left out of these extracts.
First, the whole picture of the five-mistake build: how many errors it counted.
What next build printed (Next 16.3.1), then the checks, when the build ran it
next build
> Build error occurred Error: Turbopack build failed with 7 errors: ok the log shows "Error: Turbopack build failed with 7 errors:" ok the log shows "> Build error occurred"
The build printed more than is shown. This panel shows the part of its log that holds > Build error occurred, without its stack-frame lines (the ones that start with at). The checks below it were made against the whole log.
Five mistakes were made, and the build counted seven errors, because two of the mistakes were reported more than once. The order the errors are listed in is Turbopack's own, so each entry below shows its own block.
1 A server-only module imported from a client component
next build failsThe mistake. secret.js begins with import 'server-only'. Panel.js, a client component, imports it, and a server page renders the panel.
app/server-only/secret.js
import 'server-only';
export const key = 'sk-not-for-browsers';
app/server-only/Panel.js
'use client';
import { key } from './secret.js';
export default function Panel() {
return <p>{key}</p>;
}
app/server-only/page.js
import Panel from './Panel.js';
export default function Page() {
return <Panel />;
}
What next build printed (Next 16.3.1), then the checks, when the build ran it
next build
./app/server-only/secret.js:1:1
Error: You're importing a module that depends on "server-only" into a React Client Component module. This API is only available in Server Components but one of its parents is marked with "use client", so this module is also a Client Component.
Learn more: https://nextjs.org/docs/app/building-your-application/rendering
> 1 | import 'server-only';
| ^^^^^^^^^^^^^^^^^^^^^
2 | export const key = 'sk-not-for-browsers';
3 |
Ecmascript file had an error
Import traces:
Client Component Browser:
./app/server-only/secret.js [Client Component Browser]
./app/server-only/Panel.js [Client Component Browser]
./app/server-only/Panel.js [Server Component]
./app/server-only/page.js [Server Component]
Client Component SSR:
./app/server-only/secret.js [Client Component SSR]
./app/server-only/Panel.js [Client Component SSR]
./app/server-only/Panel.js [Server Component]
./app/server-only/page.js [Server Component]
ok the log shows "You're importing a module that depends on \"server-only\" into a React Client Component module"
ok the log shows "'server-only' cannot be imported from a Client Component module"
The build printed more than is shown. This panel shows the part of its log that holds You're importing a module that depends on "server-only", without its stack-frame lines (the ones that start with at). The checks below it were made against the whole log.
What it means. The server-only import is a marker. Its job is to make the build fail if the module could ever be sent to a browser, and a client component's imports are sent to the browser. The log held two messages for this one mistake (the panel's checks found both) and an import trace that walks from the page to the module.
The fix. Keep the code that uses the secret on the server: import it in a server component, and give the client component only the result, as a prop (lesson 10).
2 A hook in a server component
next build failsThe mistake. page.js has no 'use client' directive and calls useState.
app/hook/page.js
import { useState } from 'react';
export default function Page() {
const [count] = useState(0);
return <p>{count}</p>;
}
What next build printed (Next 16.3.1), then the checks, when the build ran it
next build
./app/hook/page.js:1:10
Error: 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.
Learn more: https://nextjs.org/docs/app/api-reference/directives/use-client
> 1 | import { useState } from 'react';
| ^^^^^^^^
2 | export default function Page() {
3 | const [count] = useState(0);
4 | return <p>{count}</p>;
Ecmascript file had an error
ok the log shows "You're importing a module that depends on `useState` into a React Server Component module"
The build printed more than is shown. This panel shows the part of its log that holds You're importing a module that depends on `useState`, without its stack-frame lines (the ones that start with at). The checks below it were made against the whole log.
What it means. Components are server components unless a file or one of its parents is marked, and a hook such as useState works only in a client component, as the message says. The message points at the import, not at the call.
The fix. Put the state in a small component in a file that begins with 'use client' and render it from the server page (lesson 9).
3 metadata exported from a client file
next build failsThe mistake. page.js begins with 'use client' and also exports metadata.
app/client-metadata/page.js
'use client';
export const metadata = { title: 'Nope' };
export default function Page() {
return <p>hello</p>;
}
What next build printed (Next 16.3.1), then the checks, when the build ran it
next build
./app/client-metadata/page.js:2:14
Error: You are attempting to export "metadata" from a component marked with "use client", which is disallowed. "metadata" must be resolved on the server before the page component is rendered. Keep your page as a Server Component and move Client Component logic to a separate file. Read more: https://nextjs.org/docs/app/api-reference/functions/generate-metadata#why-generatemetadata-is-server-component-only
1 | 'use client';
> 2 | export const metadata = { title: 'Nope' };
| ^^^^^^^^
3 | export default function Page() {
4 | return <p>hello</p>;
5 | }
Ecmascript file had an error
Import traces:
Client Component Browser:
./app/client-metadata/page.js [Client Component Browser]
./app/client-metadata/page.js [Server Component]
Client Component SSR:
./app/client-metadata/page.js [Client Component SSR]
./app/client-metadata/page.js [Server Component]
ok the log shows "You are attempting to export \"metadata\" from a component marked with \"use client\", which is disallowed."
The build printed more than is shown. This panel shows the part of its log that holds You are attempting to export "metadata" from a component marked with "use client", without its stack-frame lines (the ones that start with at). The checks below it were made against the whole log.
What it means. Metadata is turned into tags on the server before the page renders, so the file that exports it has to be a server file. The message says to keep the page a server component and move the client logic into a file of its own.
The fix. Remove the directive from page.js, move whatever needed the browser to a client component, and keep metadata (or generateMetadata) in the page (lesson 23).
4 A value exported from a "use server" file
next build failsThe mistake. actions.js begins with 'use server', exports an async function and also exports export const limit = 10.
app/not-async/actions.js
'use server';
export const limit = 10;
export async function save() {}
What next build printed (Next 16.3.1), then the checks, when the build ran it
next build
./app/not-async/actions.js:2:1
Error: Only async functions are allowed to be exported in a "use server" file.
1 | 'use server';
> 2 | export const limit = 10;
| ^^^^^^^^^^^^^^^^^^^^^^^^
3 | export async function save() {}
4 |
Ecmascript file had an error
Import trace:
Server Component:
./app/not-async/actions.js
./app/not-async/page.js
ok the log shows "Only async functions are allowed to be exported in a \"use server\" file."
ok the log shows "Export save doesn't exist in target module"
The build printed more than is shown. This panel shows the part of its log that holds Only async functions are allowed to be exported in a "use server" file., without its stack-frame lines (the ones that start with at). The checks below it were made against the whole log.
What it means. Every export of a 'use server' file becomes an address a browser can post to, so each one has to be an async function. The same build also reported, for the page that imports save, that the export does not exist in the module; the panel's checks found that message in the log too.
The fix. Move the constant to another file and import it where it is needed; keep only async functions in the action file (lesson 17).
5 "use cache" without the cacheComponents flag
next build failsThe mistake. page.js has a function that begins with 'use cache', in a project whose next.config.mjs does not set cacheComponents.
app/use-cache/page.js
async function getTitle() {
'use cache';
return 'cached';
}
export default async function Page() {
return <h1>{await getTitle()}</h1>;
}
What next build printed (Next 16.3.1), then the checks, when the build ran it
next build
./app/use-cache/page.js:2:3
Error: To use "use cache", please enable the feature flag `cacheComponents` in your Next.js config.
Read more: https://nextjs.org/docs/app/api-reference/directives/use-cache#usage
1 | async function getTitle() {
> 2 | 'use cache';
| ^^^^^^^^^^^
3 | return 'cached';
4 | }
5 | export default async function Page() {
Ecmascript file had an error
ok the log shows "To use \"use cache\", please enable the feature flag `cacheComponents` in your Next.js config."
The build printed more than is shown. This panel shows the part of its log that holds To use "use cache", please enable the feature flag, without its stack-frame lines (the ones that start with at). The checks below it were made against the whole log.
What it means. The directive belongs to Cache Components, which is off by default in this version, so the compiler refuses it instead of ignoring it.
The fix. Set cacheComponents: true in the configuration, knowing that it changes how the whole project is cached (lesson 16), or leave the directive out.
6 middleware.js and proxy.js in the same project
next build failsThe mistake. The project has both middleware.js, the old name, and proxy.js.
proxy.js
import { NextResponse } from 'next/server';
export function proxy() {
return NextResponse.next();
}
middleware.js
import { NextResponse } from 'next/server';
export function middleware() {
return NextResponse.next();
}
What next build printed (Next 16.3.1), then the checks, when the build ran it
next build
▲ Next.js 16.3.1 (Turbopack)
✓ Running next.config.mjs took <t>
> Build error occurred
Error: Both middleware file "./middleware.js" and proxy file "./proxy.js" are detected. Please use "./proxy.js" only. Learn more: https://nextjs.org/docs/messages/middleware-to-proxy
at ignore-listed frames
ok the log shows "Both middleware file \"./middleware.js\" and proxy file \"./proxy.js\" are detected"
What it means. The proxy replaced the middleware in version 16 and a project may have one of them. The build stops at the first problem, before it compiles anything, so there is no code frame: the message names both files.
The fix. Keep proxy.js, rename the exported function to proxy, and delete middleware.js (lesson 20).
7 Reading cookies outside Suspense with Cache Components
next build failsThe mistake. With cacheComponents: true, the home page awaits cookies() directly, with no <Suspense> around it.
next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
cacheComponents: true,
};
export default nextConfig;
app/page.js
import { cookies } from 'next/headers';
export default async function Home() {
const jar = await cookies();
return <h1>Theme: {jar.get('theme')?.value ?? 'none'}</h1>;
}
What next build printed (Next 16.3.1), then the checks, when the build ran it
next build
Error: Route "/": Next.js encountered uncached or runtime data during prerendering.
`fetch(...)`, `cookies()`, `headers()`, `params`, `searchParams`, or `connection()` accessed outside of `<Suspense>` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.
Ways to fix this:
- [stream] Provide a placeholder with `<Suspense fallback={...}>` around the data access
- [cache] For uncached data (`fetch`, database calls): cache the access with `"use cache"` (does not apply to `connection()`)
- [block] Set `export const instant = false` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-prerender-dynamic
To get a more detailed stack trace and pinpoint the issue, try one of the following:
- Start the app in development mode by running `next dev`, then open "/" in your browser to investigate the error.
- Rerun the production build with `next build --debug-prerender` to generate better stack traces.
Error occurred prerendering page "/". Read more: https://nextjs.org/docs/messages/prerender-error
Export encountered an error on /page: /, exiting the build.
⨯ Next.js build worker exited with code: 1 and signal: null
ok the log shows "Route \"/\": Next.js encountered uncached or runtime data during prerendering."
ok the log shows "[stream] Provide a placeholder with `<Suspense fallback={...}>`"
ok the log shows "[cache] For uncached data"
ok the log shows "[block] Set `export const instant = false`"
ok the log shows "Error occurred prerendering page \"/\"."
The build printed more than is shown. This panel shows the part of its log that holds Error: Route "/": Next.js encountered uncached or runtime data during prerendering., without its stack-frame lines (the ones that start with at). The checks below it were made against the whole log.
What it means. This error does not come from the compiler. The code compiled, and then the build rendered the page and found that it needed data that only exists per request. It names the route, lists the three ways out, and ends with the line that names the page it was prerendering. The stack frames were left out of the panel.
The fix. Pick one of the three the message lists: wrap the part that reads the cookie in <Suspense> with a fallback so the rest is prerendered; cache the access with "use cache"; or allow a blocking route. Streaming is the one lesson 12 and lesson 16 describe.
8 Route segment config that Cache Components rejects
next build failsThe mistake. With cacheComponents: true, page.js exports dynamic = 'force-dynamic'.
app/page.js
export const dynamic = 'force-dynamic';
export default function Home() {
return <h1>Home</h1>;
}
What next build printed (Next 16.3.1), then the checks, when the build ran it
next build
./app/page.js:1:14
Error: Route segment config "dynamic" is not compatible with `nextConfig.cacheComponents`. Please remove it.
> 1 | export const dynamic = 'force-dynamic';
| ^^^^^^^
2 | export default function Home() {
3 | return <h1>Home</h1>;
4 | }
Ecmascript file had an error
ok the log shows "Route segment config \"dynamic\" is not compatible with `nextConfig.cacheComponents`. Please remove it."
The build printed more than is shown. This panel shows the part of its log that holds Route segment config "dynamic" is not compatible, without its stack-frame lines (the ones that start with at). The checks below it were made against the whole log.
What it means. Under Cache Components the old route segment options that decided static against dynamic are gone: what is dynamic is decided by what the code reads. The message asks you to remove the export. (The same build is the one lesson 29 reads from top to bottom.)
The fix. Delete the export. To make part of a page per-request, read the request data inside <Suspense>; to keep a part static, cache it.
Pages that fail, in next dev
One route per mistake, requested in one next dev session. The panel shows the answer's status and what the development server printed for that request, as printed, including the code frame with the line marked. Request lines are the server's, with the durations masked.
9 cookies() used without await
next devThe mistake. The page calls cookies().get('theme') and does not await cookies().
app/err/cookies-sync/page.js
import { cookies } from 'next/headers';
export default function Page() {
const theme = cookies().get('theme');
return <p>{theme ? theme.value : 'no theme'}</p>;
}
cookies.mjs
const res = await fetch(process.env.BASE_URL + '/err/cookies-sync');
console.log('GET /err/cookies-sync ->', res.status);
check('the page answers 500', res.status, 500);
What the driver printed (Next 16.3.1, next dev), when the build ran it
node cookies.mjs
GET /err/cookies-sync -> 500 ok the page answers 500
What next dev printed meanwhile
Error: Route "/err/cookies-sync" used `cookies().get`. `cookies()` returns a Promise and must be unwrapped with `await` or `React.use()` before accessing its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis
at Page (app/err/cookies-sync/page.js:3:24)
1 | import { cookies } from 'next/headers';
2 | export default function Page() {
> 3 | const theme = cookies().get('theme');
| ^
4 | return <p>{theme ? theme.value : 'no theme'}</p>;
5 | }
6 |
⨯ TypeError: {imported module ./nodemodules/next/headers.js}.cookies(...).get is not a function
at Page (app/err/cookies-sync/page.js:3:27)
1 | import { cookies } from 'next/headers';
2 | export default function Page() {
> 3 | const theme = cookies().get('theme');
| ^
4 | return <p>{theme ? theme.value : 'no theme'}</p>;
5 | }
6 | {
digest: '<digest>'
}
GET /err/cookies-sync 500 in <t> (next.js: <t>, application-code: <t>)
The driver ran against next dev -H 127.0.0.1 -p 0 of project dev; BASE_URL is the address Next printed.
What it means. In version 16 cookies(), headers() and the other request functions return Promises. The first line of the log is Next.js recognising the mistake and saying what to do; the second, the TypeError, is what JavaScript then does with it, and that one is what the page answers 500 for.
The fix. const jar = await cookies(), and make the component async (lesson 21).
10 params read without await
next devThe mistake. The page reads params.slug straight from its props.
app/err/params-sync/[slug]/page.js
export default function Page({ params }) {
return <p>{`slug: ${params.slug}`}</p>;
}
params.mjs
const res = await fetch(process.env.BASE_URL + '/err/params-sync/abc');
const text = />(slug: [^<]*)</.exec(await res.text())[1];
console.log('GET /err/params-sync/abc ->', res.status, ' the page says:', text);
check('the page answers 200', res.status, 200);
check('but where the slug should be it says undefined', text, 'slug: undefined');
What the driver printed (Next 16.3.1, next dev), when the build ran it
node params.mjs
GET /err/params-sync/abc -> 200 the page says: slug: undefined ok the page answers 200 ok but where the slug should be it says undefined
What next dev printed meanwhile
Error: Route "/err/params-sync/[slug]" used `params.slug`. `params` is a Promise and must be unwrapped with `await` or `React.use()` before accessing its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis
at Page (app/err/params-sync/[slug]/page.js:2:30)
1 | export default function Page({ params }) {
> 2 | return <p>{`slug: ${params.slug}`}</p>;
| ^
3 | }
4 |
GET /err/params-sync/abc 200 in <t> (next.js: <t>, application-code: <t>)
The driver ran against next dev -H 127.0.0.1 -p 0 of project dev; BASE_URL is the address Next printed.
What it means. This one is the quietest of the set: the page answered 200 and rendered slug: undefined, because a Promise has no property called slug. The log is the only sign. It says that the Promise must be unwrapped before its properties are read.
The fix. const { slug } = await params in an async component (lesson 5).
11 A function passed from a server component
next devThe mistake. Two pages: one gives a <button> an onClick in a server component; the other passes a function prop, onPick, to a client component.
app/err/handler/page.js
export default function Page() {
return <button onClick={() => console.log('clicked')}>Click</button>;
}
app/err/fn-prop/Picker.js
'use client';
export default function Picker({ onPick }) {
return <button onClick={() => onPick('a')}>Pick</button>;
}
app/err/fn-prop/page.js
import Picker from './Picker.js';
export default function Page() {
return <Picker onPick={(value) => console.log(value)} />;
}
handlers.mjs
const base = process.env.BASE_URL;
const handler = await fetch(base + '/err/handler');
const prop = await fetch(base + '/err/fn-prop');
console.log('GET /err/handler ->', handler.status);
console.log('GET /err/fn-prop ->', prop.status);
check('both pages answer 500', [handler.status, prop.status], [500, 500]);
What the driver printed (Next 16.3.1, next dev), when the build ran it
node handlers.mjs
GET /err/handler -> 500 GET /err/fn-prop -> 500 ok both pages answer 500
What next dev printed meanwhile
⨯ Error: Event handlers cannot be passed to Client Component props.
<button onClick={function onClick} children=...>
^^^^^^^^^^^^^^^^^^
If you need interactivity, consider converting part of this to a Client Component.
at ignore-listed frames {
digest: '<digest>'
}
GET /err/handler 500 in <t> (next.js: <t>, application-code: <t>)
⨯ Error: Event handlers cannot be passed to Client Component props.
<... onPick={function onPick}>
^^^^^^^^^^^^^^^^^
If you need interactivity, consider converting part of this to a Client Component.
at ignore-listed frames {
digest: '<digest>'
}
GET /err/fn-prop 500 in <t> (next.js: <t>, application-code: <t>)
The driver ran against next dev -H 127.0.0.1 -p 0 of project dev; BASE_URL is the address Next printed.
What it means. Props that cross from a server component to a client component are serialised, and a function cannot be. The message marks the offending prop with carets. Both variants gave the same message in this version.
The fix. Move the element that needs the handler into a client component and render that from the server (lesson 9), or pass a Server Action, which can cross (lesson 17).
12 window in a server component
next devThe mistake. The page renders window.innerWidth.
app/err/window/page.js
export default function Page() {
return <p>Width: {window.innerWidth}</p>;
}
window.mjs
const res = await fetch(process.env.BASE_URL + '/err/window');
console.log('GET /err/window ->', res.status);
check('the page answers 500', res.status, 500);
What the driver printed (Next 16.3.1, next dev), when the build ran it
node window.mjs
GET /err/window -> 500 ok the page answers 500
What next dev printed meanwhile
⨯ ReferenceError: window is not defined
at Page (app/err/window/page.js:2:21)
1 | export default function Page() {
> 2 | return <p>Width: {window.innerWidth}</p>;
| ^
3 | }
4 | {
digest: '<digest>'
}
GET /err/window 500 in <t> (next.js: <t>, application-code: <t>)
The driver ran against next dev -H 127.0.0.1 -p 0 of project dev; BASE_URL is the address Next printed.
What it means. A server component runs in Node.js, which has no window, document or localStorage. The log shows the line.
The fix. Do browser-only work in a client component, inside an effect, which runs only in the browser (React course).
13 next/image without a width
next devThe mistake. The page renders <Image src="/x.png" alt="" /> with no width and height.
app/err/image/page.js
import Image from 'next/image';
export default function Page() {
return <Image src="/x.png" alt="" />;
}
image.mjs
const res = await fetch(process.env.BASE_URL + '/err/image');
console.log('GET /err/image ->', res.status);
check('the page answers 500', res.status, 500);
What the driver printed (Next 16.3.1, next dev), when the build ran it
node image.mjs
GET /err/image -> 500 ok the page answers 500
What next dev printed meanwhile
⨯ Error: Image with src "/x.png" is missing required "width" property.
at ignore-listed frames {
digest: '<digest>'
}
GET /err/image 500 in <t> (next.js: <t>, application-code: <t>)
The driver ran against next dev -H 127.0.0.1 -p 0 of project dev; BASE_URL is the address Next printed.
What it means. The component needs a size to reserve space before the picture loads (lesson 24), and an image from public/ gives it nothing to read one from.
The fix. Give width and height, or fill inside a sized parent, or import the file so its size is known.
14 dynamic = 'error' on a page that reads cookies
next devThe mistake. The page exports dynamic = 'error', which asks Next.js to fail if the page cannot be static, and then awaits cookies().
app/err/static-error/page.js
import { cookies } from 'next/headers';
export const dynamic = 'error';
export default async function Page() {
const jar = await cookies();
return <p>{jar.size} cookies</p>;
}
static-error.mjs
const res = await fetch(process.env.BASE_URL + '/err/static-error');
console.log('GET /err/static-error ->', res.status);
check('the page answers 500', res.status, 500);
What the driver printed (Next 16.3.1, next dev), when the build ran it
node static-error.mjs
GET /err/static-error -> 500 ok the page answers 500
What next dev printed meanwhile
⨯ Error: Route /err/static-error with `dynamic = "error"` couldn't be rendered statically because it used `cookies()`. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering
at Page (app/err/static-error/page.js:4:28)
2 | export const dynamic = 'error';
3 | export default async function Page() {
> 4 | const jar = await cookies();
| ^
5 | return <p>{jar.size} cookies</p>;
6 | }
7 | {
code: 'NEXT_STATIC_GEN_BAILOUT',
digest: '<digest>'
}
GET /err/static-error 500 in <t> (next.js: <t>, application-code: <t>)
The driver ran against next dev -H 127.0.0.1 -p 0 of project dev; BASE_URL is the address Next printed.
What it means. The option is a promise that a page is static, and reading the request breaks it. The error carries a code, NEXT_STATIC_GEN_BAILOUT, which is how the framework tells this kind of failure from others.
The fix. Either remove the export and accept a per-request page, or take the cookie reading out of the page (lesson 11).
15 An invalid revalidate value
next devThe mistake. The page exports revalidate = 'soon'.
app/err/revalidate/page.js
export const revalidate = 'soon';
export default function Page() {
return <p>revalidate</p>;
}
revalidate.mjs
const res = await fetch(process.env.BASE_URL + '/err/revalidate');
console.log('GET /err/revalidate ->', res.status);
check('the page answers 500', res.status, 500);
What the driver printed (Next 16.3.1, next dev), when the build ran it
node revalidate.mjs
GET /err/revalidate -> 500 ok the page answers 500
What next dev printed meanwhile
⨯ Error: Invalid revalidate value "soon" on "/err/revalidate", must be a non-negative number or false
at ignore-listed frames
⨯ Error: Invalid revalidate value "soon" on "/err/revalidate", must be a non-negative number or false
at ignore-listed frames {
page: '/err/revalidate'
}
GET /err/revalidate 500 in <t> (next.js: <t>, application-code: <t>)
The driver ran against next dev -H 127.0.0.1 -p 0 of project dev; BASE_URL is the address Next printed.
What it means. The option takes a number of seconds or false. A string is rejected when the route is read, and here the message was printed twice.
The fix. Use a number: export const revalidate = 60 (lesson 14).
16 next/router in the App Router
next devThe mistake. A client component imports useRouter from next/router, the Pages Router's.
app/err/old-router/Go.js
'use client';
import { useRouter } from 'next/router';
export default function Go() {
const router = useRouter();
return <button onClick={() => router.push('/')}>Go</button>;
}
app/err/old-router/page.js
import Go from './Go.js';
export default function Page() {
return <Go />;
}
old-router.mjs
const res = await fetch(process.env.BASE_URL + '/err/old-router');
console.log('GET /err/old-router ->', res.status);
check('the page answers 500', res.status, 500);
What the driver printed (Next 16.3.1, next dev), when the build ran it
node old-router.mjs
GET /err/old-router -> 500 ok the page answers 500
What next dev printed meanwhile
⨯ Error: NextRouter was not mounted. https://nextjs.org/docs/messages/next-router-not-mounted
at Go (app/err/old-router/Go.js:4:27)
2 | import { useRouter } from 'next/router';
3 | export default function Go() {
> 4 | const router = useRouter();
| ^
5 | return <button onClick={() => router.push('/')}>Go</button>;
6 | }
7 | {
digest: '<digest>'
}
GET /err/old-router 500 in <t> (next.js: <t>, application-code: <t>)
The driver ran against next dev -H 127.0.0.1 -p 0 of project dev; BASE_URL is the address Next printed.
What it means. The two routers have separate hooks. In next dev the import resolves, and the error comes when the component renders and calls the hook with no Pages Router to talk to.
The fix. Import useRouter from next/navigation (lesson 6).
17 A default export in a route handler
next devThe mistake. route.js has export default function handler(), as a Pages Router API route would.
app/err/default-export/route.js
export default function handler() {
return new Response('hello');
}
default-export.mjs
const res = await fetch(process.env.BASE_URL + '/err/default-export');
console.log('GET /err/default-export ->', res.status);
check('the page answers 405', res.status, 405);
What the driver printed (Next 16.3.1, next dev), when the build ran it
node default-export.mjs
GET /err/default-export -> 405 ok the page answers 405
What next dev printed meanwhile
⨯ Detected default export in '[project]/app/err/default-export/route.js'. Export a named export for each HTTP method instead. ⨯ No HTTP methods exported in '[project]/app/err/default-export/route.js'. Export a named export for each HTTP method. GET /err/default-export 405 in <t> (next.js: <t>, application-code: <t>)
The driver ran against next dev -H 127.0.0.1 -p 0 of project dev; BASE_URL is the address Next printed.
What it means. A route handler exports one function for each HTTP method it answers. A default export is ignored, so the file exports no method at all, and the request gets a 405. Both messages are in the log.
The fix. Name the export after the method: export function GET() (lesson 19).
Found while serving
Two that next build accepts and next start serves, one with an error and one with none.
18 A Server Action id the server does not know
next startThe mistake. A POST names an action id that this build never made: a stale form from an older deployment, or a made-up one.
unknown-action.mjs
const base = process.env.BASE_URL;
const body = new FormData();
body.append('$ACTION_ID_' + '0'.repeat(40), '');
const res = await fetch(base + '/actions', { method: 'POST', body, headers: { origin: base } });
console.log('POST with an unknown action id ->', res.status);
check('the server answers 500', res.status, 500);
What the driver printed (Next 16.3.1, next build + next start), when the build ran it
node unknown-action.mjs
POST with an unknown action id -> 500 ok the server answers 500
What next start printed meanwhile
⨯ Error: Failed to find Server Action. This request might be from an older or newer deployment.
Read more: https://nextjs.org/docs/messages/failed-to-find-server-action
at ignore-listed frames
⨯ Error: Failed to find Server Action. This request might be from an older or newer deployment.
Read more: https://nextjs.org/docs/messages/failed-to-find-server-action
at ignore-listed frames
The driver ran against next start -H 127.0.0.1 -p 0 of project app, right after its next build; BASE_URL is the address Next printed.
What it means. Action ids are made by the build, so a form rendered by one build can be posted after the next one is deployed. The message names that cause. An id the server has never had is treated the same way, which is also why guessing ids gets an attacker nothing (and why actions still check the session themselves, lesson 27).
The fix. For users with an old page open, a reload is enough; teams that deploy often keep the old build's actions available for a while. In your own code there is nothing to change.
19 A server variable read in a client component
next startThe mistake. Leaky.js is a client component and renders process.env.APP_GREETING, a variable without the NEXT_PUBLIC_ prefix.
app/env/leak/Leaky.js
'use client';
export default function Leaky() {
// APP_GREETING has no NEXT_PUBLIC_ prefix, so nothing replaces this expression in the code sent to browsers.
return <p data-leaky>{`APP_GREETING is ${String(process.env.APP_GREETING)}`}</p>;
}
app/env/leak/page.js
import Leaky from './Leaky.js';
export default function Page() {
return (
<main>
<h1>A server variable in a client component</h1>
<Leaky />
</main>
);
}
leak.mjs
import { readdirSync, readFileSync } from 'node:fs';
const base = process.env.BASE_URL;
const html = await (await fetch(base + '/env/leak')).text();
const rendered = /<p data-leaky[^>]*>([^<]*)<\/p>/.exec(html)[1];
const scripts = readdirSync(process.env.APP_DIR + '/.next/static/chunks', { withFileTypes: true })
.filter((entry) => entry.isFile() && entry.name.endsWith('.js'))
.map((entry) => readFileSync(entry.parentPath + '/' + entry.name, 'utf8'));
const mentionsName = scripts.some((text) => text.includes('APP_GREETING'));
const holdsValue = scripts.some((text) => text.includes('hello from .env'));
console.log('the server rendered:', rendered);
console.log('a script sent to browsers mentions the variable:', mentionsName);
console.log('a script sent to browsers holds its value:', holdsValue);
check('the server rendered the value, because the server has the real environment', rendered, 'APP_GREETING is hello from .env');
check('the scripts sent to browsers refer to the variable', mentionsName, true);
check('and none of them holds its value', holdsValue, false);
What the driver printed (Next 16.3.1, next build + next start), when the build ran it
node leak.mjs
the server rendered: APP_GREETING is hello from .env a script sent to browsers mentions the variable: true a script sent to browsers holds its value: false ok the server rendered the value, because the server has the real environment ok the scripts sent to browsers refer to the variable ok and none of them holds its value
The driver ran against next start -H 127.0.0.1 -p 0 of project app, right after its next build; BASE_URL is the address Next printed.
What it means. There is no error anywhere, which is what makes this one worth knowing. The server renders the component too, with the real environment, so the HTML has the value. Only NEXT_PUBLIC_ variables are written into the code sent to the browser at build time (lesson 22). The panel shows both halves: the value in the HTML, and in no script, though a script does name the variable. What a browser then renders was not run, since no panel here has one. Because no script holds the value, the browser cannot render the text the server sent, and React would report that difference as a hydration mismatch.
The fix. Read the variable in a server component and pass it down as a prop, or, if it is meant to be public, rename it with the NEXT_PUBLIC_ prefix and build again.