Skip to content

Troubleshooting

This page collects the spots people most often stop at. Find the entry that matches what you are seeing and follow the notes underneath. If your symptom is not on the list, copy the error message verbatim into Claude Code and ask “how do I fix this?” — a next thing to try usually comes back.

Usually nvm install 20 froze midway or failed outright.

On macOS and Linux, open a fresh terminal and run command -v nvm. If the output is empty, nvm itself installed but the loader did not make it into your shell startup file (.zshrc or .bashrc). Paste the two lines the nvm install printout suggested (export NVM_DIR="$HOME/.nvm" ...) into that file by hand and open a new terminal.

On Windows, double-check that you actually finished the WSL2 install. The commands in this guide do not run in PowerShell — every step belongs inside the Ubuntu shell on WSL2.

node --version prints something older than v20

Section titled “node --version prints something older than v20”

Multiple Node versions are installed and the system default points at an older one.

Terminal window
nvm use 20
nvm alias default 20

Open a new terminal and run node --version again to confirm.

If you see EACCES or permission denied, the npm global install location’s permissions are the most likely cause. Try these in order:

  • Do not retry with sudo. That tends to compound the problem.
  • If node_modules/ is leftover from a previous attempt, delete it and try again: rm -rf node_modules pnpm-lock.yaml.
  • If the error sticks, reinstall the nvm copy of Node: nvm uninstall 20 && nvm install 20.

Docker is not running, so supabase start fails

Section titled “Docker is not running, so supabase start fails”

You see Cannot connect to the Docker daemon or something close. On macOS and Windows, check Docker Desktop is actually running. The whale icon in the system tray should not be gray — it has to show the running state.

On Linux, docker ps sometimes hits a permission error. Confirm you ran the usermod -aG docker $USER line in 01-prerequisites.md and logged out and back in afterwards.

A build error like Missing environment variable VITE_SUPABASE_URL

Section titled “A build error like Missing environment variable VITE_SUPABASE_URL”

The zod validation in src/shared/config/env.ts caught it. Usually one of three things:

  • .env.local does not exist — start with cp .env.example .env.local.
  • The file exists but the VITE_SUPABASE_URL= line is empty — re-copy from Settings → API on the Supabase dashboard.
  • The prefix is not VITE_ (e.g. only SUPABASE_URL=... is in the file) — prepend VITE_ so the variable reaches the client bundle.

After editing env, restart pnpm dev — Vite reads .env.local once at dev server boot.

STRIPE_SECRET_KEY prints undefined in the browser console

Section titled “STRIPE_SECRET_KEY prints undefined in the browser console”

That key has no VITE_ prefix, so it does not get exposed to the client. That is expected. The Stripe Secret key is read inside an Edge Function via Deno.env.get('STRIPE_SECRET_KEY'), and the value lives under supabase secrets set.

Terminal window
supabase secrets list

If STRIPE_SECRET_KEY is not in the listing, set it:

Terminal window
supabase secrets set STRIPE_SECRET_KEY=sk_test_...

First check that VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY in .env.local are not malformed. The URL must look like https://<ref>.supabase.co, with no trailing /.

If the values are clean, head to Authentication → Providers on the Supabase dashboard and confirm Email is enabled. Sometimes it gets locked down to “not accepting signups.”

Open DevTools, go to the Network tab, and click signup. Look at the response body of the request to supabase.co. The reason for the rejection is in there.

Open Table Editor and confirm your domain table actually exists. The SQL migration may not have been applied.

Terminal window
supabase db push

If the table exists but no data lands, it is the RLS policy seat. The default is deny, so without a policy even INSERT is blocked. Head to Authentication → Policies and look at the policy list for that table. If empty, add the policy inside api/migrations/ and re-run supabase db push.

Do not create policies in the UI. Policies created in the UI do not end up in git, so they disappear in the next environment.

Re-copy the anon key. A trailing space or newline often sneaks in during paste. Never use the service_role key inside the client. It bypasses RLS, so once it lands on the client every row is exposed.

Stripe webhook signature verification fails

Section titled “Stripe webhook signature verification fails”

Webhook signature verification failed shows up in Edge Function logs. Almost always one of three:

  • STRIPE_WEBHOOK_SECRET is not set in supabase secrets. Confirm with supabase secrets list, then set: supabase secrets set STRIPE_WEBHOOK_SECRET=whsec_....
  • The secret is set but it belongs to a different endpoint. On the Stripe dashboard, Developers → Webhooks, click your endpoint — the Signing secret is right there. Copy it again.
  • For local development with stripe listen, the signing secret it prints (whsec_...) differs from the one on the dashboard. Locally, set the value stripe listen printed.

The Price ID in pricing-grid is still a placeholder when you click the checkout button. The Price ID in src/widgets/pricing-grid/model/plans.ts has to be your own test-mode Price ID from the Stripe dashboard.

You find it on the Stripe dashboard under Products — click Pro or Enterprise, and the price_... Price ID sits next to the price.

You imported features/sign-in from inside features/checkout. FSD’s six-layer rule forbids direct cross-slice imports within the same layer.

If two features share logic, pull it down into shared/lib/ or entities/. Or compose the two features one layer up in a widget (e.g. widgets/auth-checkout-flow/).

You imported features/checkout/model/useCheckout.ts directly from another slice. FSD only allows the slice’s index.ts (public API) to be imported from outside.

// blocked
import { useCheckout } from '@/features/checkout/model/useCheckout';
// allowed — only what index.ts exports
import { useCheckout } from '@/features/checkout';

An <img> is missing the alt attribute. For decorative images, an empty string is correct.

// Meaningful image
<img src="/logo.svg" alt="MyApp logo" />
// Decorative image (screen readers skip it)
<img src="/decoration.svg" alt="" />

Port 5173 is in use, trying another one... drops. A previous dev server did not shut down.

Terminal window
lsof -ti:5173 | xargs kill

Kill the process holding the port, then bring pnpm dev back up. Vite often picks a different port (5174 and so on) on its own — using that URL also works.

Usually the dev server is dead. Look at the terminal and confirm pnpm dev is still alive. If it is alive and the screen still does not update, it is browser cache — force-refresh (Cmd+Shift+R or Ctrl+Shift+R).

Edits to CSS seats like tokens.css occasionally slip past HMR. Cycling the dev server (Ctrl+C and bring it back) clears it.

src/features/checkout/model/useCheckout.ts(14,7): error TS2322: Type 'string' is not assignable to type 'PriceId'.

This shape of error means a type mismatch in code the AI wrote. Paste the output verbatim into Claude Code and say “fix this” — the AI patches the seat.

If you attempt a commit while the build is broken, the pre-commit-check.sh hook blocks it. That is by design. The hook is what keeps broken code out of your git history.

If nothing here matches, two more things to try.

First, copy the entire error message into Claude Code and ask “how do I fix this?”. A next move usually comes back, and it is often a good one.

If that still does not unstick it, open a GitHub issue or post in your team’s Slack. Include the same three things together:

  • Where you got stuck (which file, which command)
  • The full error message
  • Environment info (macOS / Linux / Windows, Node version, Docker version, Supabase CLI version)