PreflightVibe logoPreflightVibe Free scan

Guides / add-security-headers-vercel-netlify-lovable

How to Add Security Headers to Your Vibe-Coded App (Vercel, Netlify, Lovable)

What each security header does in plain words, copy-paste configs for Vercel, Netlify and Cloudflare Pages, and what to do when your builder's hosting won't let you set them.

Why headers matter (and why your AI didn't add them)

Security headers are short instructions your server sends with every page. They tell the browser which scripts it may run, whether other sites may embed your app in a frame, and whether it must always use HTTPS. They don't fix bugs in your code, but they make whole classes of attacks (cross-site scripting, clickjacking, downgrade attacks) much harder.

Lovable, Bolt, Cursor and v0 generate app code. Headers are set by the host that serves the built files, so no change to your React components will add them. That's why so many vibe-coded apps ship with none.

What each header does

A starter CSP (test it first)

This policy fits a typical Vite + React + Supabase app that uses Google Fonts. Replace YOUR-PROJECT with your Supabase project ref.

default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: blob: https://YOUR-PROJECT.supabase.co; connect-src 'self' https://YOUR-PROJECT.supabase.co wss://YOUR-PROJECT.supabase.co; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'

Warning: a strict CSP will break anything it doesn't list. Common misses: Supabase Realtime (needs the wss:// entry), Supabase Storage images, analytics scripts and their beacon endpoints (for example Google Analytics needs googletagmanager.com in script-src and its collection domains in connect-src), Stripe.js (https://js.stripe.com in script-src and frame-src), inline scripts pasted into index.html, and custom fonts. Ship it first as Content-Security-Policy-Report-Only: the browser logs violations in the DevTools console without blocking anything. Click through sign-up, login, payments and uploads, add the origins you actually need, then rename the header to Content-Security-Policy.

Vercel: vercel.json

Common for Cursor and v0 apps. Put vercel.json in the project root (next to package.json) and redeploy. Next.js projects can use headers() in next.config.js instead.

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "Content-Security-Policy", "value": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: blob: https://YOUR-PROJECT.supabase.co; connect-src 'self' https://YOUR-PROJECT.supabase.co wss://YOUR-PROJECT.supabase.co; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'" },
        { "key": "X-Frame-Options", "value": "DENY" },
        { "key": "Strict-Transport-Security", "value": "max-age=63072000" },
        { "key": "X-Content-Type-Options", "value": "nosniff" },
        { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
        { "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" }
      ]
    }
  ]
}

Moving a single-page app (like a Lovable or Bolt export) to Vercel? Add "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] to the same file so deep links don't 404. Real files are still served first.

Netlify: netlify.toml

Put this in netlify.toml at the repo root:

[[headers]]
  for = "/*"
  [headers.values]
    Content-Security-Policy = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: blob: https://YOUR-PROJECT.supabase.co; connect-src 'self' https://YOUR-PROJECT.supabase.co wss://YOUR-PROJECT.supabase.co; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'"
    X-Frame-Options = "DENY"
    Strict-Transport-Security = "max-age=63072000"
    X-Content-Type-Options = "nosniff"
    Referrer-Policy = "strict-origin-when-cross-origin"
    Permissions-Policy = "camera=(), microphone=(), geolocation=()"

Netlify or Cloudflare Pages: _headers file

Both read a plain-text _headers file from the published output folder. In a Vite project, save it as public/_headers so the build copies it into dist. Each header line is indented under the path pattern.

/*
  Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: blob: https://YOUR-PROJECT.supabase.co; connect-src 'self' https://YOUR-PROJECT.supabase.co wss://YOUR-PROJECT.supabase.co; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'
  X-Frame-Options: DENY
  Strict-Transport-Security: max-age=63072000
  X-Content-Type-Options: nosniff
  Referrer-Policy: strict-origin-when-cross-origin
  Permissions-Policy: camera=(), microphone=(), geolocation=()

On both hosts these rules apply to static files. Responses from functions or server-rendered pages must set their own headers.

Lovable and other hosted builders

As far as we can tell, Lovable's built-in publishing does not offer a setting for custom response headers, and we're not aware of a hidden one. Asking the Lovable AI to "add security headers" may create a vercel.json or _headers file, but Lovable's own hosting won't read it. Your realistic options:

  1. Deploy the same code to Vercel or Netlify. Connect your project to GitHub, import the repo on Vercel or Netlify (build command npm run build, output folder dist for a Vite app), add one of the configs above, and point your custom domain there.
  2. Put a proxy in front. If your domain runs through Cloudflare's proxy, a Response Header Transform Rule can add the headers to every response. Check first that your host supports a proxied custom domain.
  3. Partial fallback: a CSP meta tag. A <meta http-equiv="Content-Security-Policy" content="..."> tag in index.html still restricts scripts and connections, but browsers ignore frame-ancestors and report-only mode in meta tags, and HSTS, X-Frame-Options and nosniff can't be set this way.

The same logic applies to Bolt, Replit and others: find out which host actually serves your app. If it's Netlify or Vercel, the files above work. If it's the builder's own hosting and there's no header setting, use one of the options above.

Check that it worked

After redeploying, check the live custom domain, not just the preview URL:

curl -sI https://your-domain.com | grep -iE 'content-security|x-frame|strict-transport|x-content-type|referrer-policy|permissions-policy'

-I sends a HEAD request. If a host answers HEAD differently, use curl -s -D - -o /dev/null https://your-domain.com to see the headers from a normal GET. Then open the app in a browser with DevTools open and look for CSP errors in the console.

Or skip the terminal: the free PreflightVibe scan flags missing CSP, HSTS, X-Frame-Options and nosniff headers, alongside leaked keys and open Supabase tables. See what our security checker for vibe coded apps covers.

FAQ

Can I add security headers inside my React or Vite code?
No. Headers come from whatever serves your files. The only partial exception is a CSP meta tag, which can't set frame-ancestors or report-only mode.
Will a Content-Security-Policy break my app?
It can. Start with Content-Security-Policy-Report-Only, test every flow, add the origins you need, then enforce.
Do I need both X-Frame-Options and frame-ancestors?
Modern browsers follow frame-ancestors when both are present. Sending both is harmless and covers older browsers.
Does Vercel or Netlify already send HSTS?
Yes, on HTTPS domains. Set it yourself only to change the value.
How do I check which headers my site sends?
Run curl -sI https://your-domain.com, or paste your URL into the free PreflightVibe scan.

Check your headers with a free PreflightVibe scan

Paste your live URL. In about 30 seconds you get an A–F grade covering headers, leaked keys, exposed files and Supabase access. Unlock the full report with fixes for $29 when you are ready.

Run the free scan