@k8ordo/state

Troubleshooting

Common symptoms, what causes them, and how to fix them. To look up an error, search this page for its wording.

On this page

Loading the module throws “fields must tolerate absence”

Cause

A field has neither .default() nor .optional(). A URL parameter or a stored row can always be missing, so the definition refuses such a field as the module loads.

Fix

Give the fields the error names a .default() or an .optional() (z._default() or z.optional() in zod/mini). If the error says rejects its own defaults instead, make the object-level .refine() accept the all-defaults value.

It throws “url boolean fields must use z.stringbool()”

Cause

A url field or array item is z.boolean() or z.coerce.boolean(). Neither reads the URL’s "false" as false, so it is refused as the module loads.

Fix

Write it as z.stringbool().

It throws “url array fields must default to []”

Cause

A url array is .optional() or defaults to something other than []. An absent parameter and an empty list are the same URL, so it could never write an empty list.

Fix

Give it .default([]).

A Server Component cannot call href or parseUrl

Cause

The definition is exported from a 'use client' file, so a Server Component receives a client reference instead of the definition.

Fix

Move the definition to a module without 'use client'. It is pure, so both sides can import it.

Cause

A page exports search. @k8ordo/static writes pages out as files, and a file cannot differ per query.

Fix

Drop the search export, and read what depends on the query with useAppState in a client component. If the server really has to read it, move to @k8ordo/server.

update() or href throws “has no URL serialization”

Cause

Something is writing a value no URL can spell, such as a Date, to a url field.

Fix

Keep url to strings, numbers, booleans and arrays of them. Hold a date as a string.

An update() that changes the URL reloads the whole page

Cause

The router does not intercept Navigation API navigations. Under one that does not, Next.js today for example, navigation.navigate() is a document load.

Fix

Under that router, change the URL with links and GET forms. On @k8ordo/static and @k8ordo/server, which run on @k8ordo/router, it is never a document load.

A boolean in entry or localStorage resets on every write

Cause

The schema of entry, Web Storage or a cookie uses z.stringbool(). There the written true comes back to the schema as it is, and z.stringbool(), which expects a string, rejects it.

Fix

Write it as z.boolean(). A field moved over from url needs its spelling changed along with it.

A date in localStorage resets after a reload

Cause

Web Storage and cookie rows are JSON, so a Date comes back as a string, which z.date() rejects. It shows right after the write only because the echo never goes through JSON.

Fix

Keep the field to what JSON can hold; for a date, a string.

Two states that should be separate show the same values

Cause

Two definitions of the same kind share a key, and so silently share one store. @k8ordo/color-scheme also uses a local state keyed color-scheme.

Fix

Rename one of them to a key nothing else in the app uses. Renaming the key renames the data, so values saved under the old key are no longer read.

URL state shows its defaults for a moment on the server render

Cause

Unless given initialUrl, the server render and the hydration render show the url slot’s defaults.

Fix

Under @k8ordo/server, export search from the page and pass what it receives as initialUrl; elsewhere, pass what parseUrl returned. Under @k8ordo/static the server cannot read the query, so the switch cannot be avoided.

Cause

initialCookie seeds only the useAppState it is passed to, so a component without it renders the defaults on the server. And @k8ordo/static has no request to read at all.

Fix

Call parseCookies(request.cookies) once, in a layout, and pass the result as initialCookie to every component that uses the cookie state.

Cause

The Cookie Store API always sets Secure, and Safari drops a Secure cookie even on http://localhost.

Fix

Serve over HTTPS during development too.

A test sees values from the previous test

Cause

Stores stay in a registry inside the module, and stored rows stay in the browser.

Fix

Unmount the components, call resetStateRegistry(), then delete the rows by storageKey or cookieName.

A URL update in a test navigates the test page away

Cause

Nothing intercepts the navigate event, so navigation.navigate() is a document load.

Fix

Intercept the navigate event in the test itself, calling event.intercept() as a router would.

k8ordo

React libraries that use Baseline features without holding back.

© 2026 k8o — MIT License

Typeset in Noto Sans JP & M PLUS 2