Troubleshooting
Diagnose common setup, integration, content, and deployment failures.
Start with the failing boundary—Vite build, Worker runtime, D1, provider API, or
browser—and change one configuration plane at a time. A feature working from
.env locally does not prove its production Worker is configured.
First checks
pnpm check
pnpm test
pnpm buildThen confirm the current origin, Worker logs (pnpm wrangler tail), and whether
the failure is local or remote. Do not apply remote migrations or rotate secrets
as a speculative first step.
Install, Vite, and routing
| Symptom | Likely cause | Safe fix |
|---|---|---|
| Command/script not found | Wrong package manager or invented script | Run pnpm install; use scripts in package.json (dev, build, preview, deploy) |
Import from fumadocs-mdx:collections/* fails | .source generation is stale | Stop dev, remove only generated .source if needed, rerun pnpm dev/pnpm build |
| Route exists on disk but not in app | Generated route tree is stale or filename convention is wrong | Restart Vite and fix the file under src/routes |
| Changes disappear from route tree | src/routeTree.gen.ts was edited manually | Revert the manual edit and change route files; never edit the generated tree |
Creem code fails on node:crypto/Buffer | nodejs_compat missing | Restore compatibility_flags: ["nodejs_compat"] in wrangler.jsonc and rebuild |
Worker configuration and bindings
DB and UPLOADS are bindings from wrangler.jsonc. If they are undefined,
verify the binding name/resource and read them through env from
cloudflare:workers during the request. Do not read them from process.env or
capture them at module initialization.
| Symptom | Likely cause | Safe fix |
|---|---|---|
| Works locally, secret missing after deploy | .env/.dev.vars are never uploaded | pnpm wrangler secret put NAME, then deploy |
BETTER_AUTH_URL still points to localhost/old host | Production non-secret var is stale | Update wrangler.jsonc vars to the exact deployed origin |
| Product/model/demo change not visible | VITE_* was changed after build | Set it in the build environment and rebuild/redeploy |
| Wrong D1/R2 resource | Binding ID/name differs between environments | Compare wrangler.jsonc, Cloudflare resources, and deployed config |
D1 and Drizzle
| Symptom | Cause | Fix |
|---|---|---|
| Registration says table does not exist locally | Local Miniflare D1 has no migrations | pnpm db:migrate:local |
| Production schema is old | Only local migrations were applied | Back up/review, then pnpm db:migrate |
| Generated SQL does not match schema | Migration was not regenerated after schema change | pnpm db:generate, review drizzle/, apply local then remote |
| Drizzle asks for PostgreSQL/connection URL | Instructions from a different template were followed | Use D1/SQLite config and the DB binding; there is no DATABASE_URL |
Suggested drizzle-kit push command fails or bypasses history | Wrong workflow | Use generated, committed SQL migrations; do not use drizzle-kit push |
| Transaction API fails | D1 has no interactive transaction support | Use db.batch()/idempotent statements and design partial-failure handling |
Better Auth and OAuth
- Login loops or cookies fail: ensure
BETTER_AUTH_URLexactly matches the public scheme/host/port, the request uses HTTPS in production, and proxies do not change the apparent origin unexpectedly. - OAuth callback mismatch: configure the provider callback as
<origin>/api/auth/callback/githubor/google; update it when the domain changes. - Protected API is still accessible: page
beforeLoadis not sufficient; recheck the session/role in every sensitive handler. - Local registration cannot finish: apply local D1 migrations and configure Resend, or temporarily adapt verification behavior only for intentional local development.
Resend and contact
If no mail arrives, verify RESEND_API_KEY, sender-domain verification, and
websiteConfig.mail.fromEmail; inspect the returned Resend error and dashboard.
The current sendEmail() returns structured failures, but auth/contact callers
do not consistently propagate them, so a success screen is not proof of
delivery. Add safe logging/monitoring rather than logging recipients or bodies.
Creem payments
| Symptom | Check |
|---|---|
| Checkout uses wrong/missing product | VITE_CREEM_PRODUCT_PRO_MONTHLY/_PRO_YEARLY/_LIFETIME; rebuild after changes |
| API hits wrong Creem environment | CREEM_SERVER_IDX and API key belong together |
| Webhook signature fails | Exact raw payload, CREEM_WEBHOOK_SECRET, and provider signature headers |
| Payment succeeds but D1 does not update | Webhook URL is exactly /api/payment/notify/creem, remote migration exists, inspect Worker logs |
| Dashboard shows unrelated subscription | Subscription page is still mock UI; connect it to /api/user/subscription |
R2 uploads
UPLOADSundefined: create/bind the bucket and keep request-timeenv.UPLOADSaccess.- 401 upload: the endpoint requires a Better Auth session.
- 400 upload: send
FormDataentries namedfiles; current limits are five files and 5 MB each with animage/*MIME. - 404 read: confirm the returned key exists in the same local/remote bucket.
- Unsafe content accepted: MIME metadata is not signature validation; add decoding/magic-byte and dimensions policy.
OpenAI and Replicate
| Symptom | Likely cause/fix |
|---|---|
| Chat returns 400 immediately | Validate OPENAI_API_KEY, body shape, and model availability; inspect Worker error safely |
| Search toggle has no sources | It selects a search-preview model, not a custom tool; sources appear only if the model returns them |
| Media says provider not configured | Set REPLICATE_API_TOKEN locally/with wrangler secret put, restart/redeploy |
| Polling never finishes | Inspect Replicate task; handle canceled as terminal and verify output parsing/model mapping |
| Output disappears later | Provider URL was not durable; copy successful results to R2 |
| Unexpected AI bill | Public endpoints lack production quotas/rate limits; harden them before exposure |
Content and i18n
- Docs page missing from sidebar: add it to the correct
meta.jsonand make sure the MDX file exists. - Chinese docs show missing/fallback content: add
slug.zh.mdx; most shipped template docs are currently English-only. /en/*redirects: expected—the default English locale is canonical without a prefix. Chinese uses/zh/*.- Locale switch returns home: current
LanguageToggleintentionally navigates to the selected locale root; change it to localize the current path if desired. - Blog/legal body is not translated: those loaders currently share one MDX collection across locale routes.
Still stuck
Capture the smallest reproduction, exact command/request, local versus deployed
environment, sanitized error, Worker log timestamp, and relevant config plane.
Avoid posting .env, Wrangler secrets, OAuth credentials, webhook payloads, or
user data.
