Bolt.new app login broken after deploy
The Bolt preview and the deployed site are two origins. A session on the preview is not a session on the deploy, and a Supabase project that accepted the preview URL will reject the new one until you allow it. Password login can also fail because the published build never received the project URL or the anon key.
What you see
- Email and password work on the Bolt preview URL. On the published URL the same form returns to login, says invalid credentials, or shows a blank auth screen.
- After submit, the address bar jumps to localhost, to the old preview host, or to a Supabase error page about a redirect URL, and no session is stored on the deployed site.
- The confirmation or password-reset email opens the preview app, or a host you do not use anymore. The deployed app stays logged out.
- The browser console on the deployed site says the Supabase URL or anon key is missing. The preview does not say that.
Why this happens
- Supabase Auth only redirects to URLs on the allow list. The preview was added. The deployed origin was not.
- Vite and Next inline public env vars at build time. A key that existed only in the Bolt preview is absent from the published bundle until you rebuild with the host's env.
- The user account lives in the Supabase project the preview used. A deploy pointed at a different project does not have that user.
Common causes
Site URL and redirect URLs still name the preview
In Supabase, Authentication, then URL configuration, Site URL is the default for email links. Redirect URLs are the allow list. If Site URL is http://localhost:5173 or the Bolt preview, confirmation and reset links go there. If the deployed origin is not on the allow list, signInWithOAuth and signInWithOtp stop at Supabase.
How to check. Read Site URL and the redirect list. The deployed https origin has to be there, including the callback path you actually use. A wildcard such as https://your-deployed-host/** covers that host's paths.
The published bundle has no project URL or anon key
Bolt can keep the preview running with env values that were never saved on the host you published to. The client then calls undefined.supabase.co, or sends an empty apikey, and the auth request fails before a password is checked.
How to check. On the deployed site, print the public Supabase URL and anon key the bundle was built with. undefined means that build did not have them. Set them on the host and rebuild. Restarting the old build does not insert them.
The code exchange runs on a different origin than the one that started login
OAuth and magic links use a PKCE verifier stored on the origin that started the request. If login starts on the deploy and the redirect lands on the preview, or the reverse, the callback cannot exchange the code. Password login does not use that redirect. If only Google or magic-link login is broken, this is the place to look.
How to check. Start login on the deployed URL and watch the address bar until it stops. Every hop after Supabase should be the deployed origin. A preview host in that chain is the bug.
Fix it
1. Put the public Supabase values in the published build
Use the project URL and the anon key. Do not put the service_role key in a VITE_ or NEXT_PUBLIC_ variable. Save the values where the publish step builds, then trigger a new build. Vite reads VITE_ variables. Next.js reads NEXT_PUBLIC_ variables.
VITE_SUPABASE_URL=https://YOUR_PROJECT.supabase.co VITE_SUPABASE_ANON_KEY=eyJ... # payload role is "anon" const supabase = createClient( import.meta.env.VITE_SUPABASE_URL, import.meta.env.VITE_SUPABASE_ANON_KEY, );
2. Allow the deployed origin in Supabase Auth
Set Site URL to the deployed origin. Add that origin's callback to Redirect URLs. You can leave the preview URL on the list if you still log in from the preview. Email links use Site URL when the request does not pass a redirectTo.
3. Send redirectTo from the origin the user is on
A hardcoded preview URL or localhost sends the user away from the deploy. Read the origin in the browser at the moment they click the button.
const { error } = await supabase.auth.signInWithOAuth({
provider: "google",
options: {
redirectTo: window.location.origin + "/auth/callback",
},
});4. Exchange the code on that same origin
The callback route has to run on the deployed host. exchangeCodeForSession consumes the code and stores the session for this origin. If this file never shipped, or it redirects to the preview before the exchange, the deploy never gets a session.
const url = new URL(request.url);
const code = url.searchParams.get("code");
if (code) {
const { error } = await supabase.auth.exchangeCodeForSession(code);
if (error) return NextResponse.redirect(url.origin + "/login");
}
return NextResponse.redirect(url.origin + "/");How to verify
- Open the deployed URL in a private window. Do not reuse the preview tab. The preview session is stored for the preview origin only, so it cannot prove the deploy works.
- Sign in with a password user that exists in this Supabase project. Reload the deployed site. The session should still be there, and the address bar should still be the deployed host.
- If you use email confirmation or a reset link, open the message and check the link host before you click. It should be the deployed origin. In the Supabase Auth logs, the redirect URL on that attempt matches the deployed callback.
- Repeat the OAuth or magic-link path once. A password form can pass while the redirect path is still pointed at the preview.
How the daily check catches it
The daily check does not see your Bolt preview session. It starts on the saved URL, creates or reuses the test account, and logs in. If the deployed form errors or never ends on a logged-in page, the run fails.
What ShipWatch looks for
- ShipWatch opens the URL you saved. That should be the deployed app, not the Bolt preview.
- It submits the signup or login form with the test account you stored. An error, a redirect to another host, or a page that is still logged out fails that step.
A public look, before the daily check
The free roast reads the public homepage and checks for a login link, plus keys sitting in the JavaScript. The free scan looks for a service_role JWT and for tables the anon key can read. Neither one types into your login form. They are a way to see what the deployed page is already exposing while you fix the redirect.
Prompt to paste
Login works on the Bolt preview and fails on the deployed URL. Set the Supabase Auth Site URL and redirect allow list to the deployed origin. Stop hardcoding the preview origin or localhost in redirectTo. Rebuild with the deployed environment's public Supabase URL and anon key. Do not put the service_role key in a VITE_ or NEXT_PUBLIC_ variable. Confirm a private window can sign in on the deployed origin and stay signed in after reload.