Troubleshooting
Must run bun run build before wrangler dev
Section titled “Must run bun run build before wrangler dev”The Worker imports dist/manifest.json at runtime to resolve hashed asset
paths. If dist/ doesn’t exist (first clone, or after deleting it), wrangler dev fails with a module resolution error.
bun run build && bun run devRebuild after any client-side change (src/client/) — the Worker serves the
stale bundle otherwise.
Response.redirect() has immutable headers on Workers
Section titled “Response.redirect() has immutable headers on Workers”Hono’s secureHeaders middleware tries to append security headers to the
response. Response.redirect() returns a Response with frozen headers on
the Workers runtime — headers.append() throws.
Fix: build the redirect manually:
// ❌ crashes on Workersreturn Response.redirect(url, 302);
// ✅ worksreturn new Response(null, { status: 302, headers: { location: url.toString() } });This pattern is used throughout inertia.ts, auth.ts, and
google-oauth.routes.ts.
PBKDF2 is capped at 100K iterations
Section titled “PBKDF2 is capped at 100K iterations”The OWASP-recommended 600K iterations throws NotSupportedError on Workers.
Kilat uses 100K, which is still OWASP-acceptable for PBKDF2-HMAC-SHA256 and
fits the CPU budget. Do not raise PBKDF2_ITERATIONS above 100K.
const PBKDF2_ITERATIONS = 100_000; // Workers caps here — do not increaseWrangler auto-compresses — no custom compress middleware
Section titled “Wrangler auto-compresses — no custom compress middleware”Wrangler/Miniflare applies gzip/br compression natively. Adding a custom compress middleware causes double-compression (gzip-on-gzip) and breaks small JSON responses (the Inertia XHR payload gets consumed below the threshold).
Don’t add one. If you see garbled responses or empty XHR bodies, check whether a compress middleware slipped in.
Svelte/Vue need pre-built SSR
Section titled “Svelte/Vue need pre-built SSR”Wrangler’s esbuild bundler cannot compile .svelte or .vue source files
directly. If you’re using the Svelte or Vue template, SSR must be pre-built
to dist/ssr.js before wrangler dev / wrangler deploy runs. The React
template compiles inline because Wrangler’s esbuild handles .tsx.
c.header() headers are dropped on custom Responses
Section titled “c.header() headers are dropped on custom Responses”When a handler returns a custom Response (e.g. a redirect), Hono drops
headers queued via c.header() / setCookie(). Cookie helpers in Kilat
append to c.res.headers instead, which the context.res setter merges into
the returned response.
If your Set-Cookie isn’t landing, make sure you’re using setSessionCookie(c, …)
(append-based), not c.header("set-cookie", …).
Guards must call next()
Section titled “Guards must call next()”A guard middleware that returns undefined without calling next() errors
with “Context is not finalized”. Always end the success path explicitly:
export const requireAuth = async (c, next) => { if (!c.var.user) return redirectTo(c.req.raw, "/login"); return next(); // ← required};node:crypto is not available
Section titled “node:crypto is not available”Use the Web Crypto API instead: crypto.subtle (PBKDF2, hashing),
crypto.getRandomValues (random bytes), crypto.randomUUID (IDs). The
nodejs_compat flag is set but does not provide node:crypto.
Tests fail with stale state
Section titled “Tests fail with stale state”Run bun test --isolate — never plain bun test. Without --isolate, one
file’s afterAll teardown finalizes the next file’s cached module values.
See Testing.