나는 Framer의 React 런타임을 역공학해 사이트를 static HTML로 내보냈다
Source: Dev.to
저는 nocodetalks.co 라는 Framer 사이트를 2 년 동안 운영했으며, 정적 사이트 호스팅 비용으로 월 $10을 지불했습니다. 동적 콘텐츠도, CMS도, 폼도 없고—그저 Framer 서버에 있는 순수 HTML만 있었습니다.
그 사이트를 Vercel(무료 티어, 동일한 성능)로 옮기려 했을 때 장벽에 부딪혔습니다: Framer는 코드 내보내기를 지원하지 않음. 헬프 센터에서는 명확히 이렇게 말합니다—Framer에서 빌드할 수는 있지만 파일을 가져가서 떠날 수는 없습니다.
같은 상황에 처한 친구 몇 명과 이야기를 나눴습니다. 그들 모두 Vercel이나 Cloudflare Pages로 옮기고 싶어했지만 코드를 얻을 방법이 없었습니다.
그래서 저는 이를 위한 도구를 만들었습니다. 처음엔 제 사이트용 스크립트였지만 곧 하나의 제품이 되었습니다. 아래는 Framer가 내부적으로 어떻게 동작하는지, 그리고 “HTML만 저장하는 것”이 왜 통하지 않는지에 대해 제가 배운 내용입니다.
“View Source”가 작동하지 않는 이유
Framer 사이트는 오른쪽 클릭 → View Source 를 하면 일반 HTML처럼 보이지만, 실제로는 React 앱입니다.
- 서버가 사전 렌더링된 HTML을 보냅니다.
- 클라이언트는 이후
hydrateRoot()를 호출하는 JavaScript 번들을 로드하여 DOM을 장악합니다.
HTML 파일을 저장해서 로컬에서 열면:
- React 번들이 Framer의 CDN에서 로드되려고 시도합니다.
- Hydration은 실행되지만, Framer 도메인이 아니기 때문에 API 호출이 실패합니다.
- React가 오류를 발생시키거나 DOM을 초기화합니다.
결과: 빈 페이지 또는 깨진 레이아웃.
View Source 에서 보는 HTML은 초기 서버 렌더링일 뿐이며, 실제 사이트는 React 런타임에서 동작합니다.
해결해야 했던 5가지 문제
1. 페이지를 깨뜨리지 않고 React 제거하기
크롤러는 먼저 각 페이지의 HTML을 캡처한 뒤 React hydration과 관련된 모든 <script> 태그(메인 엔트리 포인트, modulepreload 힌트, 인라인 hydrateRoot 호출)를 제거합니다.
Framer는 또한 FAQ 아코디언, 모바일 네비게이션 메뉴, 탭 스위처와 같은 인터랙티브 컴포넌트를 위한 스크립트를 주입합니다. 모든 JavaScript을 삭제하면 이러한 컴포넌트가 잘못된 방식으로 정적으로 표시됩니다.
해결 방법
- React/hydration 레이어를 제거합니다.
- 인터랙티브 동작을 재현하는 작은 vanilla‑JS 스크립트를 삽입합니다(예: 20줄짜리 아코디언, 15줄짜리 모바일 메뉴 토글).
- 이렇게 하면 수백 킬로바이트에 달하는 React 런타임을 몇 백 바이트 수준의 커스텀 코드로 대체할 수 있습니다.
2. 보이지 않는 콘텐츠(스크롤 애니메이션)
Framer는 스크롤 시 애니메이션으로 나타나야 할 요소들을 opacity: 0으로 숨깁니다. 실제 사이트에서는 Framer의 JavaScript가 스크롤 위치를 감지해 요소를 서서히 표시합니다. 하지만 내보낸 버전에서는 해당 스크립트가 사라져 요소가 계속 보이지 않게 됩니다.
해결 방법
- 크롤러가 자동 스크롤을 수행하여 페이지를 위에서 아래로 이동시키고, 이미지 로드를 위해 일정 간격으로 멈춥니다.
- 스크롤이 끝난 뒤, DOM이 안정될 때까지(새로운 변형이 500 ms 동안 발생하지 않을 때까지) 기다린 후 최종 HTML을 캡처합니다.
- 이를 통해 요소가 보이게 된 상태에서 HTML이 저장됩니다.
5. CSS url() 참조
Framer의 스타일시트는 절대 URL을 사용해 폰트와 배경 이미지를 Framer CDN(https://framerusercontent.com/...)에서 불러옵니다.
해결 방법
- CSS
url()선언에 사용된 모든 자산을 다운로드합니다. - 로컬에 저장합니다.
- URL을 상대 경로로 재작성합니다.
이 과정은 재귀적으로 진행됩니다: 일부 CSS 파일이 다른 CSS 파일을 @import하고, 그 파일이 폰트를 참조하며, 다시 더 많은 자산을 참조하는 식입니다. 크롤러는 모든 체인을 따라가며 모든 파일을 로컬에 확보합니다.
Architecture
| 구성 요소 | 역할 |
|---|---|
| Puppeteer | 페이지를 렌더링하고 JavaScript를 실행하는 헤드리스 Chrome |
| Cheerio | 캡처 후 HTML을 파싱하고 재작성 |
| Regex | CSS url() 경로를 재작성 (Cheerio는 CSS를 파싱하지 않음) |
| Express | API 서버 및 미리보기 UI |
| p‑queue | 동시성 제어 (최대 2개의 브라우저 인스턴스) |
| archiver | 다운로드용 ZIP 스트림 생성 |
- 크롤링 전략: 너비 우선 탐색(BFS). 홈페이지에서 시작해 모든 내부 링크를 추출하고, 각각을 방문하고, 이를 반복합니다. 한 번의 내보내기당 50페이지로 제한합니다.
- 자산 다운로드: 동시에 8개의
https.get요청을 수행합니다. 직접 HTTP 요청이 Puppeteer를 사용한 자산 로드보다 약 10배 빠릅니다. - 코드 규모: 크롤러 약 1,100 줄, URL 재작성기 400 줄, ZIP 패키저 200 줄.
출력 예시
일반적으로 내보낸 Framer 사이트는 다음과 같은 폴더 구조를 제공합니다:
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.json각 HTML 파일은 자체 포함된 형태이며 상대 경로를 사용해, Vercel, Cloudflare Pages, Netlify 등 모든 정적 호스팅 제공업체에 배포할 준비가 되어 있습니다.
TL;DR
- Framer 사이트는 React 앱이며, “HTML을 저장”만으로는 안 됩니다.
- React를 제거하고, 스크롤 표시, 호버 효과, 지연 로드 이미지, CDN에서 참조되는 자산 등을 처리하는 것이 핵심 과제입니다.
- 헤드리스‑Chrome 크롤러와 스마트 후처리를 결합하면 어디서든 작동하는 완전한 정적 복사본을 내보낼 수 있습니다.
개요
CDN 의존성 없음, API 호출 없음, 프레임워크 없음. 브라우저에서 index.html을 열면 바로 작동합니다.
파일 크기가 크게 줄어듭니다. 일반적인 Framer 사이트는 **800 KB+**의 JavaScript(React 런타임, Framer 라이브러리, 하이드레이션 번들)를 로드합니다. 내보낸 버전은 보통 전체 100 KB 이하의 JS(작은 인터랙션 스크립트만)입니다.
가격 및 일회성 이유
Framer URL당 $10.99. 한 번만 결제하고 ZIP 파일을 다운로드하면 끝입니다.
일회성 모델을 선택한 이유는 사용 사례가 거래성(Transactional)이라서입니다: 사이트를 한 번 내보낸 뒤, 몇 달 후에 Framer에서 변경을 하고 다시 내보낼 수 있습니다. 매일이나 매주 하는 작업은 아닙니다.
- 참고: Framer의 Pro 플랜은 사이트당 $30/월입니다.
- 내보낸 후 Vercel의 무료 티어로 이동하면 앞으로 월 $30을 절감할 수 있습니다.
- $10.99는 약 11일 만에 비용을 회수합니다.
내보내기 도구를 만드는 다른 개발자들에게 전하고 싶은 말
초기 HTML을 신뢰하지 마세요.
클라이언트에서 하이드레이션되는 모든 사이트(React, Vue, Svelte 등)는 실제 페이지를 나타내지 않는 스냅샷을 제공합니다. 실제 브라우저에서 렌더링하고 JavaScript가 완료될 때까지 기다리세요.페이지를 스크롤하세요.
현재 레이지 로딩이 어디에나 존재합니다. 스크롤하지 않으면 절반 이상의 콘텐츠를 놓치게 됩니다.JS 기반 스타일에 주의하세요.
점점 더 많은 사이트가 CSS 대신 JavaScript를 통해 시각적 상태를 적용합니다—호버 효과, 스크롤 트리거, Intersection Observer 등. 시각적 동작을 포착하려면 사용자 상호작용을 시뮬레이션하고 DOM 변화를 관찰해야 합니다.배포하기 전에 20개 이상의 실제 사이트에서 테스트하세요.
모든 Framer 사이트는 약간씩 다른 컴포넌트 조합을 사용합니다. 엣지 케이스는 끝이 없습니다: 캐러셀, 탭, 중첩된 아코디언, 스티키 헤더, 비디오 배경 등. 각각에 대해 별도의 처리가 필요합니다.
직접 해보기
시도해 보고 싶다면: letaiworkforme.com. 내보내기와 실시간 미리보기는 무료이며, ZIP 파일을 다운로드할 때만 비용이 발생합니다.
코드는 Node.js + Puppeteer에서 실행됩니다. 기술적인 세부 사항에 대해 궁금한 점이 있으면 언제든지 답변해 드리겠습니다!