Lovable app shows a blank white screen after deploy
The Lovable preview works, but the deployed URL is a blank white page. Missing VITE_ env vars are inlined at build time. Find them, set them on the host, and confirm the app mounts.
Updated 2026-10-15
A Lovable app that looks finished in the builder and then opens as a blank white page after deploy is usually not a broken React tree. The host returned the HTML shell, the browser downloaded the JavaScript, and that JavaScript threw before React could paint. The most common throw is a Supabase client, or another SDK, created at import time with a VITE_ variable that was never present when Vite built the site.
What the white page actually is
Open the deployed URL and view the page source. You should see a document that loads, with an empty root such as <div id="root"></div> and a script tag pointing at a file under /assets. The HTTP status is 200. That rules out the refresh 404 and the wrong output folder, both of which fail before the app shell is served.
Then open the browser console. A blank Lovable screen often reports that the Supabase URL is missing or is not an http(s) URL, or that a later call read a property of undefined. The network panel shows the document and the JS bundle succeeding. The failure is inside the bundle, which is why the host dashboard can still say the deployment is Ready.
How Vite exposes env vars
Vite does not pass your whole environment into the browser. Only variables whose names start with VITE_ are exposed on import.meta.env, and they are strings. Vite statically replaces those references when it builds, so the production bundle contains the value itself, not a lookup that happens when the page loads. A variable without the prefix stays unavailable in client code: import.meta.env.DB_PASSWORD is undefined even if the host has DB_PASSWORD set. The same is true of NEXT_PUBLIC_ names. Those are a Next.js convention. A Lovable app is Vite, so a key you added as NEXT_PUBLIC_SUPABASE_URL never arrives in import.meta.env.
Built-in values such as import.meta.env.MODE, PROD, DEV, and BASE_URL are always defined. Your own keys are not. For vite build, the mode is production, so Vite loads .env, .env.local, and .env.production from the project root. A variable that already exists in the environment when Vite starts wins over the same name in an env file. On Vercel, Netlify, or Cloudflare that means the project's environment settings override a committed .env.
const url = import.meta.env.VITE_SUPABASE_URL
const anonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
if (!url || !anonKey) {
throw new Error("Missing VITE_SUPABASE_URL or VITE_SUPABASE_ANON_KEY")
}Lovable projects do not all use those two names. Search the repo for import.meta.env and list every VITE_ identifier. Those strings are the contract. The host must define the same names, for the Production environment, before the build. Preview deployments use the Preview environment, which is a separate set of values.
Put the values on the host, then rebuild
On Vercel, open the project, then Settings, then Environment Variables. For each name you found:
- Use the exact name, including the
VITE_prefix. Paste the value with no quotes unless the quotes are part of the value. Vite keeps them. - Enable the variable for Production. Add Preview as well if you use preview URLs. Development is only the local
vercel devpull, not the production build. - Do not mark the anon key as a server-only secret in a way that strips it from the build environment. The build has to see it. Do not add the service role key at all.
- Save, then redeploy. Use the deployment menu and redeploy the latest commit. Editing the variables does not rewrite an existing bundle.
Netlify uses Site configuration, then Environment variables, with the same rule: the names must be present for the deploy context you are building, and you need a new deploy afterward. Cloudflare Pages sets them under Settings, then Variables and secrets, for Production.
If .env is gitignored, which it should be when it holds local secrets, the host has nothing to fall back on. Commit an .env.example that lists the names and leaves the values blank, so the next person can see the contract. Do not commit a filled .env to "make the white screen go away." Those values would still be public once Vite inlines them, and a service role key in git is a real leak.
Check the fix in the browser
After the new deployment is Ready:
- Hard-refresh the production URL. A cached blank document is rare, but an old service worker or an old HTML file can keep pointing at the previous asset names.
- The root element should now contain the app, not stay empty. The console should no longer report a missing Supabase URL or an undefined config object.
- In DevTools, open the built JavaScript and search for your project hostname, such as
supabase.co. Vite inlined it, so it appears as a string in the bundle. You should not search for or paste a service role key. If the hostname is absent, the rebuild did not seeVITE_SUPABASE_URL. - Click through login only after the screen renders. A white screen and a localhost redirect are different failures. The redirect one still paints the app.
If the console is clean and the page is still white, check the CSS file in the network panel. A 404 on the stylesheet leaves unstyled markup, which is not the same as an empty root, but it can look blank on a white background. That points back at the output folder or the asset paths, not at env vars.
Questions that come up next
Why does the Lovable preview work when the deployed site is blank?
The preview process has the env file Lovable used while you were editing. A host build only sees variables configured on that host, plus any env files that were actually committed. Vite then bakes the VITE_ values into the JavaScript bundle. If they were missing at build time, the browser receives a bundle where those values are undefined.
Will setting the variables in the Vercel dashboard fix the current deployment?
Not by itself. Vite replaces import.meta.env.VITE_* while it builds, so the value is already inside the old JavaScript files. Save the variables for the Production environment, then redeploy so Vite runs again.
Should I prefix the Supabase service role key with VITE_?
No. Anything prefixed with VITE_ is bundled into client code and can be read by anyone who loads the site. The browser client needs the project URL and the anon (publishable) key. Keep the service role key on a server, or do not ship it.