React
Common App Router build errors and their cause
Five recurring App Router build errors, the real cause and the fix: window is not defined, hooks without use client, hydration.
Key points
- Three causes cover nearly everything: Node APIs in the client, hooks without the directive, a function passed to a client component.
- The build stops at the first broken boundary: read the first error, not the last.
- None of these errors reproduces the same way in development.
App Router build errors almost always have three causes: a Node module imported into the browser, a hook inside a server component, a non-serialisable value passed to a client component. The message names the file, the cause is at the boundary.
The five you see most
| Message | Cause | Fix |
|---|---|---|
window is not defined | A browser module evaluated during server render | useEffect or dynamic with ssr: false |
Module not found: Can't resolve 'fs' | Node API inside a client component | Move the read into a server component or route.ts |
You're importing a component that needs useState | Hook in a file without "use client" | Add the directive on the lowest component |
Event handlers cannot be passed to Client Component props | Function passed from server to client | Pass data, or use a Server Action |
Hydration failed because the server rendered HTML didn't match the client | Markup differs between server and client | Make the first render deterministic |
The first one: window is not defined
The module that touches window is evaluated when the server imports the component. The fix is to move the access into useEffect, or to load the component on the client only. The full diagnosis is in window is not defined in Next.js.
The third one: the directive in the wrong place
"use client" applies to the file and everything it imports. At the top of the root layout it makes the whole tree a client tree; on a leaf component it keeps the boundary narrow. The map of the two worlds is in server-only and client-only.
# riproduci la build prima del push
npx tsc --noEmit && npm run build 2>&1 | tail -n 40
The fifth one: hydration
Server markup and client markup must agree on the first render. Date.now(), Math.random(), localStorage and the browser timezone all produce differences. The fix is an identical first render, with the update inside useEffect. The cases are in hydration failed: initial UI mismatch.
On a catalogue of 111,985 components and 1,556 blocks, measured on 1 November 2026 on ui.eneawork.it, the build treats every page as a boundary: one error inside a single client component stops the whole deploy. There is no partial build.
Where to look in the code
Three records that help when the error happens at runtime rather than at build time: error pages, error handling patterns, and form blocks when the error starts in a handler.
A 404 after a deploy is not a build error, but the two are easy to confuse: the sequence is in 404 after deploy. The choice between action and route that prevents half of these errors is in Route Handler or Server Action. The framework comparison with the same build tree is in Astro vs Next.js.
The limit to budget for
None of these errors reproduces in development with the same evaluation order as the build. A local npm run build before the push is the only test that catches them early.
Frequently asked questions
Why does the build fail when development works?
Because the build evaluates every module under stricter rules and without <code>window</code>. A module that works in the browser may make no sense on the server: the boundary is the problem, not the command.
Does typeof window fix it?
It is a patch: <code>typeof window</code> avoids the error but not the markup mismatch between server and client. Prefer <code>useEffect</code> or <code>dynamic</code> with <code>ssr: false</code>.
Where does "use client" go?
On the lowest component that holds state, events or hooks. Putting it high turns the whole subtree into client code and you lose server rendering.
Can I pass a function to a client component?
No, functions are not serialisable. Pass data and handle the action with a Server Action, or define the function inside the client component.
How do I avoid hydration errors?
Make the first render identical on both sides: no current date, no random values, no <code>localStorage</code> access during render. Update after mount.