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 build

Then 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

SymptomLikely causeSafe fix
Command/script not foundWrong package manager or invented scriptRun pnpm install; use scripts in package.json (dev, build, preview, deploy)
Import from fumadocs-mdx:collections/* fails.source generation is staleStop dev, remove only generated .source if needed, rerun pnpm dev/pnpm build
Route exists on disk but not in appGenerated route tree is stale or filename convention is wrongRestart Vite and fix the file under src/routes
Changes disappear from route treesrc/routeTree.gen.ts was edited manuallyRevert the manual edit and change route files; never edit the generated tree
Creem code fails on node:crypto/Buffernodejs_compat missingRestore 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.

SymptomLikely causeSafe fix
Works locally, secret missing after deploy.env/.dev.vars are never uploadedpnpm wrangler secret put NAME, then deploy
BETTER_AUTH_URL still points to localhost/old hostProduction non-secret var is staleUpdate wrangler.jsonc vars to the exact deployed origin
Product/model/demo change not visibleVITE_* was changed after buildSet it in the build environment and rebuild/redeploy
Wrong D1/R2 resourceBinding ID/name differs between environmentsCompare wrangler.jsonc, Cloudflare resources, and deployed config

D1 and Drizzle

SymptomCauseFix
Registration says table does not exist locallyLocal Miniflare D1 has no migrationspnpm db:migrate:local
Production schema is oldOnly local migrations were appliedBack up/review, then pnpm db:migrate
Generated SQL does not match schemaMigration was not regenerated after schema changepnpm db:generate, review drizzle/, apply local then remote
Drizzle asks for PostgreSQL/connection URLInstructions from a different template were followedUse D1/SQLite config and the DB binding; there is no DATABASE_URL
Suggested drizzle-kit push command fails or bypasses historyWrong workflowUse generated, committed SQL migrations; do not use drizzle-kit push
Transaction API failsD1 has no interactive transaction supportUse db.batch()/idempotent statements and design partial-failure handling

Better Auth and OAuth

  • Login loops or cookies fail: ensure BETTER_AUTH_URL exactly 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/github or /google; update it when the domain changes.
  • Protected API is still accessible: page beforeLoad is 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

SymptomCheck
Checkout uses wrong/missing productVITE_CREEM_PRODUCT_PRO_MONTHLY/_PRO_YEARLY/_LIFETIME; rebuild after changes
API hits wrong Creem environmentCREEM_SERVER_IDX and API key belong together
Webhook signature failsExact raw payload, CREEM_WEBHOOK_SECRET, and provider signature headers
Payment succeeds but D1 does not updateWebhook URL is exactly /api/payment/notify/creem, remote migration exists, inspect Worker logs
Dashboard shows unrelated subscriptionSubscription page is still mock UI; connect it to /api/user/subscription

R2 uploads

  • UPLOADS undefined: create/bind the bucket and keep request-time env.UPLOADS access.
  • 401 upload: the endpoint requires a Better Auth session.
  • 400 upload: send FormData entries named files; current limits are five files and 5 MB each with an image/* 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

SymptomLikely cause/fix
Chat returns 400 immediatelyValidate OPENAI_API_KEY, body shape, and model availability; inspect Worker error safely
Search toggle has no sourcesIt selects a search-preview model, not a custom tool; sources appear only if the model returns them
Media says provider not configuredSet REPLICATE_API_TOKEN locally/with wrangler secret put, restart/redeploy
Polling never finishesInspect Replicate task; handle canceled as terminal and verify output parsing/model mapping
Output disappears laterProvider URL was not durable; copy successful results to R2
Unexpected AI billPublic endpoints lack production quotas/rate limits; harden them before exposure

Content and i18n

  • Docs page missing from sidebar: add it to the correct meta.json and 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 LanguageToggle intentionally 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.

On this page