Troubleshooting
Common symptoms, their causes and their fixes. To look up an error, search this page for its wording.
On this page
this application wants mode: 'server'
Cause
Under static mode, the application uses something that needs a request. The first line of the error says what kind, and the lines below name the files.
Fix
If the application needs it, change the mode of framework() to 'server'. Remove paths, site and csp, which only static mode accepts. Under server mode, write the CSP as a header in guard.ts, using nonce(). If only types are used, change the import to import type, which static mode lets through. If the application does not need it, remove that code. A GET form, such as a search or a filter, needs no Server Action.
static build needs pathnames for …
Cause
Under static mode, no URL in paths fits a route with parameters. A build cannot choose the values itself.
Fix
List that route’s URLs with the paths option of framework().
the "paths" option supplied pathnames no route wants
Cause
Under static mode, a URL that paths returned matches no route. Most often it is a typo.
Fix
Fix the URLs the error lists, or drop them from paths.
the "paths" option supplied pathnames that still hold a parameter, …
Cause
Under static mode, a path paths returned still holds a parameter such as :slug. It often happens when locales.paths expanded the locale alone. Only a page with a fallback.tsx beside it takes a path with a parameter left in it.
Fix
Expand the remaining parameter into values inside paths. If the page should answer values the build did not write, give it a fallback.tsx. A route.ts or a redirect.ts cannot have one.
… shell locations whose parameter names are not their pattern's
Cause
A shell location names a parameter differently from its pattern, as /en/posts/:slug does for /:locale/posts/:id.
Fix
Use the names of the pattern in the parentheses.
static build needs shell locations for …
Cause
A layout above the fallback.tsx receives a parameter the shell location leaves open. The layout is written as HTML in the shell too, so it needs that value. Returning a location that keeps :locale gives … leave out a parameter a layout above their fallback.tsx receives instead.
Fix
Return locations with the layout’s parameters filled, such as /ja/posts/:id. Keeping what locales.paths returns gives one per locale.
… with a segment named !fallback
Cause
The build writes shells into directories named !fallback, so no path paths returns may hold that segment, escaped as %21fallback or not.
Fix
Rename the value, or drop it from paths.
the shell for … would also answer …
Cause
A shell’s rewrite rule would also match URLs of a route the route table tries first, and the host would answer those with the shell.
Fix
If that route is a page, give it a fallback.tsx too. If only one URL overlaps, list it in paths. Otherwise, return shell locations that route cannot match.
… shell locations a params schema refused
Cause
A layout’s paramsSchema rejected a value a shell location fills in, often a locale outside the set, as in /fr/posts/:id.
Fix
Drop that location from paths, or correct the value.
the shell for … called notFound() while it rendered
Cause
A layout or a Server Component in fallback.tsx called notFound() while the build rendered the shell. A shell has no value, so there is nothing to look up yet.
Fix
Call notFound() from the Client Component that reads the value in the browser.
the shell for … was answered by …
Cause
Another route answered the shell’s URL (/posts/!fallback): a page that took !fallback as a value, or a page tried earlier in the route table with a fallback.tsx of its own and a differently named parameter in the same place.
Fix
Return shell locations that route cannot match.
static build could not render …
Cause
Under static mode, a Server Component threw while the build rendered the page. When a Client Component with no Suspense boundary above it throws, the build stops with the same message.
Fix
The original error is logged above this message, beside the page’s URL. Start from there.
Pages that render only in vite dev
Cause
Under static mode too, vite dev writes no files and renders each page per request. A value paths does not list is rendered by its page, unless a fallback.tsx answers it. When a Server Component throws, it responds with a 500 and keeps running.
Fix
Run vite build before you ship, and follow the message it stops with. Check the site it wrote with vite preview, which serves only the files in dist/client/, as a host does.
404.html served with a 200
Cause
Under static mode, the status that goes with 404.html is the host’s to decide. A build can write the page, but not the response.
Fix
Configure the host to serve 404.html with a 404 status for unknown URLs.
Values the build did not write are 404 on GitHub Pages
Cause
GitHub Pages has no rewrites and does not read _redirects, so a URL with no file gets 404.html.
Fix
Move to a host that rewrites, or list every value in paths.
Written pages answered with the shell on Cloudflare
Cause
Cloudflare reads at most 2,000 _redirects rules for fixed URLs. Past that it skips the rest, and the shell rules catch written pages too. The build warns with _redirects has … rules Cloudflare counts as static. Rules with parameters stop at 100, with the warning … counts as dynamic.
Fix
Write fewer values under patterns with a shell, and leave the rest to the shell. Netlify serves files first, so it never answers a written page with a shell.
Redirect loops on Cloudflare
Cause
Workers static assets run with html_handling set to drop-trailing-slash. The shell rules rewrite to a URL ending in a slash, and the redirect that drops the slash sends the visitor round again. Under none, the shell is a 404.
Fix
Set html_handling to the default auto-trailing-slash, or to force-trailing-slash.
_redirects cannot list …
Cause
A value the build wrote holds : followed by a letter, as in :b, or *. Hosts read those as placeholders, so no rule can answer that URL with its file, and on Cloudflare the shell answers it.
Fix
Use values whose URLs hold no : or *.
public/_redirects line … before its shell's rule
Cause
A rule in public/_redirects, such as /* /index.html 200, matches a shell location before the shell’s own rule does.
Fix
Remove that rule, or narrow it so it misses the shell locations.
this build runs on Vercel, which does not read _redirects
Cause
A build on Vercel wrote shells without vercel(). Vercel does not read _redirects, so no URL is ever answered with a shell.
Fix
Add vercel() from @k8ordo/framework/vercel beside framework().
… has "!fallback" in its HTML
Cause
Layouts receive the shell’s URL, /posts/!fallback, as pathname. Written into a link or a canonical URL, it leaves the HTML with something other than the visitor’s URL.
Fix
Render anything made from the URL in a Client Component that reads usePathname() in the browser.
the "csp" option cannot go into a page's <meta> as it is
Cause
Under static mode, the csp option holds something a <meta> cannot carry, such as a directive a <meta> ignores or 'strict-dynamic'. The directives a <meta> ignores are frame-ancestors, report-uri and sandbox. An entry with several sources separated by spaces gets the same error.
Fix
Set the directives a <meta> ignores as headers at the host. Drop 'strict-dynamic': the framework’s module script is allowed by 'self'. Give each source its own array entry.
Server Action submissions answered with a 403
Cause
Under server mode, the handler accepts a POST only when its Origin header names the host of the request’s URL. A proxy in front that rewrites Host makes the two disagree.
Fix
Have the proxy pass the original Host on unchanged. If you call the handler yourself, build the Request from the URL the visitor requested.
No redirect after redirect()
Cause
Under server mode, redirect() ends by throwing. Called inside a try, the catch receives that exception.
Fix
Call redirect() outside the try.
… belongs to what answers the request, and a page is a render
Cause
Under server mode, a page calls cookies(), responseHeaders() or requestHeaders() while it renders. These belong to the code that answers the request, not to a render.
Fix
To read the request’s headers or cookies, use the request the page receives. To write, move the code into a guard.ts or a Server Action.
A page that shows only internal error
Cause
Under server mode, the handler could not produce a response, and serve() responded with a 500. A Server Component that throws with no Suspense boundary above it leads here.
Fix
What was thrown is in the server’s log, on a line such as k8ordo: GET /products/1 failed. If the visitor should see a page of your own, add an error.tsx.
Cookies not kept over HTTP
Cause
Under server mode, secure defaults to true in cookies().set(). A browser does not keep a Secure cookie that arrived over HTTP without TLS. The default is false only for HTTP requests to localhost, 127.0.0.1 or [::1].
Fix
Serve production over HTTPS. Anywhere else you run over HTTP, pass secure: false to set.
a cookie with sameSite "none" has to be secure
Cause
Under server mode, a cookie with sameSite: 'none' was given secure: false. A browser drops SameSite=None without Secure, so cookies().set() throws on the spot.
Fix
Leave a sameSite: 'none' cookie secure, and serve over HTTPS.
Hydration errors only in production
Cause
The whole document the root layout renders is hydrated. When a CDN feature rewrites the HTML, it no longer matches what React renders. Web-font inlining, email obfuscation and script deferral are such features.
Fix
Turn that feature off. Serving fonts and scripts from your site’s own origin leaves the CDN nothing to rewrite.
Untyped href() and params
Cause
The include in tsconfig.json does not cover .k8ordo/. The name starts with a dot, so an entry that names only the directory, ".k8ordo", does not pick it up. The types then look as they do right after a clone.
Fix
List it with a glob: .k8ordo/**/*.ts.
PageProps types after a clone
Cause
.k8ordo/ is generated and not in git, so a fresh clone does not have it yet. Without the route table, params in PageProps is typed as strings. Under mode: 'server', request and search drop out of it too, and tsc reports Property 'request' does not exist.
Fix
Run vite dev or vite build once before tsc, so .k8ordo/ is generated.
routes/ holds only page.tsx, …
Cause
A file under routes/ is not a route file such as page.tsx or layout.tsx. That includes page.ts, with the wrong extension, and a file in a directory whose name starts with _, such as _parts/. When that directory has no page, the build also reports that it declares no route.
Fix
Move components to src/components/ and every other module to src/lib/, and update the imports. For a page, use the .tsx extension.
fallback.tsx placement errors
Cause
A fallback.tsx belongs beside a page.tsx that has a parameter. Anywhere else, the error lists a line routes/<path>: fallback.tsx …, under either mode.
Fix
With no page.tsx beside it, move the fallback.tsx to the page’s directory. Move a layout.tsx beside it one directory up, or fold it into page.tsx and fallback.tsx. Remove a fallback.tsx for a page with no parameter. When a layout above receives every parameter, remove the fallback.tsx too, and list the values in paths.
a params schema must validate synchronously
Cause
The paramsSchema validates asynchronously. The route that matches a URL is decided before anything renders, so the result cannot be awaited.
Fix
Keep the schema to the shape of the value. Check whether the data exists in the page, and call notFound() when it does not.
'server-only' cannot be imported in client build
Cause
A module that imports server-only is reached from a Client Component. The lines below the first are the chain of imports that got it there.
Fix
Do not import it from the Client Component. Import it in a Server Component instead, and pass what it gives you down as props.
Functions cannot be passed directly to Client Components
Cause
A Server Component passes a function to a Client Component as a prop. Props are serialized on the way, and a function cannot be.
Fix
Pass the value the function returns, or move the function into the Client Component.
useRoute must render inside a matched <Router>
Cause
useRoute() or useParams() is imported from @k8ordo/router. Under this framework the browser holds no route table, so the two hooks have no route to read.
Fix
Pass the params the page received down to the Client Component as props. Inside the Client Component, useMatch() given the pattern reads them too.