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, throughimport.meta.env.VITE_SUPABASE_URLand 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 directorydist. - 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).