Bolt.new generates a working app in minutes. The deploy step is where most founders leave the door open: exposed API keys, an RLS-less Supabase database, and secrets stored as VITE_ variables that end up plain text in your JavaScript bundle.
This guide walks through the specific steps to deploy your Bolt app securely. Each step targets a real vulnerability pattern that shows up regularly in CheckYourVibe scans.
TL;DR
The two highest-risk Bolt deployment mistakes are VITE_-prefixed secrets (baked into your JS bundle) and Supabase tables with no RLS policies (publicly readable by default). Fix those two, move API calls server-side, and scan your deployed URL before sharing it.
Why Bolt Deployment Has Security Gaps
Bolt generates functional code fast. It uses Vite for bundling and Supabase for the database, both solid choices. The issue is that Bolt optimizes for speed of development, not security hardening.
Two patterns show up in nearly every Bolt app we scan:
- VITE_ variable exposure: Vite's convention is to prefix client-accessible variables with
VITE_. Bolt often generatesVITE_SUPABASE_SERVICE_ROLE_KEYorVITE_OPENAI_API_KEY. Those values end up as plaintext in your deployed JavaScript, readable in any browser. - No Supabase RLS: Bolt generates tables and inserts data, but enabling Row Level Security requires explicit configuration. Without RLS, your Supabase anon key (which Bolt puts in the frontend) grants read/write access to every row.
The Supabase service_role key bypasses all RLS policies. If Bolt generated code that uses this key client-side (look for service_role in your src/ directory), you have full database access exposed to any visitor.
Step-by-Step: Secure Bolt Deployment
Audit your environment variables for VITE_ exposure
Open your Bolt project's .env file or check Netlify's environment variable panel. Any variable starting with VITE_ is a client-side variable. In Vite, that prefix is how you explicitly opt into browser exposure.
For a Bolt app, the dangerous ones are:
VITE_SUPABASE_SERVICE_ROLE_KEY(full database bypass)VITE_OPENAI_API_KEY(pays your OpenAI bill)VITE_STRIPE_SECRET_KEY(processes real payments)
The VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY are intentionally public. The anon key is safe to ship client-side as long as your RLS policies are correct.
grep -r "VITE_" . --include="*.env" --include="*.ts" --include="*.tsx" --include="*.js" | grep -v "node_modules"
Any output that is not VITE_SUPABASE_URL or VITE_SUPABASE_ANON_KEY is a secret that needs to move server-side.
Enable Supabase RLS on every table
Go to your Supabase dashboard, open Table Editor, and click each table. Look for "Row Level Security" in the settings panel.
Turn it on for every table. Then add policies. The simplest policy for user data:
-- Users can only read their own rows
CREATE POLICY "Users read own data" ON your_table
FOR SELECT USING (auth.uid() = user_id);
-- Users can only insert their own rows
CREATE POLICY "Users insert own data" ON your_table
FOR INSERT WITH CHECK (auth.uid() = user_id);
If you have a table that should be publicly readable (like blog posts or product listings), make it an explicit policy rather than leaving the table open by default.
Move secret API calls into Netlify Functions
Any API call that uses a secret key belongs on the server. Netlify Functions run server-side and can access environment variables that the browser never sees.
Create a netlify/functions/ directory in your project root, then write a function that wraps your API call:
const OpenAI = require("openai");
exports.handler = async (event) => {
const { prompt } = JSON.parse(event.body);
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const response = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: prompt }],
});
return {
statusCode: 200,
body: JSON.stringify({ reply: response.choices[0].message.content }),
};
};
Your frontend calls /.netlify/functions/ask-openai instead of the OpenAI API directly. The OPENAI_API_KEY environment variable (no VITE_ prefix) never leaves Netlify's servers.
Set environment variables in the Netlify dashboard correctly
In Netlify: Site Configuration > Environment variables > Add variable.
For each secret, enter it without the VITE_ prefix. Netlify stores these server-only by default. The build process can read them in Netlify Functions, but they don't appear in your client bundle.
Correct setup:
| Variable name | Where it's used |
|---|---|
SUPABASE_URL | Netlify Functions only |
SUPABASE_SERVICE_ROLE_KEY | Netlify Functions only |
OPENAI_API_KEY | Netlify Functions only |
VITE_SUPABASE_URL | Browser (safe, it's a public URL) |
VITE_SUPABASE_ANON_KEY | Browser (safe when RLS is on) |
If you need a variable in both browser and server code, create two versions: one with the prefix (browser) and one without (server). Only do this for truly non-secret values.
Run a pre-launch security scan
Before you share the URL, scan the deployed app. CheckYourVibe checks your JavaScript bundle for exposed key patterns, tests your Supabase endpoints for open access, and reviews your HTTP response headers for missing security settings.
1. Open your deployed app
2. Press F12 → Sources tab
3. Search (Ctrl+F) for: sk_live_, sk_proj_, AKIA, eyJhbGci, service_role
4. Any hits are secrets in your bundle. Stop and fix before continuing.
After the scan, check these before sharing:
- No secret key patterns in bundle (scanner or manual check above)
- Supabase RLS enabled and tested (try accessing your data as an unauthenticated user)
- Response headers include
X-Frame-Options,X-Content-Type-Options - Authentication routes actually require login (test by going to a protected URL without being logged in)
Common Bolt Deployment Security Mistakes
Using the service_role key anywhere in client code. Bolt occasionally generates Supabase clients initialized with the service_role key. This key bypasses every RLS policy. Search your src/ directory for "service_role" before deploying.
Committing your .env file to GitHub. Bolt doesn't always add .env to .gitignore. Check before your first push: run git status and confirm .env is not in the list. If it already was committed, rotate every key in it. The values are in your git history.
Deploying without testing authenticated vs unauthenticated access. Open an incognito window and visit each page of your app. Any data that should require login should fail to load. If you can see user records or admin pages without being logged in, your route guards are missing.
After enabling RLS, test it by creating a second test account and confirming you can't read the first account's data. Supabase's SQL Editor makes this easy: run a SELECT on your table and check which rows come back.
Quick Pre-Launch Checklist
Why does VITE_ in my variable name expose my API key?
Vite bakes any variable prefixed VITE_ into the client-side JavaScript bundle at build time. Even when the variable is stored in Netlify's secure environment variables panel, the value gets written as plain text into your deployed .js files. Anyone can read it by opening browser DevTools > Sources and searching for the key.
How do I deploy a Bolt.new app to Netlify securely?
Connect your GitHub repo to Netlify, set environment variables in Site Configuration > Environment variables (without VITE_ prefix), move any API call that uses a secret into a Netlify Function, and enable Supabase RLS before going live. Run CheckYourVibe on the deployed URL to catch anything you missed.
What Supabase settings do I need before launching a Bolt app?
Enable Row Level Security on every table. Confirm you are not using the service_role key in client-side code (Bolt sometimes generates this). Use the anon key for browser code and add RLS policies that grant access only to authenticated users who own the data.
Can I use Bolt.new for a production app with real user data?
Yes, but you need to add security controls that Bolt doesn't generate automatically. The main gaps are: secrets in environment variables (not VITE_-prefixed), Supabase RLS on all tables, server-side functions for third-party API calls, and input validation. Plan 1-2 hours of hardening for a typical Bolt app.
What happens if I skip Supabase RLS?
Without RLS policies, every table in your database is publicly readable and writable via the Supabase API using your anon key. Anyone who finds your app's network requests in DevTools can query, update, or delete your users' data.