Troubleshooting
Most likely first-run failures with the Inertia + React starter — Vite not serving, cookie not set, typed routes stale, shadcn add fails, SSR fallback loops — and how to resolve them.
A guide to the failures you're most likely to hit on the path from git clone to a signed-in React dashboard. Each section names the symptom you'll see and the fix.
"Supabase URL not configured" on first request
The gem reads SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY, SUPABASE_SECRET_KEY, and SUPABASE_JWKS_URL from the environment. If any is missing, the first request raises a configuration error from Supabase::Rails::Middleware.
- In dev: export the variables before running
bin/dev. Rails does not auto-load.env— the kit's.env.exampleis fordirenv,dotenv, or your process manager. The simplest fix iscp .env.example .env, fill it in, and source it through your shell tool of choice (direnv allow .,dotenv -- bin/dev, etc.). - In production: set the same four variables through your platform's secret manager.
Reminder: SUPABASE_JWKS_URL is not printed by supabase status — derive it from SUPABASE_URL as <SUPABASE_URL>/auth/v1/.well-known/jwks.json.
Cookie isn't being set
You signed up, see no error, but every refresh sends you back to /session/new. Two possible causes:
- You're on
http://andconfig.force_ssl = true(orconfig.assume_ssl = true) is on. Thesb-sessioncookie is markedSecure, and the browser silently drops it on a non-HTTPS origin. For local development make sure those two are commented out inconfig/environments/production.rband you're not somehow booting the app underproduction. Local dev runsconfig/environments/development.rb, which doesn't set either. secret_key_baseis not stable. If Rails regenerates a key between requests (e.g. you have noconfig/master.keyand noRAILS_MASTER_KEYenv var), the cookie written by request N is undecryptable by request N+1. Checkbin/rails credentials:showruns cleanly; if it errors, generate a master key withbin/rails credentials:editand commit the resultingconfig/credentials.yml.enc(the key itself stays inconfig/master.key, which is gitignored).
Vite isn't serving (404 on /vite/@vite/client)
You opened http://localhost:3000 and the page is unstyled, the console is full of Failed to load resource: 404 on /vite/... paths.
bin/devisn't running both processes.Procfile.devrunsbin/rails s -p ${PORT:-3000}andnpx vite. If you started Rails alone withbin/rails s, the Vite dev server isn't up andrails-vite-pluginhas nothing to proxy to. Usebin/devinstead.- Port 5173 is taken. Vite defaults to 5173. If another Vite app is already there, the second instance picks a random free port — but Rails still looks at 5173. Stop the other dev server (or override the Vite port via
VITE_RUBY_PORT/vite.config.ts'sserver.port). npm installdidn't run. Ifnode_modules/is empty,npx vitefails silently. Runnpm install(orbin/setup --skip-server) and try again.
Inertia page rendering throws "Page not found: <name>"
Inertia tried to resolve a page that doesn't exist under app/javascript/pages/. Two causes:
- You forgot to create the React file.
DashboardController#indexwithdefault_render: trueresolves topages/dashboard/index.tsx. If only the controller exists, Inertia raises this error at render time. Create the matching file. - Casing mismatch. Inertia resolves with the literal page name. On macOS the filesystem is case-insensitive but the bundler is case-sensitive — so a page name
"Dashboard/Index"won't resolvepages/dashboard/index.tsxeven though the file is there. Use the kebab/snake casing the controller emits ("dashboard/index","settings/passwords/show").
Typed routes are stale after editing routes.rb
You added a new route, restarted bin/dev, but import { projects } from "@/routes" still errors with "no exported member 'projects'".
- Typelizer didn't regenerate. Typelizer hooks into the Rails reloader; in dev it should regenerate
app/javascript/routes/*.tson every relevantroutes.rbchange. If it didn't, force it:bin/rails typelizer:generate. Restartnpx viteso the TS file is re-read. - The route is in the exclude list.
config/initializers/typelizer.rbexcludes/^\/rails/and/^\/up/by default. If your route happens to match one of those, the helper is intentionally not emitted. Edit theexclude:list if needed.
npm run check fails with "Cannot find module '@/...'"
tsc can't resolve the @/ path alias. The kit ships two tsconfigs (tsconfig.app.json for app/javascript, tsconfig.node.json for the Vite config + build scripts). The path alias lives in tsconfig.app.json under compilerOptions.paths.
- Make sure you're using the right one.
npm run checkruns both tsconfigs in sequence; if one fails, the message shows which. The Vite config (vite.config.ts) is type-checked bytsconfig.node.jsonand shouldn't import@/. - Restart
npm run checkafter editing a tsconfig. TypeScript caches resolution; re-run from a fresh process.
npx shadcn@latest add <component> fails
You ran the CLI and it errored. Common causes:
components.jsonlost. Somenpx shadcn@latest initinvocations rewrite it. The kit'scomponents.jsonis checked in for a reason —git checkout components.jsonif you accidentally clobbered it.- Path alias mismatch. The CLI uses
aliases.utils = "@/lib/utils". If you re-aliased@/intsconfig.jsonwithout updatingcomponents.json, the import the CLI writes won't resolve at build time. - CDN unreachable. The CLI fetches from
ui.shadcn.com. A corporate proxy or air-gapped network blocks it. Vendor the source: clone theshadcn-ui/uirepo, copy the primitive you want directly intoapp/javascript/components/ui/, and adjust imports by hand.
SSR fallback loop (every request is client-rendered)
You set ssr_enabled = true and --build-arg SSR_ENABLED=true, deployed, and every page is being client-rendered (no pre-rendered HTML in the initial response).
config.on_ssr_error in Inertia falls back silently to client rendering whenever the SSR process raises. Causes you'll see most often:
-
A
window/documentaccess in a component that runs on the server. Server-side,windowisundefined. Guard browser-only code:useEffect(() => { // Anything that touches window goes here — useEffect doesn't run on the server. }, [])Avoid top-level access like
const isDark = window.matchMedia(...).matches. The kit'slib/browser.tsexportsisBrowserfor that purpose. -
SSR bundle missing. If
SSR_ENABLED=truewasn't passed todocker build,public/vite-ssr/ssr.jsdoesn't exist; Puma'sinertia_ssrplugin reports "SSR bundle not found" and falls back. Re-build the image with the flag. -
Node 22 missing in the final image. The
Dockerfile'sbranch-ssr-truestage copies/usr/local/node;branch-ssr-falsedoes not. If SSR is on but the wrong stage was selected (build arg passed in the wrong spot), the runtime image has nonode. Confirmdocker run … which nodereturns a path.
Watch your APM / logs for InertiaRails::SSR::Error; that's the only signal you'll get without manually checking.
React <Form> posts but the server response doesn't update the page
You're seeing the server's flash toast, but the page's props aren't updating to match.
- You're returning a redirect, not a re-render. That's the right pattern for success (the kit's
SessionsController#createdoes it). For errors, you wantrender inertia: …, status: :unprocessable_contentso Inertia merges the new props (and theerrorshash) onto the current page. - The action returned a 302 but Inertia treated it as 200. Inertia uses the response status to decide whether to follow. Make sure your error path returns
:unprocessable_content(422) and the success path returns:see_other(303) for non-GET.redirect_toin Rails 8 defaults to 303 for POST/PUT/PATCH/DELETE, which is what Inertia wants.
bin/rspec fails with "Failed to authenticate / Supabase URL not configured"
Request specs that hit an Inertia controller chain need the test helper's stubbed resume_session. If you wrote a new spec and didn't sign_in_as, the gem's middleware will try the real Supabase call and fail (the fake SUPABASE_URL set by rails_helper.rb points at the local CLI's port, which isn't running in CI).
- Use
sign_in_asat the top of any spec that asserts on an authenticated page:before { sign_in_as(email: "user@example.com", name: "Test User") } - Or stub
sign_outfor explicit unauthenticated paths.
Both helpers come from spec/support/supabase_auth_helpers.rb and are auto-included for type: :request specs.
"Could not start Supabase: port 54321 already in use"
Another Supabase stack — almost always one from a different project on the same machine — is bound to the local ports. Either stop that stack first:
cd ../other-project
supabase stop…or change this project's ports in supabase/config.toml (look for [api] port = 54321, [db] port = 54322, [studio] port = 54323) and re-run supabase start.
"Lock timeout" or "database is locked" on SQLite
SQLite serialises writes — under concurrent writes (a job process + a web process, or two Puma workers writing at once), one will block until the other commits. Symptoms:
SQLite3::BusyException: database is lockedMitigations, in order:
- Confirm WAL is on. Rails 8.1 enables
journal_mode = WALby default, but checkbin/rails db -p→PRAGMA journal_mode;returnswal. - Lower
RAILS_MAX_THREADS. Default is 3 in the kit's puma config. If you're seeing locks, you're probably running too many concurrent writes; a smaller pool serialises at the Ruby level instead of inside SQLite. - Move off SQLite for hot tables. The most common offender is Solid Queue (a job-heavy app writes constantly to
solid_queue_*). Pointing thequeuerole at Postgres while keepingprimaryon SQLite is a valid compromise. - One server max. SQLite is local-disk; the file isn't shared across hosts. For multi-server deploys, you have to swap to Postgres regardless.
bin/ci fails at npm run format on a fresh checkout
Prettier disagreed with a file. On a fresh checkout this is almost always:
- Different Prettier version. The kit pins Prettier in
package.json. Ifnpm installdidn't run (ornode_modulesis stale),npx prettierfalls back to a globally installed copy. Runnpm installand re-try. - CRLF line endings on Windows. Prettier expects LF. Configure git with
git config core.autocrlf inputor runnpm run format:fixto rewrite the files.
For real format failures (after you've edited code), npm run format:fix writes the corrections in place.