TypeError: Cannot read properties of undefined (reading 'trim') — at line: process.env.NEXT_PUBLIC_SUPABASE_URL
appears when:On the production URL after a successful deploy; localhost:3000 and preview both work.
App works locally but not in production
Five buckets cover nine out of ten production-only bugs: missing env vars, hardcoded localhost URLs, CORS allow-lists, SSR mismatch, and case-sensitive file paths. Classify yours in 10 minutes — then follow the matching fix page.
When an app works locally but not in production, the gap is almost always environment-specific: an env var is missing, an absolute URL is hardcoded to localhost, CORS still allow-lists only the dev origin, a server component reads window, or a file path is case-wrong for Linux. Open Vercel function logs, match the error to one of those five buckets, apply the matching fix.
Quick fix for app works locally not in production
01// lib/env.ts — preflight env mapping for local + production02import { z } from "zod";03 04const EnvSchema = z.object({05 NEXT_PUBLIC_SITE_URL: z.string().url(),06 NEXT_PUBLIC_SUPABASE_URL: z.string().url(),07 NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().min(10),08 DATABASE_URL: z.string().url(),09 STRIPE_SECRET_KEY: z.string().startsWith("sk_"),10});11 12// Throws at module load if any var is missing.13// Add NEXT_PUBLIC_ prefix for anything the browser needs.14export const env = EnvSchema.parse(process.env);15 16// Use relative URLs — never http://localhost:3000/api/...17// const res = await fetch("/api/users");Deeper fixes when the quick fix fails
01 · Map env vars correctly between local and production
Every variable the browser reads must start with NEXT_PUBLIC_. Server-only secrets stay un-prefixed. Add all of them to Vercel → Settings → Environment Variables, scoped to Production. Trigger a fresh deploy — NEXT_PUBLIC_ values bake in at build time.
01// Run in CI before build to catch missing env vars02import { env } from "../lib/env";03 04console.log("Env preflight passed:");05console.log(" NEXT_PUBLIC_SITE_URL =", env.NEXT_PUBLIC_SITE_URL);06console.log(" NEXT_PUBLIC_SUPABASE_URL =", env.NEXT_PUBLIC_SUPABASE_URL);07console.log(" DATABASE_URL =", env.DATABASE_URL.replace(/:[^@]+@/, ":***@"));08 09// package.json10// "scripts": { "prebuild": "tsx scripts/env-preflight.ts" }02 · Replace hardcoded URLs with runtime detection
When you need an absolute URL (OAuth callbacks, email links), derive it from the current origin on the client or VERCEL_URL on the server.
01export function getBaseUrl(): string {02 if (typeof window !== "undefined") {03 return window.location.origin;04 }05 if (process.env.NEXT_PUBLIC_SITE_URL) {06 return process.env.NEXT_PUBLIC_SITE_URL;07 }08 if (process.env.VERCEL_URL) {09 return `https://${process.env.VERCEL_URL}`;10 }11 return "http://localhost:3000";12}13 14// Usage: fetch(`${getBaseUrl()}/api/auth/callback`)15// Or better — use relative URLs everywhere: fetch("/api/...")03 · Guard case-sensitive imports before merge
macOS is case-insensitive; Linux is not. Run a fresh Linux build as part of CI to catch Module not found before Vercel does.
01# Build on Linux in CI — same case sensitivity as Vercel02name: build03on: [push]04jobs:05 build:06 runs-on: ubuntu-latest07 steps:08 - uses: actions/checkout@v409 - uses: actions/setup-node@v410 with: { node-version: "20" }11 - run: npm ci12 - run: npm run buildWhy AI-built apps hit app works locally not in production
The development environment forgives things production cannot. Your laptop runs macOS with a case-insensitive filesystem, Vercel builds on Linux with a case-sensitive one — an import Button from "./button"that resolves locally fails the Linux build as “Module not found”. Your localhost has every env var set because they live in .env.local, which never uploaded to Vercel. Your browser on localhost reaches Supabase from localhost:3000, which Supabase still has in its allowed origins list because the AI builder added it during preview.
AI builders make this worse by hardcoding. Lovable, Bolt, and Cursor generate code that refers to http://localhost:3000/api/... instead of a relative /api/.... They include window.localStorage in a component that later becomes a server component when you restructure the app. They wire OAuth callbacks to http://localhost:3000/auth/callback because that is what worked in preview. None of it fails locally. All of it fails the moment the domain changes.
The structural fix is to make production less different from dev. Test on the actual production URL, not a preview. Use vercel env pull to sync env vars down locally so the two environments stay aligned. Write tests that run against vercel dev so the build pipeline is exercised before every merge. And ship an env schema that fails the build when any required variable is missing, so you find out about the gap at deploy time rather than at 3am when a user files a bug.
Diagnose app works locally not in production by failure mode
Match the symptom in your console or function log to a bucket, then open the matching dedicated fix page.
| Symptom | Bucket | Matching fix |
|---|---|---|
| process.env.X undefined; 500 in log | Env var | /fix/env-variables-not-loading-vercel |
| Blocked by CORS policy | CORS | /fix/cors-error-production-only |
| Hydration failed, text mismatch | SSR mismatch | /fix/nextjs-hydration-error-production |
| White screen, no error, console empty | Bundle load failure | /fix/white-screen-after-vercel-deploy |
| 500 on every route after deploy | Runtime crash | /fix/500-error-after-deploy |
app works locally not in production by AI builder
How often each AI builder ships this error and the pattern that produces it.
| Builder | Frequency | Pattern |
|---|---|---|
| Lovable | High | NEXT_PUBLIC_* missing on Vercel; Cannot read properties of undefined at render |
| Bolt.new | High | Hardcoded StackBlitz preview URLs in OAuth callbacks and webhook endpoints |
| v0 | Medium | Server component reads window or localStorage; hydration mismatch in prod only |
| Cursor | Medium | Mixed-case import paths; works on macOS, Module not found on Linux |
| Claude Code | Low | Occasionally forgets to push .env additions to Vercel after adding locally |
Related errors we fix
Stop app works locally not in production recurring in AI-built apps
- →Ship a zod env schema at module load; prefix browser vars with NEXT_PUBLIC_.
- →Use relative URLs (/api/...) — never hardcode http://localhost:3000 anywhere.
- →Run a Linux build in GitHub Actions before merge — matches Vercel case sensitivity.
- →Use vercel env pull to keep local .env.local aligned with production.
- →Visit the production URL in incognito after every deploy — not the preview, not staging.
Still stuck with app works locally not in production?
If you have fixed one bucket and the next broke, or the bug reproduces for some users and not others, a fixed-price engagement ships this week:
- →Two fixes deep and it still breaks
- →Bug reproduces for some users and not others
- →Stack is Supabase + Stripe + OAuth — CORS and URLs tangled
- →You need a launch pass before real users see this
app works locally not in production questions
Why does my app work on localhost but not in production?+
How do I diagnose 'app works locally not in production'?+
What breaks first when an AI-built app goes to production?+
Can I reproduce 'app works locally not in production' on my laptop?+
How much does a 'works locally not in production' bug cost to fix?+
Ship the fix. Keep the fix.
Emergency Triage restores service in 48 hours. Break the Fix Loop rebuilds CI so this error cannot ship again.
Hyder Shah leads Afterbuild Labs, shipping production rescues for apps built in Lovable, Bolt.new, Cursor, Replit, v0, and Base44. our rescue methodology.
app works locally not in production experts
If this problem keeps coming back, you probably need ongoing expertise in the underlying stack.