Troubleshooting
Common issues and how to resolve them
Quick solutions to common issues. Most problems come down to environment variables, the data layer's configuration, or email setup.
Which environment surfaces you have to check depends on your edition — pick yours with the switcher below, and the choice is remembered across every page in these docs.
Quick Fixes
Before diving into specific issues, try these first:
- Restart the dev server: Stop and restart
bun dev - Clear browser cookies: Especially for auth issues
- Check environment variables: Ensure all are set correctly
- Check the database is reachable:
psql "$DATABASE_URL" -c 'select 1' - Check the migrations are applied:
bun run db:migrate, then look at the server output in your terminal — auth and data errors surface in the Next.js process
- Restart Convex: Stop and restart
bun convex dev - Check Convex dashboard: Look for errors in function logs
Authentication Issues
Can't Sign In
Symptoms:
- Login form submits but nothing happens
- Error messages about invalid credentials
- Redirect loops
Solutions:
-
Check email verification:
- Users must verify their email before signing in
- Check if verification email was sent
- Try resending verification email
-
Verify the backend is reachable and the secret is set:
BETTER_AUTH_SECRET and DATABASE_URL both belong in .env.local. Generate a
secret if you're unsure:
openssl rand -base64 32Changing BETTER_AUTH_SECRET invalidates every existing session, so everyone
has to sign in again.
Make sure Convex is running and connected:
bun convex devBETTER_AUTH_SECRET and JWKS must both be set on the Convex deployment,
not in .env.local. Generate a secret if you're unsure:
openssl rand -base64 32If you rotated BETTER_AUTH_SECRET, regenerate JWKS too — the private key is
encrypted with it.
-
Check
NEXT_PUBLIC_APP_URL:- Must match your actual URL (the runtime uses this, not
BETTER_AUTH_URL) - For local:
http://localhost:3000 - For production:
https://yourdomain.com
- Must match your actual URL (the runtime uses this, not
-
Clear cookies and try again:
- Open DevTools → Application → Cookies
- Delete all cookies for your domain
- Try signing in again
Session Issues
Symptoms:
- Getting logged out unexpectedly
- Session not persisting
- "Unauthorized" errors
Solutions:
-
Check cookie settings:
- Cookies require HTTPS in production
- Ensure
NEXT_PUBLIC_APP_URLuses the correct protocol
-
Verify secret consistency:
BETTER_AUTH_SECRETmust match everywhere- If you changed it, all existing sessions are invalid
-
Check session expiration:
- Sessions expire after configured time
- Users need to sign in again after expiration
2FA Not Working
Symptoms:
- TOTP codes rejected
- "Invalid code" errors
- Can't complete 2FA setup
Solutions:
-
Check device time:
- TOTP codes are time-based
- Device clock must be accurate
- Enable automatic time sync
-
Try backup codes:
- Each backup code works once
- 12 codes are generated during setup
-
Wait for next code:
- Codes refresh every 30 seconds
- If timing is close, wait for next code
-
Re-setup 2FA:
- Disable and re-enable 2FA
- Scan QR code again
- Save new backup codes
Passkeys Not Working
Symptoms:
- Can't register passkey
- Passkey authentication fails
- "Origin mismatch" errors
Solutions:
-
Check
PASSKEY_RP_ID:- Must match your domain exactly
- No protocol (just
yourdomain.com) - For local: use
localhost
-
Ensure HTTPS:
- Passkeys require HTTPS (except localhost)
- Check SSL certificate is valid
-
Check browser support:
- Use Chrome, Safari, Firefox, or Edge
- Must be recent version
- WebAuthn must be enabled
-
Check device settings:
- Biometrics must be enabled
- Device must support WebAuthn
Email Issues
Emails Not Sending
Symptoms:
- No verification email received
- Password reset email not arriving
- Invitation emails failing
Solutions:
- Check the Resend API key is where the sender reads it:
Email is sent from Next.js (lib/email.ts), so RESEND_API_KEY and
EMAIL_FROM belong in .env.local — restart bun dev after changing them.
Email is sent from Convex (convex/email.ts), so both variables belong on the
Convex deployment:
bunx convex env set RESEND_API_KEY "re_your_key"-
Verify
EMAIL_FROM:- Must use verified domain
- Format:
"Name <email@domain.com>"
-
Check Resend dashboard:
- View sent emails
- Check for errors
- Verify domain status
-
Check spam folder:
- Verification emails often land in spam
- Mark as "not spam" to improve delivery
-
Test mode limitations:
- In test mode, only verified emails receive mail
- Use your own verified email for testing
Emails Going to Spam
Solutions:
-
Verify your domain:
- Add DNS records in Resend
- Wait for verification (can take hours)
-
Configure SPF/DKIM/DMARC:
- Resend provides these records
- Add all required DNS entries
-
Use proper sender address:
- Use subdomain like
mail.yourdomain.com - Avoid generic addresses
- Use subdomain like
-
Warm up your domain:
- Start with low volume
- Gradually increase sending
Data Layer Issues
Connection Failed
Symptoms:
- The app can't read or write any data
- Queries returning undefined, or pages failing to render
- Errors naming the database or deployment
Solutions:
Check DATABASE_URL is set and reachable. It lives in .env.local, and the
dev server only picks up changes on restart:
psql "$DATABASE_URL" -c 'select 1'Check the connection string. Most hosted providers require
?sslmode=require; a string that works from their web console can still fail
locally without it.
Check your provider's connection limit. Each dev server and each migration run opens its own connections. If your provider offers a pooled connection string, use it.
Check the migrations actually ran. A missing table or column reads like a connection problem but isn't:
set -a && . ./.env.local && set +a && bunx drizzle-kit migrateCheck Convex is running:
bun convex devVerify the Convex URLs all name the same deployment:
CONVEX_DEPLOYMENT="dev:your-deployment"
NEXT_PUBLIC_CONVEX_URL="https://your-deployment.convex.cloud"
NEXT_PUBLIC_CONVEX_SITE_URL="https://your-deployment.convex.site"Check the Convex dashboard. Log in to dashboard.convex.dev, confirm the deployment exists, and read its logs.
Reinstall Convex if the CLI itself is misbehaving:
bun add convex
bun convex devSchema Errors
Symptoms:
- Schema validation or migration failures
- Type errors against tables you just changed
- Deployment fails before the app starts
Solutions:
DATABASE_URL is undefined when running drizzle-kit. The CLI does not read
.env.local. Export it into the shell first:
set -a && . ./.env.local && set +a && bunx drizzle-kit migrateNever reach for db:push to fix a broken schema. drizzle-kit push diffs
the schema straight against the database and skips the drizzle/ folder, so the
custom trigger migrations never run and the journal drifts out of sync with what
is actually deployed. Generate and apply a migration instead:
bun run db:generate
bun run db:migrateAuth tables out of date after editing lib/auth.ts. Regenerate them before
generating the migration, and re-apply the compound indexes marked port: in
the header of lib/db/auth-schema.ts:
bun run auth:schema
bun run db:generate
bun run db:migrateA migration that half-applied leaves the database and drizzle/meta/_journal.json
disagreeing. Fix the migration and re-run it rather than editing the journal by
hand.
Check convex/schema.ts — every field needs a valid v.* validator, and a
syntax error there fails the whole push.
Let Convex type-check it. The schema is validated on startup:
bun convex devRedeploy once the schema is valid:
bunx convex deploy --yesAuth tables out of date after editing convex/betterAuth/auth.ts:
bun scripts/generate-auth-schema.tsKeep your own indexes in convex/schema.ts, layered onto the spread tables, so
a regeneration cannot wipe them.
Function Errors
Symptoms:
- Reads or writes failing
- Error messages in console
- Unexpected behavior
Solutions:
Read the Next.js server output. Reads run in Server Components via
lib/server/* and writes are server actions in actions/*, so both throw in the
Next.js process — not in the browser console.
Check the UI actually re-rendered. There are no live queries in this
edition: after a mutation the client has to call router.refresh() (or
invalidate the relevant React Query key) for the Server Component to re-read.
Stale-looking data is usually a missing refresh, not a failed write.
Check the error is not a constraint. Foreign keys and the owner-guard
triggers in drizzle/ reject writes at the database level; the message names the
constraint.
Check Convex logs. Open the dashboard, go to Logs, and find the failing function.
Add error handling:
export const myFunction = query({
handler: async (ctx) => {
try {
// Your code
} catch (error) {
console.error("Function failed:", error);
throw error;
}
},
});Test the function directly from the Convex dashboard and check its input arguments.
Environment Variable Issues
Variable Not Found
Symptoms:
- "Environment variable undefined"
- Features not working
- Deployment errors
Solutions:
-
Check
.env.localexists:ls -la .env.local -
Verify variable names:
- Case-sensitive
- No spaces around
= - No quotes issues
-
Restart dev server:
- Environment changes require restart
- Stop and restart
bun dev
-
Check you set it on the right surface:
There is only one: .env.local in development, your host's environment in
production. Everything the app reads lives there.
The exception is drizzle-kit, which does not load .env.local — so
db:generate and db:migrate see DATABASE_URL as undefined when run bare.
Export the file into the shell first:
set -a && . ./.env.local && set +a && bunx drizzle-kit migrateThere are two, and a variable set on the wrong one is simply absent. .env.local
covers the Next.js side; the Convex deployment covers everything Better Auth
itself reads — BETTER_AUTH_SECRET, JWKS, EMAIL_FROM, RESEND_API_KEY,
PASSKEY_RP_ID:
bunx convex env set VARIABLE_NAME "value"AUTUMN_SECRET_KEY is the one variable needed in both places.
Secret Mismatch
Symptoms:
- Auth randomly failing
- "Invalid token" errors
- Sessions not working
Solutions:
- Sync secrets:
BETTER_AUTH_SECRET lives in exactly one place per environment — .env.local
locally, your host's environment in production. Development and production must
use different values.
BETTER_AUTH_SECRET lives on the Convex deployment, one value per
deployment. Development and production deployments must use different
values, and rotating it means regenerating JWKS on the same deployment,
because the private key is encrypted with it.
-
Generate new secret:
openssl rand -base64 32Update everywhere.
-
Clear all sessions:
- Old sessions won't work with new secret
- Users need to sign in again
Build Issues
Build Fails
Symptoms:
bun run buildfails- TypeScript errors
- Module not found
Solutions:
-
Check TypeScript errors:
bunx tsgo --noEmit -
Install dependencies:
rm -rf node_modules bun install -
Clear Next.js cache:
rm -rf .next bun run build -
Check import paths:
- Use
@/alias correctly - Ensure files exist
- Use
Production Deployment Fails
Solutions:
-
Check all env vars are set:
- In Vercel dashboard
- For production environment
-
Match local build:
bun run buildFix any errors locally first.
-
Check Vercel logs:
- Deployment logs show errors
- Function logs show runtime errors
-
Remember that
vercel.jsonoverrides the dashboard's Build Command, and that production and preview take different paths:
A production deploy runs drizzle-kit migrate before next build. If the log
fails before the Next.js build even starts, it is the migrate step —
DATABASE_URL is unreachable from the build, or a migration errored. Preview
deploys skip migrating entirely, so they can keep passing while production
fails.
A production deploy runs convex deploy, which authenticates with
CONVEX_DEPLOY_KEY set in Vercel (not on the Convex deployment). Preview
deploys skip that step, so a missing deploy key only shows up on your first
production deploy.
Organization Issues
Can't Create Organization
Solutions:
-
Check user is authenticated:
- Must be logged in
- Email must be verified
-
Check Autumn is configured:
AUTUMN_SECRET_KEYmust be set- Or billing must be removed
Can't Add Members
Solutions:
-
Check Pro plan:
- Organization must be on Pro plan
- Upgrade first, then add members
-
Check permissions:
- Only owners and admins can add members
- Verify user role
-
Check billing status:
- Subscription must be active
- No payment failures
Invitations Not Working
Solutions:
-
Check email is sending:
- Verify Resend is configured
- Check Resend logs
-
Check invitation status:
- View in organization dashboard
- Resend if needed
-
Check user account:
- Invitee may need to create account first
- Email must match invitation
Admin Panel Issues
Can't Access Admin
Solutions:
-
Check admin role:
- User must have
admin(orowner) in itsrolevalue roleis a comma-separated string, soadmin,usercounts
- User must have
-
Set the first admin by hand — register a normal account first, then promote it:
update "user" set role = 'admin' where email = 'you@example.com';Open the Convex dashboard, find your row in the user table, and edit its
role field to admin.
User Actions Failing
Solutions:
-
Check permissions:
- Can't ban yourself
- Can't impersonate other admins
- Can't delete your own account
-
Check user exists:
- User may have been deleted
- Refresh the page
Performance Issues
Slow Page Loads
Solutions:
-
Use pagination:
- Don't load all records at once
- Implement cursor-based pagination
-
Narrow your reads:
- Select only the columns you need rather than whole rows
- Make sure an index covers what you filter and sort on —
explain analyzeagainstDATABASE_URLshows whether one is being used - Watch for N+1 reads: use Drizzle's
withto pull related rows in one query - Host the database in the same region as your functions; every read is a round trip
- Avoid fetching too much data
- Use
.withIndex(...)rather than.filter(...), which scans the table - Check the Convex region is close to your users — a slow connection shows up as laggy real-time updates
Memory Issues
Solutions:
-
Check for memory leaks:
- Monitor with DevTools
- Check for unsubscribed listeners
-
Optimize images:
- Use Next.js Image component
- Compress large images
Getting More Help
If you're still stuck:
- Check logs:
- Browser console
- Vercel deployment and function logs
Server-side errors surface in the Next.js process — your terminal locally, Vercel's function logs in production — because auth and data access both run there. Slow-query and connection metrics come from your database provider.
Convex dashboard logs carry everything that runs inside Convex, including Better Auth and email sending.
- Search documentation:
- Convex Docs
- kitcn — the Better Auth + Convex integration
-
Common error messages:
- Search the exact error message
- Check GitHub issues for the libraries
-
Contact support:
- Provide detailed error messages
- Include relevant configuration
- Describe steps to reproduce
Most issues are environment-related. Double-check every variable is set — and set on the right surface for your edition.