How to Deploy Your Lovable App to Production (2026)
Your Lovable app works in preview. But preview isn't production. Here's the step-by-step guide to deploying your Lovable app with proper security, environment config, and monitoring.
Your Lovable app works. The UI looks great. Authentication flows are smooth. Data saves and loads correctly. You showed it to a few friends and they were impressed.
Now you want to put it in front of real users. And this is where things get complicated, because the gap between "works in preview" and "ready for production" is wider than most people realize.
Lovable does an excellent job getting you from idea to working app. But it optimizes for speed and functionality, not for production hardening. The defaults that make development fast, permissive database access, client-side configuration, minimal error handling, become liabilities the moment real users start interacting with your app.
This guide walks through everything you need to do between "it works" and "it's deployed." If you've built something with Lovable and you're ready to ship it for real, this is the checklist.
Step 1: Lock Down Supabase Row-Level Security
This is the single most important thing you need to do before deploying a Lovable app. It's also the thing most people skip.
Lovable connects to Supabase for your database and authentication. By default, Supabase tables have Row-Level Security (RLS) disabled, which means anyone with your Supabase URL and anon key can read, write, and delete any data in your database. Your anon key is exposed in client-side code. It's not a secret. It's designed to be public. The only thing protecting your data is RLS.
A May 2025 security audit found that 170 out of 1,645 Lovable apps had security vulnerabilities exposing personal user data. The most common cause was missing or misconfigured Row-Level Security policies. This is not a theoretical risk.
Check if RLS is enabled on every table:
SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public';If rowsecurity is false for any table, enable it and add policies:
-- Enable RLS on the table
ALTER TABLE public.todos ENABLE ROW LEVEL SECURITY;
-- Allow users to only see their own data
CREATE POLICY "Users can view own todos"
ON public.todos
FOR SELECT
USING (auth.uid() = user_id);
-- Allow users to only insert their own data
CREATE POLICY "Users can insert own todos"
ON public.todos
FOR INSERT
WITH CHECK (auth.uid() = user_id);
-- Allow users to only update their own data
CREATE POLICY "Users can update own todos"
ON public.todos
FOR UPDATE
USING (auth.uid() = user_id);
-- Allow users to only delete their own data
CREATE POLICY "Users can delete own todos"
ON public.todos
FOR DELETE
USING (auth.uid() = user_id);Do this for every single table that contains user data. No exceptions.
Step 2: Audit Your Environment Variables
Lovable apps often have configuration values scattered between client-side code and environment variables. Before deploying, you need to sort out what belongs where.
These are safe to expose client-side (prefixed with VITE_ or NEXT_PUBLIC_):
- Supabase URL
- Supabase anon key
- Stripe publishable key
- PostHog/analytics keys
These must NEVER be in client-side code:
- Supabase service role key
- Stripe secret key
- API keys for OpenAI, Resend, or any third-party service
- Database connection strings
- Webhook secrets
Search your codebase for hardcoded secrets:
# Look for hardcoded keys in your source files
grep -r "sk_live\|sk_test\|service_role\|secret" src/ --include="*.ts" --include="*.tsx"If you find any secrets in client-side code, move them to server-side API routes or Edge Functions immediately.
Step 3: Move Sensitive Operations Server-Side
Lovable sometimes generates code that performs sensitive operations directly from the client. This is a security problem because anything running in the browser can be inspected, modified, and replayed by any user.
Common patterns to fix:
// BAD: Client-side admin check
const { data: profile } = await supabase
.from('profiles')
.select('is_admin')
.eq('id', user.id)
.single();
if (profile?.is_admin) {
// Admin action performed client-side
await supabase.from('users').delete().eq('id', targetUserId);
}The problem: a user can simply skip the if check using browser dev tools. Instead, move this to a server-side function:
// GOOD: Server-side admin check via Supabase Edge Function
// supabase/functions/admin-delete-user/index.ts
import { createClient } from '@supabase/supabase-js'
Deno.serve(async (req) => {
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
);
// Verify the requesting user is an admin
const authHeader = req.headers.get('Authorization')!;
const { data: { user } } = await supabase.auth.getUser(
authHeader.replace('Bearer ', '')
);
const { data: profile } = await supabase
.from('profiles')
.select('is_admin')
.eq('id', user?.id)
.single();
if (!profile?.is_admin) {
return new Response('Forbidden', { status: 403 });
}
const { targetUserId } = await req.json();
await supabase.from('users').delete().eq('id', targetUserId);
return new Response(JSON.stringify({ success: true }), {
headers: { 'Content-Type': 'application/json' },
});
});Step 4: Configure a Custom Domain
Deploying on a Lovable subdomain (e.g., your-app.lovable.app) is fine for testing, but for production you'll want a custom domain.
If deploying via Vercel or Netlify:
- Add your custom domain in the hosting provider's dashboard
- Update DNS records (typically a CNAME pointing to your provider)
- Wait for SSL certificate provisioning (automatic with most providers)
- Update your Supabase project's Site URL in Authentication > URL Configuration
- Update any OAuth redirect URLs to use the new domain
- Update
VITE_SITE_URLor equivalent environment variable
Don't forget to update your Supabase redirect URLs. If your auth callbacks still point to the old domain, users won't be able to log in after the domain switch.
Step 5: Add Error Monitoring
Lovable apps ship with zero error monitoring. When something breaks in production, you'll have no idea unless a user tells you. That's not acceptable for a production app.
Set up basic error tracking with Sentry (free tier handles most small apps):
// src/lib/sentry.ts
import * as Sentry from "@sentry/react";
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
environment: import.meta.env.MODE,
integrations: [
Sentry.browserTracingIntegration(),
],
tracesSampleRate: 0.1, // 10% of transactions
});
export default Sentry;Wrap your app's root component:
import * as Sentry from "@sentry/react";
const App = () => (
<Sentry.ErrorBoundary fallback={<ErrorFallback />}>
<YourApp />
</Sentry.ErrorBoundary>
);Step 6: Add Security Headers
Lovable's default deployment doesn't include security headers. These protect your users from common attacks like clickjacking, XSS, and MIME-type sniffing.
If deploying on Vercel, add a vercel.json:
{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
{ "key": "Strict-Transport-Security", "value": "max-age=63072000; includeSubDomains; preload" },
{ "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" }
]
}
]
}Step 7: Add Rate Limiting to Public Endpoints
If your Lovable app has any public-facing API routes or form submissions, they need rate limiting. Without it, a single bad actor can overwhelm your database, drain your API quotas, or abuse your email-sending capabilities.
For Supabase Edge Functions, you can implement basic rate limiting:
const rateLimitMap = new Map<string, { count: number; resetTime: number }>();
function checkRateLimit(ip: string, limit = 10, windowMs = 60000): boolean {
const now = Date.now();
const record = rateLimitMap.get(ip);
if (!record || now > record.resetTime) {
rateLimitMap.set(ip, { count: 1, resetTime: now + windowMs });
return true;
}
if (record.count >= limit) {
return false;
}
record.count++;
return true;
}Step 8: Test the Critical Paths
Before deploying, manually test every critical user flow:
- Sign up - Can a new user create an account?
- Log in - Can an existing user log in?
- Core action - Can users perform the main action your app exists for?
- Payment (if applicable) - Does Stripe checkout work end to end?
- Error states - What happens when the network is down? When the API returns an error?
- Mobile - Does everything work on a phone?
Open your browser's developer tools, go to the Network tab, and throttle the connection to "Slow 3G." Use your app for 5 minutes. You'll find bugs you never knew existed.
Step 9: Set Up Database Backups
Supabase provides automatic daily backups on paid plans. If you're on the free tier, set up your own backup process:
# Manual backup via pg_dump
pg_dump -h your-project-ref.supabase.co \
-U postgres \
-d postgres \
--clean --if-exists \
> backup_$(date +%Y%m%d).sqlFor production apps handling real user data, the Supabase Pro plan ($25/month) is a worthwhile investment for daily backups alone.
Step 10: Deploy Checklist
Before you hit deploy, run through this final checklist:
- RLS enabled on every table with appropriate policies
- No secrets in client-side code
- Sensitive operations moved server-side
- Custom domain configured with SSL
- Error monitoring set up (Sentry or equivalent)
- Security headers configured
- Rate limiting on public endpoints
- Critical user flows tested manually
- Database backups configured
- OAuth redirect URLs updated for production domain
- Environment variables set in hosting provider
-
console.logstatements removed from production code
The Faster Way
This checklist covers the most critical items, but there's a lot more that goes into production readiness: performance optimization, SEO meta tags, accessibility, proper loading states, input validation on every form.
You can work through all of it manually. Or you can automate the audit.
FinishKit scans your entire repo and generates a prioritized Finish Plan covering security, error handling, testing, and deploy configuration. It catches the gaps that Lovable leaves behind, so you can ship with confidence. Run a free scan and see what your app needs before it's ready.
If you built it with Lovable, you already have the hard part done. The app exists. Now make sure it's ready for the real world.