Troubleshooting
This page helps you find the cause of the failures this codebase actually produces and rule out the guesses that are wrong.
Use this page when something in your Litestore setup fails and you need to find out why. Each entry points to specific behaviour in the source. Where a common guess is wrong, the entry says so.
The entries are grouped by where you notice the failure, such as sign-in, the database or checkout. They are not grouped by which part of the code turns out to be at fault, because you start from the symptom.
Sign-in
Magic links do not arrive.
Litestore sends sign-in magic links through Resend, and only through Resend. That is why RESEND_API_KEY and RESEND_SENDER_EMAIL are required variables rather than optional ones.
Check the Resend sender domain first. A valid key with a sender address Resend refuses to send from looks the same as any other failure: nothing arrives in the inbox.
Do not blame Redis. checkRateLimit fails open, which means sign-in still goes through when Redis is unreachable and only rate limiting stops working. Production does need Redis, but a missing Redis does not cause a missing email.
Google sign-in does not show up on the page.
Litestore adds the Google provider only when both AUTH_GOOGLE_ID and AUTH_GOOGLE_SECRET are set.
If you set only one of them, you get no button and no error. Nothing fails. Litestore simply never adds the provider, so the sign-in page renders without it.
Database
The first request hangs, then everything is slow.
services/db.ts sets this timeout:
connectionTimeoutMillis: 30_000When the database is reachable but far away, or when the connection pool is full, a request waits out the whole 30 seconds before it fails. You see a long wait, not a quick error.
Check first that the app and Postgres run in the same region. Distance between them is the usual cause, and it shows up as slowness rather than as a connection error.
bun run db:seed fails.
The script runs tsx prisma/seed.ts, but tsx is not a dependency of this repository. The script calls a runner that is not installed.
Run the seed file directly instead:
bun prisma/seed.tsThis runs the same seed file without the missing runner.
Prisma disagrees with the schema after you pull.
The generated client no longer matches schema.prisma. Regenerate it:
bun run db:generatepostinstall runs the same step, so a fresh bun install also fixes it.
Checkout
Starting a checkout throws an error.
getPaymentProvider() throws when STRIPE_SECRET_KEY or STRIPE_WEBHOOK_SECRET is missing.
Only payment is affected, not the whole store. Customers can still browse and add items to their cart without Stripe, but the store cannot take money.
An unknown provider name fails loudly.
Litestore does this on purpose. The storefront checkout action passes the provider name as an optional string. If Litestore does not recognise the value, it throws instead of quietly falling back to Stripe.
A quiet fallback could take a payment through a processor the caller did not ask for. Throwing prevents that.
Build and checks
Typecheck runs out of memory.
bun run typecheck already sets its own memory limit:
NODE_OPTIONS='--max-old-space-size=12288'If you run tsc directly, you need to set the same limit yourself. The build uses 6144.
bun run lint passes but bun run verify fails.
The two commands run different sets of checks. lint is the short check you run while working, and it skips five checks. verify runs the full set. See The Harness for the details.
A clean lint tells you nothing about the five checks it skipped. Run verify before you decide everything passes.
A failing contract test is a signal, not an obstacle
scripts/check-critical-tests.ts makes sure test coverage over the critical
paths only ever grows. When a contract test starts failing, it is telling you
that a guarantee changed. If you delete the test, you silence the alarm and
leave the fault in place.
Background work
Scheduled work never runs.
env.ts does not configure Inngest at all. No missing variable will warn you about it, and the app shows no error at startup.
A store that is not connected to Inngest therefore serves pages normally, while every cron job and workflow stays idle. Once you connect the deployment to Inngest, the schedules register on the first sync.
Which kind of failure is this
The entries above follow two patterns.
Anything involved in taking money fails loudly. getPaymentProvider() throws when the Stripe keys are missing, and an unknown provider name throws instead of falling back.
Optional features fail quietly instead. When Redis is unreachable, rate limiting fails open. When only one Google variable is set, you get no button and no error. When Inngest is not connected, every schedule stays idle while the store keeps serving.
So start by asking a different question than "what is broken?":
Is the feature throwing an error, or is it simply missing? A missing feature usually means a missing environment variable, not a bug.
Related
Development
See how you start Postgres and the dev server, reseed the database, and run the checks before you commit.
Configuration
Find out which environment variables env.ts requires and what each optional group turns on.
Deploying
Take a configured store to production, from running migrations to serving the Inngest functions.