@k8ordo/framework

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.

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.

k8ordo

React libraries that use Baseline features without holding back.

© 2026 k8o — MIT License

Typeset in Noto Sans JP & M PLUS 2