afterbuild/ops
ERR-ENV/Vercel · Deployment
ERR-ENV
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.

Last updated 17 April 2026 · 6 min read · By Hyder Shah
Direct answer

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

lib/env.ts
typescript
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");
Validate process.env at startup and prefix browser-needed vars with NEXT_PUBLIC_ so Vercel surfaces the gap at build time.

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.

scripts/env-preflight.ts
typescript
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" }
Run env preflight before build; a missing var fails the build, not the request.

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.

lib/base-url.ts
typescript
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/...")
Detect the origin at runtime. Never hardcode http://localhost:3000.

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.

.github/workflows/ci.yml
bash
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 build
Fresh Linux build in CI surfaces case-sensitive import errors before Vercel does.

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

SymptomBucketMatching fix
process.env.X undefined; 500 in logEnv var/fix/env-variables-not-loading-vercel
Blocked by CORS policyCORS/fix/cors-error-production-only
Hydration failed, text mismatchSSR mismatch/fix/nextjs-hydration-error-production
White screen, no error, console emptyBundle load failure/fix/white-screen-after-vercel-deploy
500 on every route after deployRuntime 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.

AI builder × app works locally not in production
BuilderFrequencyPattern
LovableHighNEXT_PUBLIC_* missing on Vercel; Cannot read properties of undefined at render
Bolt.newHighHardcoded StackBlitz preview URLs in OAuth callbacks and webhook endpoints
v0MediumServer component reads window or localStorage; hydration mismatch in prod only
CursorMediumMixed-case import paths; works on macOS, Module not found on Linux
Claude CodeLowOccasionally 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

Still stuck with app works locally not in production?

Emergency triage · $299 · 48h turnaround
We restore service and write the root-cause report.

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
start the triage →

app works locally not in production questions

Why does my app work on localhost but not in production?+
Five recurring causes account for 90 percent of these reports. Environment variables not uploaded to Vercel or the wrong scope. Hardcoded localhost URLs in API calls or redirects. CORS policy on Supabase or a third-party API that only lists the dev origin. SSR-only code (window, localStorage, Date.now) running in a server component. And case-sensitive file paths that import fine on macOS and fail on the Linux build machine. Diagnose by reading the Vercel function log first.
How do I diagnose 'app works locally not in production'?+
Three tabs: Vercel Function logs, browser DevTools Console, and browser DevTools Network. The error always shows up in one of them. If the Network tab shows a 404 or a CORS error, look at the URL — hardcoded localhost is the usual cause. If the Console shows Hydration failed, a server component rendered something different than the client. If the Function log shows a 500, it is almost always an env var or a missing runtime dependency.
What breaks first when an AI-built app goes to production?+
In our rescue data, OAuth redirects rank first (localhost still in the callback), environment variables second, CORS third, hydration errors fourth, case-sensitive file paths fifth. The reason is that each of these is invisible locally — the dev server, the single developer's machine, and the preview environment all forgive them. Production is the first place where all five constraints apply simultaneously.
Can I reproduce 'app works locally not in production' on my laptop?+
Yes, usually. vercel dev runs the same build pipeline Vercel uses. vercel env pull syncs production env vars to a local .env.local. Running node --trace-warnings against the production build catches most runtime surprises. For CORS and OAuth, you need to test against the production URL itself — those cannot be reproduced locally because the browser origin differs.
How much does a 'works locally not in production' bug cost to fix?+
If it is one of the five common causes and you can read a log, zero. If it is a combination of causes or you cannot get past the first error, the Emergency Triage at $299 covers it in 48 hours. For teams launching in the next week who want a single pass over the whole surface, the Deploy-to-Production engagement catches every common pitfall before a user does.
Next step

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.

About the author

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.

Sources