Unstack Pro Docs

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:

  1. Restart the dev server: Stop and restart bun dev
  2. Clear browser cookies: Especially for auth issues
  3. Check environment variables: Ensure all are set correctly
  1. Check the database is reachable: psql "$DATABASE_URL" -c 'select 1'
  2. 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
  1. Restart Convex: Stop and restart bun convex dev
  2. 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:

  1. Check email verification:

    • Users must verify their email before signing in
    • Check if verification email was sent
    • Try resending verification email
  2. 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 32

Changing BETTER_AUTH_SECRET invalidates every existing session, so everyone has to sign in again.

Make sure Convex is running and connected:

bun convex dev

BETTER_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 32

If you rotated BETTER_AUTH_SECRET, regenerate JWKS too — the private key is encrypted with it.

  1. 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
  2. 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:

  1. Check cookie settings:

    • Cookies require HTTPS in production
    • Ensure NEXT_PUBLIC_APP_URL uses the correct protocol
  2. Verify secret consistency:

    • BETTER_AUTH_SECRET must match everywhere
    • If you changed it, all existing sessions are invalid
  3. 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:

  1. Check device time:

    • TOTP codes are time-based
    • Device clock must be accurate
    • Enable automatic time sync
  2. Try backup codes:

    • Each backup code works once
    • 12 codes are generated during setup
  3. Wait for next code:

    • Codes refresh every 30 seconds
    • If timing is close, wait for next code
  4. 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:

  1. Check PASSKEY_RP_ID:

    • Must match your domain exactly
    • No protocol (just yourdomain.com)
    • For local: use localhost
  2. Ensure HTTPS:

    • Passkeys require HTTPS (except localhost)
    • Check SSL certificate is valid
  3. Check browser support:

    • Use Chrome, Safari, Firefox, or Edge
    • Must be recent version
    • WebAuthn must be enabled
  4. 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:

  1. 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"
  1. Verify EMAIL_FROM:

    • Must use verified domain
    • Format: "Name <email@domain.com>"
  2. Check Resend dashboard:

    • View sent emails
    • Check for errors
    • Verify domain status
  3. Check spam folder:

    • Verification emails often land in spam
    • Mark as "not spam" to improve delivery
  4. Test mode limitations:

    • In test mode, only verified emails receive mail
    • Use your own verified email for testing

Emails Going to Spam

Solutions:

  1. Verify your domain:

    • Add DNS records in Resend
    • Wait for verification (can take hours)
  2. Configure SPF/DKIM/DMARC:

    • Resend provides these records
    • Add all required DNS entries
  3. Use proper sender address:

    • Use subdomain like mail.yourdomain.com
    • Avoid generic addresses
  4. 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 migrate

Check Convex is running:

bun convex dev

Verify 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 dev

Schema 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 migrate

Never 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:migrate

Auth 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:migrate

A 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 dev

Redeploy once the schema is valid:

bunx convex deploy --yes

Auth tables out of date after editing convex/betterAuth/auth.ts:

bun scripts/generate-auth-schema.ts

Keep 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:

  1. Check .env.local exists:

    ls -la .env.local
  2. Verify variable names:

    • Case-sensitive
    • No spaces around =
    • No quotes issues
  3. Restart dev server:

    • Environment changes require restart
    • Stop and restart bun dev
  4. 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 migrate

There 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:

  1. 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.

  1. Generate new secret:

    openssl rand -base64 32

    Update everywhere.

  2. Clear all sessions:

    • Old sessions won't work with new secret
    • Users need to sign in again

Build Issues

Build Fails

Symptoms:

  • bun run build fails
  • TypeScript errors
  • Module not found

Solutions:

  1. Check TypeScript errors:

    bunx tsgo --noEmit
  2. Install dependencies:

    rm -rf node_modules
    bun install
  3. Clear Next.js cache:

    rm -rf .next
    bun run build
  4. Check import paths:

    • Use @/ alias correctly
    • Ensure files exist

Production Deployment Fails

Solutions:

  1. Check all env vars are set:

    • In Vercel dashboard
    • For production environment
  2. Match local build:

    bun run build

    Fix any errors locally first.

  3. Check Vercel logs:

    • Deployment logs show errors
    • Function logs show runtime errors
  4. Remember that vercel.json overrides 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:

  1. Check user is authenticated:

    • Must be logged in
    • Email must be verified
  2. Check Autumn is configured:

    • AUTUMN_SECRET_KEY must be set
    • Or billing must be removed

Can't Add Members

Solutions:

  1. Check Pro plan:

    • Organization must be on Pro plan
    • Upgrade first, then add members
  2. Check permissions:

    • Only owners and admins can add members
    • Verify user role
  3. Check billing status:

    • Subscription must be active
    • No payment failures

Invitations Not Working

Solutions:

  1. Check email is sending:

    • Verify Resend is configured
    • Check Resend logs
  2. Check invitation status:

    • View in organization dashboard
    • Resend if needed
  3. Check user account:

    • Invitee may need to create account first
    • Email must match invitation

Admin Panel Issues

Can't Access Admin

Solutions:

  1. Check admin role:

    • User must have admin (or owner) in its role value
    • role is a comma-separated string, so admin,user counts
  2. 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:

  1. Check permissions:

    • Can't ban yourself
    • Can't impersonate other admins
    • Can't delete your own account
  2. Check user exists:

    • User may have been deleted
    • Refresh the page

Performance Issues

Slow Page Loads

Solutions:

  1. Use pagination:

    • Don't load all records at once
    • Implement cursor-based pagination
  2. 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 analyze against DATABASE_URL shows whether one is being used
  • Watch for N+1 reads: use Drizzle's with to 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:

  1. Check for memory leaks:

    • Monitor with DevTools
    • Check for unsubscribed listeners
  2. Optimize images:

    • Use Next.js Image component
    • Compress large images

Getting More Help

If you're still stuck:

  1. 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.

  1. Search documentation:
  1. Common error messages:

    • Search the exact error message
    • Check GitHub issues for the libraries
  2. 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.

On this page