Skip to content

Gotchas

  1. Node 22 required. Node 18 fails in ways that read as “command not found”. nvm use.
  2. npm run setup writes .env once and never touches it again. Re-running it will not fix a value you changed by hand.
  3. SESSION_SECRET is optional in development, and without it a fresh one is generated per boot, so restarting the API signs you out. Required in production.
  4. Ports: Vite 5173, API 4000. Avoid 5000 on macOS; AirPlay Receiver binds it.
  5. VITE_BELUGA_API=false renders the bundled fixture with no database. Right for UI work, confusing if you set it and forget.
  6. A fresh checkout has no store in it. npm test seeds per suite; npm run test:e2e drives the real app against data/beluga.sqlite and fails on a missing heading in a new clone. npm run db:migrate && npm run db:seed once per checkout.
  7. .env is irrelevant to e2e. Playwright points ENV_FILE at a file that does not exist, on purpose.
  8. Postgres skips silently in db/dialect.test.ts if embedded-postgres cannot start. Confirm with --reporter=verbose and look for repository on postgres.
  1. The success redirect proves nothing. Only the webhook marks an order paid. Anyone testing without stripe listen concludes checkout is broken.
  2. Local testing needs stripe listen --forward-to localhost:4000/api/webhooks/stripe, the printed whsec_… in .env, and an API restart. The restart is the step people skip.
  3. Stripe Prices are immutable. Changing an amount mints a new Price and archives the old one. That is why historic orders still resolve.
  4. Same for tax_behavior, so changing how a store quotes prices reaches Stripe only when each product is republished, and nothing republishes itself.
  5. A product not published to Stripe cannot be bought. Saving does not publish.
  6. Test and live keys have separate catalogues. Publishing under test keys puts nothing in the live account.
  1. SQLite is a file and uploaded images are files. On any platform with an ephemeral filesystem, both vanish on redeploy. Attach a volume, or move to Postgres and a bucket. The shape of a deployment.
  2. PUBLIC_URL builds Stripe’s success and cancel URLs and every emailed link. Wrong value: buyers redirected somewhere wrong after paying.
  3. SEO head rewriting runs only in the production branch, invisible under npm run dev. Verify with npm run build && npm start and curl the title.
  4. A half-configured ASSETS_S3_* group, or a stray one with no bucket, is refused at boot. That is the feature; read the message.
  5. The Stripe webhook endpoint has to be created again for live keys, with a new secret. Until then the live store records no orders.
  1. Email is a logged no-op until SMTP_URL is set. Orders complete, no mail sent, nothing errors.
  2. Tax is off by default and under-collection is silent. Three things must be true in Stripe first, none doable from Beluga.
  3. Shipping fails silently three ways. A coverage gap ships free; no recorded weights means everything matches the lightest band; subtotal bounds ignore digital lines. Shipping.
  4. Abandoned cart recovery is off by default, and a guest’s cart is never stored server-side.
  5. Customer orders link to an account only after email verification. Not an oversight.
  6. A partial refund does not restock. Full refunds and cancellations do, once.
  7. role is recorded but gates nothing. Every administrator can do everything.
  8. Removing a staff member destroys their sessions immediately.
  9. The webhook signing secret is shown once. Rolling it invalidates the old one immediately.
  1. Every schema change lands in both dialects, then npm run db:generate.
  2. New routes go in MUTATIONS or READS in server/security.test.ts. Not optional.
  3. Money is integer cents everywhere. CSV columns are *_cents for the same reason.
  4. Storefront search is client-side against the /api/store snapshot, capped at 200 products. Past that, swap to GET /api/products?search= behind src/lib/store-source.ts.
  5. Image derivative naming lives in shared/images.ts because both sides use it, and nothing type-checks that they agree.
  6. Reserved page slugs (shop, cart, confirm, product, collection, about, admin, setup, account) are refused.
  7. variantName and aboutText are deprecated but still present, kept one release for rollback. Do not build on them.
  8. Verify outbound webhooks against the raw body. A re-serialised body fails on the first payload with different key ordering.