I reverse-engineered Framer's React runtime to export sites as static HTML
Source: Dev.to
I had a Framer site (nocodetalks.co) running for 2 years, paying $10 / month for static‑site hosting. No dynamic content, no CMS, no forms—just plain HTML on Framer’s servers.
When I tried to move it to Vercel (free tier, same performance) I hit a wall: Framer has no code export. Their help center says it flat out—you can build on Framer, but you can’t take your files and leave.
I talked to a few friends who were in the same spot. They all wanted to move to Vercel or Cloudflare Pages but had no way to get their code out.
So I built a tool to do it. What started as a script for my own site turned into a product. Below is what I learned about how Framer works under the hood, and why “just saving the HTML” doesn’t work.
Why “View Source” Doesn’t Work
Framer sites look like normal HTML when you right‑click → View Source, but they are actually React apps.
- The server sends pre‑rendered HTML.
- The client then loads a JavaScript bundle that calls
hydrateRoot()to take over the DOM.
If you save the HTML file and open it locally:
- The React bundle tries to load from Framer’s CDN.
- Hydration runs, but API calls fail because you’re not on Framer’s domain.
- React either throws errors or wipes the DOM.
Result: a blank page or broken layout.
The HTML you see in View Source is only the initial server render. The real site lives in React’s runtime.
The 5 Problems I Had to Solve
1. Stripping React Without Breaking the Page
The crawler first captures each page’s HTML, then removes every <script> tag related to React hydration (main entry point, modulepreload hints, inline hydrateRoot calls).
Framer also injects scripts for interactive components (FAQ accordions, mobile nav menus, tab switchers). Deleting all JavaScript makes those components static in the wrong way.
Fix
- Strip the React/hydration layer.
- Inject tiny vanilla‑JS scripts that replicate the interactive behavior (e.g., a 20‑line accordion, a 15‑line mobile‑menu toggle).
- This replaces hundreds of kilobytes of React runtime with a few hundred bytes of custom code.
2. Invisible Content (Scroll Animations)
Framer hides elements that should animate in on scroll by setting opacity: 0. In the live site, Framer’s JavaScript detects scroll position and fades them in. In the exported version those scripts are gone, so the elements stay invisible.
Fix
- The crawler auto‑scrolls each page from top to bottom, pausing at intervals to let images load.
- After scrolling, it waits for the DOM to settle (no new mutations for 500 ms) before capturing the final HTML.
- This forces the elements to become visible before the HTML is saved.
5. CSS url() References
Framer’s stylesheets reference fonts and background images using absolute URLs that point to Framer’s CDN (e.g., https://framerusercontent.com/...).
Fix
- Download every asset referenced in CSS
url()declarations. - Save them locally.
- Rewrite the URLs to relative paths.
The process is recursive: some CSS files @import other CSS files, which reference fonts, which reference more assets. The crawler follows the chain until everything is local.
Architecture
| Component | Role |
|---|---|
| Puppeteer | Headless Chrome for rendering pages and executing JavaScript |
| Cheerio | Parse and rewrite HTML after capture |
| Regex | Rewrite CSS url() paths (Cheerio doesn’t parse CSS) |
| Express | API server and preview UI |
| p‑queue | Concurrency control (max 2 browser instances) |
| archiver | Stream ZIP creation for download |
- Crawling strategy: Breadth‑first search (BFS). Start at the homepage, extract all internal links, visit each one, repeat. Capped at 50 pages per export.
- Asset downloads: 8 concurrent
https.getrequests. Direct HTTP requests are ~10× faster than using Puppeteer for assets. - Code size: ~1,100 lines for the crawler, 400 lines for the URL rewriter, 200 lines for the ZIP packager.
What the Output Looks Like
A typical exported Framer site gives you a folder structure like this:
index.html
about.html
pricing.html
contact.html
assets/
css/
a3f2c1_styles.css
b7e9d4_chunk.css
js/
menu-toggle.js
faq-accordion.js
scroll-reveal.js
images/
hero.webp
team-photo.jpg
logo.svg
fonts/
inter-var.woff2
playfair-display.woff2
manifest.jsonEach HTML file is self‑contained with relative paths, ready to be deployed to any static‑hosting provider (Vercel, Cloudflare Pages, Netlify, etc.).
TL;DR
- Framer sites are React apps; you can’t just “save the HTML”.
- Stripping React, handling scroll reveals, hover effects, lazy‑loaded images, and CDN‑referenced assets are the core challenges.
- A headless‑Chrome crawler + smart post‑processing can export a fully functional static copy that works anywhere.
Overview
No CDN dependencies, no API calls, no framework. Open index.html in a browser and it works.
File sizes drop a lot. A typical Framer site loads 800 KB+ of JavaScript (React runtime, Framer library, hydration bundle). The exported version is usually under 100 KB of JS total (just the small interaction scripts).
Pricing and why it’s one‑time
$10.99 per Framer URL. You pay once, download the ZIP, and that’s it.
I chose a one‑time model because the use case is transactional: you export a site once, maybe come back months later to re‑export after making changes in Framer. It’s not something you do daily or weekly.
- For context: Framer’s Pro plan is $30/month per site.
- If you export and move to Vercel’s free tier, you save $30/month going forward.
- The $10.99 pays for itself in about 11 days.
What I’d Tell Other Devs Building Export Tools
Don’t trust the initial HTML.
Any site that hydrates on the client (React, Vue, Svelte, etc.) gives you a snapshot that doesn’t represent the real page. Render it in a real browser and wait for JavaScript to finish.Scroll the page.
Lazy loading is everywhere now. If you don’t scroll, you miss half the content.Watch for JS‑driven styles.
More and more sites apply visual states through JavaScript instead of CSS—hover effects, scroll triggers, intersection observers. To capture visual behavior, you need to simulate user interaction and observe DOM changes.Test on 20+ real sites before you ship.
Every Framer site uses a slightly different combination of components. Edge cases are endless: carousels, tabs, nested accordions, sticky headers, video backgrounds. Each one needs specific handling.
Try It Yourself
If you want to try it: letaiworkforme.com. The export and live preview are free; you pay only to download the ZIP.
The code runs on Node.js + Puppeteer. Happy to answer any questions about the technical details!