On this page7 sections
Once a Framer site is self-hosted as code, the security headers are yours to set. Start with Strict-Transport-Security, Referrer-Policy, X-Frame-Options (or frame-ancestors), Permissions-Policy and the X-Content-Type-Options: nosniff header a converted Next.js project already sends. Then add a Content Security Policy in report-only mode, list the domains your Custom Code scripts actually load from, and enforce it once the reports are clean. On Framer, its hosting decides these; after converting, you decide them in next.config.ts, your host's config or your reverse proxy.
Deframer is an independent tool and is not affiliated with, endorsed or sponsored by Framer. This post covers sites you've converted from your own Framer project, or one whose owner gave permission.
What the converted project already sets
A Next.js project from Deframer ships with a small, safe baseline in next.config.ts:
poweredByHeader: false, so responses don't advertise the framework.X-Content-Type-Options: nosniffon every path, so browsers don't guess content types.
Everything else is left to you, because the right values depend on your domain, your host and the third-party scripts your site uses. That last part matters: Framer sites often carry analytics, chat widgets or tag managers in Site Settings, then Custom Code, and the converted project keeps that code as it was, running at the same points in the page. A strict policy that ignores it will break it.
The headers worth setting
Header | Suggested value | What it does |
|---|---|---|
| | Browsers only use HTTPS for your domain |
| See below | Limits where scripts, styles, images and frames load from |
| | Stops other sites framing yours (clickjacking) |
| | Sends only your origin to other sites |
| | Turns off browser features you don't use |
| | Already set in Next.js projects |
| | Isolates your window from popups you open |
A few cautions:
- HSTS is sticky. Once browsers have seen it, they refuse plain HTTP for
max-age. Start with a short value such asmax-age=300, confirm every subdomain works over HTTPS, then raise it. Only addincludeSubDomainsif every subdomain has HTTPS. Don't addpreloadunless you've read the preload list's requirements. X-Frame-Options: DENYbreaks any place where you deliberately embed your own site in an iframe. UseSAMEORIGINor the CSP'sframe-ancestorsif you do.Cross-Origin-Opener-Policy: same-origincan interfere with payment or login popups that talk back to the opener. Leave it out if you use those.
Writing a Content Security Policy for a converted site
A CSP lists, per type of resource, where the browser may load it from. The hard part on a converted Framer site is scripts.
What the page loads
Go through these sources before writing a policy:
- The site itself (
'self'): pages, scripts, styles, and assets if you chose "Assets in the project". - Framer's CDN (
https://framerusercontent.com) if you kept the default "Assets on Framer's CDN": images, fonts and videos load from there. - Custom Code: every script tag and every domain those scripts load from. Tag managers load more scripts from more domains.
- Embeds and code components: maps, videos, calendars, and anything in
src/codeorsrc/vendorthat loads from a third party. - Inline scripts and styles: the framework's own inline bootstrapping scripts, Custom Code written inline, and inline styles that animations set at runtime.
A starting policy
This is a report-only starting point for a Next.js project with assets on Framer's CDN and a Google Tag Manager snippet in Custom Code. Adjust the domains to what your site really uses.
default-src 'self';
script-src 'self' 'unsafe-inline' https://www.googletagmanager.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob: https://framerusercontent.com https://www.googletagmanager.com;
font-src 'self' data: https://framerusercontent.com;
media-src 'self' https://framerusercontent.com;
connect-src 'self' https://www.google-analytics.com https://*.google-analytics.com;
frame-src 'self';
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none';
upgrade-insecure-requestsWhy 'unsafe-inline' for scripts and styles? Custom Code is often inline <script> blocks, the framework adds inline scripts, and animations set inline styles. A nonce-based policy is stricter, but it requires dynamic rendering and wiring a nonce into every inline script, including the ones from Custom Code. Even with 'unsafe-inline', the policy still blocks scripts from domains you didn't list, which is where most real-world injection lands. Treat a nonce-based policy as a later improvement, not a launch requirement.
form-action 'self' works for a Next.js, TanStack Start, Astro or React Router project, whose forms post to the project's own API routes. On a Vite static site, forms post to a form service; add that service's domain to connect-src.
Roll it out in report-only mode
- Send the policy as
Content-Security-Policy-Report-Onlyfirst. Nothing is blocked; violations show in the browser console. - Click through every page, open menus, play videos, submit forms, accept the cookie banner. Note each reported violation.
- Add the legitimate sources; remove scripts you no longer want.
- Switch the header name to
Content-Security-Policyand test again.
Where to set the headers
In Next.js
Extend the existing headers() in next.config.ts:
async headers() {
const csp = [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' https://www.googletagmanager.com",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: blob: https://framerusercontent.com",
"font-src 'self' data: https://framerusercontent.com",
"frame-ancestors 'none'",
"base-uri 'self'",
"object-src 'none'",
].join("; ");
return [
{
source: "/:path*",
headers: [
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Content-Security-Policy-Report-Only", value: csp },
{ key: "Strict-Transport-Security", value: "max-age=300" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "X-Frame-Options", value: "DENY" },
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
],
},
];
},Keep the existing nosniff entry when you edit it.
On a static host
- Netlify and Cloudflare Pages read a
_headersfile in the output folder; createpublic/_headersso the build copies it intodist/. Netlify also accepts headers innetlify.toml. - Vercel reads
headersinvercel.json; a Vite project'svercel.jsonalready exists, so add aheadersarray to it. - CloudFront uses a response headers policy attached to the distribution. See S3 and CloudFront.
In a reverse proxy
With Caddy in front of a container (self-hosting with Docker):
example.com {
header {
Strict-Transport-Security "max-age=300"
Referrer-Policy "strict-origin-when-cross-origin"
X-Frame-Options "DENY"
Permissions-Policy "camera=(), microphone=(), geolocation=()"
}
reverse_proxy site:3000
}Set each header in one place only. If both the app and the proxy send a CSP, browsers enforce both, and the result is the stricter intersection, which is confusing to debug.
Check the result
curl -sI https://example.com | grep -iE "strict-transport|content-security|referrer|x-frame|permissions|x-content"Online scanners such as Mozilla's HTTP Observatory grade the headers and explain each finding. Treat the grade as a checklist, not a goal: a perfect score with a broken chat widget isn't a win.
Headers are one layer
Headers protect visitors' browsers. They don't stop someone copying public pages, and they don't secure your server. Also keep:
- Dependencies updated (
npm audit, and your host's automatic updates). - Form webhooks secret, in environment variables, never in the repository.
- The project in Git, so you can rebuild from scratch (back up a Framer site).
If your worry is other people converting your site, that's a different problem with different tools; what website copy protection can and cannot do explains them.
FAQ
Does Framer set security headers for me?
Framer's hosting controls the headers on a Framer-hosted site. Once you self-host the converted code, you choose them. Check your live Framer site with curl -sI if you want to match its current behavior first.
Will a Content Security Policy break my Custom Code?
It can, if a script loads from a domain the policy doesn't list. Roll out in report-only mode, add each legitimate domain, then enforce.
Why does the example allow 'unsafe-inline'?
Converted sites often include inline Custom Code, and the framework and animations use inline scripts and styles. Domain restrictions still block most injected third-party scripts. A nonce-based policy is stricter but takes more work.
Should I enable HSTS preload?
Only after HSTS has run with a long max-age and includeSubDomains without problems, and you're sure every subdomain will always have HTTPS. Removal from the preload list is slow.
Where should I put headers if I use Cloudflare in front of my host?
Either works; pick one. Setting them in the app or host config keeps them in Git with the rest of the site.
