What self-hosting actually requires
Framer's plans differ in pages and bandwidth (1 GB on Free, 50 GB on Basic, 100 GB on Pro before overage) but not in whether you can take the site elsewhere -- none of them hand you files. Self-hosting therefore starts with a capture that localizes every asset: stylesheets, the module graph behind the animations, fonts, and every srcset variant of every image. If you have not done that yet, start with the export guide.
Once you have the folder, the rest is ordinary static hosting. There is no server-side code in a captured Framer site; it is HTML, CSS, JavaScript and assets, and anything that serves files with the right MIME types will serve it. That includes a small VPS with Nginx, which is worth remembering when a hosting comparison starts to feel like a religion.
Free tiers, as listed on 28 Sep 2026
| Host | Free bandwidth / month | Builds or deploys | Custom domain + HTTPS | Reads from the export |
|---|---|---|---|---|
| Vercel (Hobby) | 100 GB | 100 deploys / day | Yes | vercel.json |
| Netlify (Free) | Up to 15 GB (300 credits, 20 per GB) | 15 credits per production deploy | Yes | netlify.toml, _headers |
| Cloudflare Pages (Free) | Unmetered | 500 builds / month | Yes | _headers |
| Your own Nginx / Caddy | Whatever the box has | You copy files | You configure it | Nothing; it is a folder |
Source: Vercel — Hobby plan · Netlify — Pricing · Cloudflare Pages — Limits · Cloudflare Pages
Bandwidth before you pay, in GB per month
| Item | Value |
|---|---|
| Framer Free | 1 GB |
| Framer Basic | 50 GB |
| Framer Pro | 100 GB |
| Vercel Hobby | 100 GB |
| Netlify Free | 15 GB |
Source: Framer — Pricing · Vercel — Hobby plan · Netlify — Pricing
What is in the export, so you do not write it twice
The sanitized ZIP holds the site with its mirrored hosts folded into assets/{css,js,img,fonts,media,data}, every reference rewritten to match, plus vercel.json (cleanUrls, no trailing slash), netlify.toml, a _headers file Netlify and Cloudflare Pages both read, and a README written for someone who has never deployed anything. Each host reads only its own file and ignores the others, and none of them declare a build step, because there is nothing to build.
The one thing worth knowing about the Vercel config is the cache policy. Captured assets are not content-hashed -- the pipeline names them into sequential slots like script-007.js -- so a header of immutable would leave returning visitors on stale JavaScript after a recapture. The export uses max-age=0, s-maxage=31536000, stale-while-revalidate instead: the edge caches for a year and a redeploy purges it, while the browser revalidates with a cheap 304. Same speed, no stale-forever failure mode.
vercel.json, as exported
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"cleanUrls": true,
"trailingSlash": false
}Which file each host reads
- Vercel
vercel.json—cleanUrls: true,trailingSlash: false- Netlify
netlify.tomland_headers- Cloudflare Pages
_headers— 20,000 files per site and 25 MiB per file on the free tier- Nginx, Caddy, S3
- Nothing — it is a folder — Correct MIME types and a try_files rule are the whole configuration
All three ship in the same ZIP and none of them declares a build step. You are not choosing a host at export time; you are choosing one at import time.
Source: Vercel — Project configuration (vercel.json) · Cloudflare Pages — Limits · Cloudflare Pages
Deploying to each host, without a CLI
Vercel
Unzip, push the folder to a private GitHub repository (the web uploader is fine), then vercel.com/new → Import → Deploy with the defaults. The README in the ZIP walks this in eleven clicks. Every push to the repository redeploys.
Netlify
app.netlify.com → Sites → drag the unzipped folder onto the drop zone.
netlify.tomlsetspublish = "."and an empty build command;_headersaddsnosniff, a referrer policy andSAMEORIGINframing.Cloudflare Pages
Workers & Pages → Create → Pages → Upload assets, then drag the folder. Or connect the GitHub repository with no build command and
/as the output directory._headersis read as-is.Your own server
Copy the folder to
/var/www/siteand use the Nginx block below. Caddy is two lines:root * /var/www/siteandfile_server. Either way, HTTPS is a certbot or automatic-TLS step you do once.
Nginx for a captured site
server {
listen 443 ssl http2;
server_name example.com;
root /var/www/site;
index index.html;
location / {
try_files $uri $uri.html $uri/ =404;
}
location /assets/ {
add_header Cache-Control "public, max-age=0, s-maxage=31536000, stale-while-revalidate=86400";
}
gzip on;
gzip_types text/css application/javascript image/svg+xml application/json;
}The domain moves last
The cutover, on a clock
Lower the DNS TTL
· Before anything — Drop the record's TTL to 300 seconds a day ahead. Everything else in this list is fast; DNS is the part that is not, and it is the part you cannot hurry once it has started.
Deploy to the host's preview URL
— Import the folder, let the host give you its own
*.vercel.appor*.pages.dev, and walk every route there. This is the last moment a mistake costs nothing.Repoint the record
· The switch — Change the A or CNAME record to the new host and let the certificate issue. Keep the Framer site published — you are changing where the domain points, not deleting anything.
Verify from a network you do not control
— A phone on mobile data, or any resolver that has not cached the old answer. Check the certificate, then walk the routes again.
Unpublish the original
· Only now — Once the old TTL has expired everywhere and the logs show traffic arriving at the new host. Until then the old site is your rollback, and a rollback you deleted is not one.
Prove it stands alone before you touch DNS
- In the unzipped folder:
python3 -m http.server 8080, openhttp://127.0.0.1:8080. - DevTools → Network → throttle Offline, then hard-reload. The page should render fully: images, fonts, styles.
- Clear the throttle and filter the request list with
-domain:127.0.0.1. Anything left is a dependency you still have on somebody else's server. - Walk every route from the sitemap, at 1280 / 1024 / 390 px.
- Submit each form once on the deployed preview and confirm where it went.
Updating it later
A self-hosted site is only a burden if every change is a migration. Two things keep it a chore instead: version history (every save is atomic, with a reachable version zero and named checkpoints, so a bad edit is a restore, not a rollback ritual) and Source Sync, which recaptures the Framer origin and reapplies your edits. After either, export again and redeploy the same way you did the first time.