BusiRocket

Article

Bolt.new app shows a blank page after deploying to Netlify or Vercel: how to find the cause

Published on

In Bolt the app works. You publish to Netlify, or download the project and deploy it to Vercel, open the address, and get a white page. No error, no layout, nothing. Bolt's own Netlify guide covers how to connect and publish, and says nothing about this.

A blank page is not one problem. It is what a React app looks like whenever it fails before drawing its first screen, and there are four usual reasons. The good news is that the browser tells you which one in a single line.

Step one: read the console

Open the deployed site, press F12 (or right-click, Inspect), and go to the Console tab. Reload. The first red line is the one that matters; the rest are usually consequences of it. Then look at the Network tab, filtered to JS, and check that the main file, something like index-a1b2c3.js, answered 200.

Match what you see to one of the sections below.

1. "supabaseUrl is required" or another undefined variable

The most common one. The console shows supabaseUrl is required, or Cannot read properties of undefined, or a request to undefined/rest/v1/....

Bolt projects are built with Vite, and Vite treats environment variables differently from a server:

  • Only variables whose name starts with VITE_ reach the browser code, through import.meta.env.VITE_SUPABASE_URL and similar.
  • They are written into the JavaScript at build time. The deployed file contains the literal value, not a lookup.

So the .env file Bolt used in the preview is not on Netlify or Vercel unless you put the same variables there. Add each one in the host's settings (Netlify: Site configuration, Environment variables; Vercel: Settings, Environment Variables), with the exact VITE_ name from your code, and then redeploy. Adding a variable without rebuilding changes nothing, because the old build still has undefined baked in.

Only put public keys in VITE_ variables. The Supabase anon key is meant to be public; a Stripe secret key or an OpenAI key is not, and anything with the VITE_ prefix can be read by anyone who opens the site. If the app needs a secret, it belongs in a server function, not in the frontend.

2. "Expected a JavaScript module script" or "Failed to load module script"

The console says the server answered a script with a MIME type of text/html, or the Network tab shows the main JavaScript file returning 404.

The host is serving the wrong folder. npm run build writes the finished site to dist. If the host publishes the repository root instead, it serves the source index.html, which points at /src/main.tsx, a file browsers cannot run.

  • Netlify: build command npm run build, publish directory dist.
  • Vercel: Framework Preset Vite, Output Directory dist.
  • Drag-and-drop or FTP: upload the contents of dist, not the project.

The same message also appears when a rewrite rule is too aggressive. A Netlify rule written as /* /index.html 200!, with the exclamation mark, answers every request, JavaScript included, with the HTML page. Drop the !.

3. Assets requested from the wrong path

The Network tab shows /assets/index-a1b2c3.js returning 404 while the page itself loaded. This happens when the site lives under a subfolder, for example https://user.github.io/my-app/, and the build assumed it was at the root.

Set base in vite.config.ts to the subfolder:

export default defineConfig({
  base: '/my-app/',
  plugins: [react()],
})

If the app uses React Router, give the router the same base with basename="/my-app" or the first route will not match. For a site on its own domain or a netlify.app address, base stays at the default /.

4. A crash that only production hits

The console shows a real JavaScript error from your own code: x.map is not a function, Cannot read properties of null, a failed JSON.parse.

Since React 16, an error during rendering that nothing catches unmounts the whole app, and an unmounted app is a white page. The preview often did not hit the error because it had different data: a logged-in test user, a filled table, a local mock. In production the first visitor is logged out, the table is empty, or an API call is blocked, and the code assumes something that is not there.

Read the error, find the component in the stack trace (production builds minify names; Vite's build.sourcemap: true makes them readable), and guard the case. Then add an error boundary around the app so the next unexpected error shows a message instead of nothing.

When the console is empty

If there is no error at all and the page is still blank, check two things. View the page source: if <div id="root"></div> is there but no <script type="module"> tag, the build did not run and the host is serving a placeholder. And open the site in a private window: a stale service worker from an earlier deploy can keep serving an old, broken shell to your own browser while new visitors see the real thing.

After the white page is gone

Two problems tend to show up next. Refreshing an inner page returns 404, which is the host not knowing it is serving a single-page app: the fix per host is the same for Bolt as for Lovable. And login sends people to localhost, which is four Supabase settings.

All of these are part of what usually breaks between preview and production. If you are fixing the third one in a week and the app still is not ready for real users, our vibe code rescue audit reads it end to end: 450 EUR, 48 working hours, a written report of what is broken and what each fix costs, with the fee deducted if you continue into the rescue (1,500-6,000 EUR, fixed scope).

Next step

Does this problem sound like yours?

Tell us about it and we will say how we would approach it, in what order and at what cost.