Works on localhost, breaks in production.
Your local run and your production deployment differ in five places: the public variables the browser bundle was built with, the URL sign-in returns to, the cookies a server route reads, which key runs where, and whether the hosted database got your migrations. Check them in this order and the cause is usually the first one that fails.
Same code, two different environments.
npm run dev, localhost: works vercel production: 500, a blank page, "Invalid API key", or sign-in that lands on localhost
What is different in production? Not your code. A production build freezes the public variables into the bundle, the deployment runs with the variables that existed when it was built, sign-in returns to whatever URL your project allows, and a server route only knows the user if the request's cookies reach it. Each of those is a setting, and each has a primary source below.
Five checks. In this order.
The public variables were not there when the bundle was built
Anything the browser needs, such as your Supabase URL and publishable key, is prefixed
NEXT_PUBLIC_, and
Next.js
says what that does: "it will be inlined into any JavaScript sent to the browser." And the
consequence: "variables will be frozen with the value evaluated at build time, so these
values need to be set appropriately when the project is built." So a variable added to the
dashboard after the build is not in the bundle.
Vercel:
"Any change you make to Environment Variables are not applied to previous deployments, they
only apply to new deployments." Check: the same names as in your .env.local
exist for the Production environment, then redeploy.
Vercel project > Settings > Environment Variables > Production NEXT_PUBLIC_SUPABASE_URL set NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY set then: Deployments > Redeploy
Sign-in comes back to localhost
Magic links and OAuth return the user to the URL the project allows, and a project that
only ever knew http://localhost:3000 sends production users there.
Supabase:
"we recommend setting the exact redirect URL path for your site URL in production."
Check: the Site URL is your production domain and the production callback path is in the
redirect allow list.
A server route has no session
Locally you were signed in through the browser; in production a server component,
route handler or middleware built a client with no cookies, and the database saw nobody.
Supabase:
"Server-Side Rendering (SSR) with Supabase requires cookie-based session storage." Then every
policy that compares auth.uid() fails, inserts get 42501 and selects come
back empty. Proven on a real PostgreSQL and explained on
the auth.uid() page.
The wrong key in the wrong place
The publishable key belongs in the browser and runs under row level security. The secret
key belongs on the server only, and even there it does not always bypass policies.
Supabase:
"A secret key bypasses RLS only when the request carries no user access token." Check: no
secret key in any NEXT_PUBLIC_ variable, and the server code that needs to
bypass policies is not forwarding the user's token.
The hosted database never got the migration
If local development runs against a local database, the table, column or policy you added there does not exist on the hosted project until your migration is applied to it. Check: open the hosted project's SQL editor and confirm the table and its policies are there; if row level security is on with no policy, every request is refused, which is the 42501 page.
Straight answers.
- Do I have to redeploy after adding an environment variable on Vercel?
- Yes. Vercel's documentation says changes to environment variables are not applied to previous deployments and only apply to new deployments, and Next.js inlines public variables into the bundle at build time.
- Why does sign-in send production users to localhost?
- Because the project's Site URL or redirect allow list still points at localhost. Supabase recommends setting the exact redirect URL path for your site URL in production.
- Which Supabase key goes in the browser?
- The publishable key, which runs under row level security. The secret key stays on the server, and it bypasses row level security only when the request carries no user access token.
Still broken after the five checks? Send us the error.
The free diagnosis reads the cause from your error and gives you the fix path in about a minute. No repo access, no card. If it needs hands-on work, one blocker is scoped and you pay only after it works.
Get the free diagnosisChecking policies on the hosted project? The free RLS auditor lists the holes worst first in about a minute.
The diagnosis lives at rescue.ticassociation.com; paste the error text and the stack you built with.