나는 Framer의 React 런타임을 역공학해 사이트를 static HTML로 내보냈다

발행: (2026년 3월 15일 오후 04:17 GMT+9)
12 분 소요
원문: Dev.to

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 앱입니다.

  1. 서버가 사전 렌더링된 HTML을 보냅니다.
  2. 클라이언트는 이후 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/...)에서 불러옵니다.

해결 방법

  1. CSS url() 선언에 사용된 모든 자산을 다운로드합니다.
  2. 로컬에 저장합니다.
  3. URL을 상대 경로로 재작성합니다.

이 과정은 재귀적으로 진행됩니다: 일부 CSS 파일이 다른 CSS 파일을 @import하고, 그 파일이 폰트를 참조하며, 다시 더 많은 자산을 참조하는 식입니다. 크롤러는 모든 체인을 따라가며 모든 파일을 로컬에 확보합니다.

Architecture

구성 요소역할
Puppeteer페이지를 렌더링하고 JavaScript를 실행하는 헤드리스 Chrome
Cheerio캡처 후 HTML을 파싱하고 재작성
RegexCSS url() 경로를 재작성 (Cheerio는 CSS를 파싱하지 않음)
ExpressAPI 서버 및 미리보기 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에서 실행됩니다. 기술적인 세부 사항에 대해 궁금한 점이 있으면 언제든지 답변해 드리겠습니다!

0 조회
Back to Blog

관련 글

더 보기 »

트라비고

Gemini와 함께 말하는 속도만큼 빠르게 여행하세요! 라이브 에이전트가 몰입형 스토리텔링 및 3D 내비게이션과 만나는 곳. 이 프로젝트는 Gemini Live Ag...에 진입하기 위해 만들어졌습니다.