Free launch audit — a senior engineer reads your repo and tells you exactly what's blocking launch.Claim yours
Production engineering

Bolt.new works in preview and breaks in production: the five causes

Your Bolt app runs perfectly in the editor and fails the moment it is deployed. It is nearly always one of five things. Here is how to tell which, and the fix for each.

GEGen2ProdProduction engineering

6 min read

A browser window floating above a server rack, connected by a single frayed cable

Bolt.new runs your entire stack inside a browser tab. The dev server, the bundler, the Node runtime, even the "backend" — all of it executes in a WebContainer on your machine. That is why it feels instant, and it is also why the single most common message we get from Bolt users is some version of:

It works in Bolt. I deployed it to Netlify and now it doesn't.

Nothing is wrong with your app. The environment it was built in is not the environment it runs in, and Bolt never had to notice the difference. We have fixed enough of these to know that it is almost always one of five causes, so here they are, in the order to check them.

First, read the actual error

Before anything else, open the deployed site, open the browser console, and reproduce the failure. Then open the Netlify deploy log for the same build. You are looking for one of these shapes:

What you seeWhich cause below
undefined where a config value should be, or Cannot read properties of undefined1 — environment variables
A request to /.netlify/functions/... returns 404 or 5022 — the function was never deployed
blocked by CORS policy in the console3 — CORS
Works logged out, breaks logged in, or the reverse4 — Supabase and auth
The build itself fails, or deploys a blank page5 — the build step

If none of them match, skip to the end — it may be the sixth thing.

1. The environment variable exists in Bolt and nowhere else

In the WebContainer, Bolt keeps your .env values in memory and injects them into the dev server. On Netlify, nothing is injected unless you put it there yourself. The code is fine; the value is undefined.

Check the Netlify dashboard under Site configuration → Environment variables and compare it against the .env in your project. Every variable the app reads has to be there, spelled identically.

Two subtleties catch people out:

  • Prefixes control exposure. In a Vite project, only variables prefixed VITE_ are bundled into the client. If your code reads import.meta.env.API_URL it will always be undefined in the browser; rename it to VITE_API_URL. The reverse is worse — see cause 4.
  • Netlify Functions read process.env, not import.meta.env. A variable set in the dashboard is available to functions at runtime, but the client only gets it if it was present at build time with the right prefix.

2. The function ran in preview but was never deployed

Bolt will happily write a netlify/functions/send-email.ts and call it from the client, and in the WebContainer the call works because Bolt simulates the function locally. On Netlify, the function only exists if the build picked it up.

Check the deploy log for a line listing the functions that were bundled. If yours is not there:

  • The directory is wrong. Netlify looks in netlify/functions by default; if Bolt put them in functions/ or src/functions/, either move them or set the path in netlify.toml:

    [functions]
      directory = "netlify/functions"
  • The function imports something that is not in package.json. Bolt sometimes uses a dependency without adding it. The local simulation forgives this; the bundler does not.

  • The file is TypeScript with a syntax the bundler rejects. The log will say so.

The same applies if Bolt used Supabase Edge Functions instead: they run in preview, but they have to be deployed to your Supabase project separately, which Bolt's publish button does not do.

3. CORS was open in preview and is not in production

In the WebContainer, the client and the "server" share an origin, so CORS never comes up. Deployed, your client is on your-site.netlify.app and it is calling something else — a third-party API, a Supabase function, a backend on another host — and that something is refusing the origin.

Read the error carefully. It names the origin that was blocked and the header that was missing. Then fix it at the right layer:

  • Your own function should set Access-Control-Allow-Origin to your domain, not *. Bolt tends to generate *, which "works" and also means any site on the internet can call your function with your users' cookies.
  • A third-party API that blocks browsers is telling you it should be called from a server. Move the call into a Netlify Function and call that instead. This is also where the key goes (see cause 1).

4. Supabase behaves differently for a real user

This is the one that looks like a random bug. Everything works when you test logged out, or it works when you are logged in, and breaks for a second account.

The cause is Row Level Security. In preview you were probably testing as the same user who created every row, or with RLS not yet enabled, so every query returned everything. In production a different user hits a policy — or the absence of one — and gets an empty result or a permission denied.

Check every table:

select tablename, rowsecurity as rls_enabled
from pg_tables
where schemaname = 'public'
order by rls_enabled, tablename;

A table with RLS off is readable and writable by anyone with your public anon key, which is everyone. A table with RLS on and no policy returns nothing. What you want is RLS on with a policy per operation, and the five RLS mistakes we find in almost every AI-generated app walks through the exact SQL.

5. The build step Bolt never ran

In the WebContainer you were running a dev server the whole time. A production build — vite build — is a different process with stricter rules, and Bolt may never have executed it once.

Read the deploy log from the top. The usual culprits:

  • TypeScript errors. The dev server tolerates them; the build does not. Fix them or, as a temporary measure, exclude the type check from the build command — and put it back before you call this done.
  • Case-sensitive imports. import Button from './button' finds Button.tsx on a Mac and fails on Netlify's Linux builders.
  • A build command or publish directory that doesn't match the framework. For a Vite project it should be npm run build and dist. Bolt sometimes writes build or leaves the field empty.
  • Node version. Set it explicitly in netlify.toml or with a .nvmrc, so the builder uses the version your dependencies expect.

The sixth thing: it worked, and then it stopped

If the app deployed fine and broke later without a code change, look at whatever changed underneath it. The most common: a Supabase project paused for inactivity on the free tier, an API key rotated or hitting its quota, or a domain's certificate failing to renew. None of these are Bolt's fault either, and all of them are things a working app needs someone watching for — monitoring and alerts is how you stop finding out from customers.

Making it not happen again

Every one of the five is a symptom of one gap: the app has never been built and run outside the editor in a controlled way. The permanent fix is small and mechanical:

  1. Export to GitHub and connect Netlify to the repo instead of publishing from Bolt.
  2. Add a netlify.toml that states the build command, publish directory, functions directory and Node version explicitly.
  3. Turn on deploy previews, so every pull request gets its own URL and you see the production build before it replaces the live site.
  4. Put every environment variable in the dashboard, with the secrets in functions and only the genuinely public values under VITE_.

You can keep working in Bolt after this; it pulls from the same repo. What changes is that "it works in Bolt" stops being the last check before customers see it.

If you would rather have someone go through your Bolt project and hand you the list, the free launch audit is exactly that — a senior engineer, your repo, a ranked report within 48 hours, and a fixed price against each fix. What we fix in Bolt.new apps covers the rest of what we find.

Share

Your AI-built product deserves a real launch.

Start with the free audit. In 48 hours you'll know what's broken, what each fix costs, and whether it's even worth doing — before you've spent a dollar.

Free audit, no card · Fixed price before we start · Your code stays yours