워드프레스 정적 내보내기는 사이트 전체를 HTML 파일 묶음으로 복사하는 일입니다. Simply Static이 그 일을 하고, 나온 폴더를 아무 데나 올리면 그게 곧 사이트가 됩니다.
이 블로그 기준 한 번 돌리면 URL 2,395개를 훑어 파일 2,391개(93MB)가 나옵니다. 글 11편짜리 사이트의 실측치입니다.
함정은 다섯 군데입니다 — CLI 백그라운드 실행, 검색엔진 인증 파일 누락, 검색 색인 순서, 내보내기 전 폴더 비우기, 그리고 404 페이지.
지난 편에서 간격과 글자 크기를 토큰으로 고정하는 얘기까지 했습니다. 이제 그 시안이 실제 파일이 되어 인터넷에 올라갈 차례입니다 — 워드프레스 정적 내보내기입니다.
이 블로그는 워드프레스로 글을 쓰지만, 인터넷에 떠 있는 건 워드프레스가 아닙니다. HTML 파일 뭉치입니다. 맥에서 도는 로컬 워드프레스가 글쓰기 도구 역할만 하고, 완성된 결과물을 파일로 뽑아 올립니다. 그 “뽑는” 일을 하는 게 Simply Static입니다.
왜 정적으로 내보내나요?
워드프레스를 서버에 그대로 올리면 방문자가 페이지를 열 때마다 PHP가 돌고 DB를 조회합니다. 정적 내보내기를 하면 서버는 그냥 파일을 주기만 합니다. 차이는 이렇습니다.
| 항목 | 워드프레스 호스팅 | 정적 파일 |
|---|---|---|
| 월 비용 | 호스팅 요금 발생 (상품마다 다름) | 0원 — 도메인 비용만 연 1만원대 |
| 속도 | PHP 실행 + DB 조회 | 파일 전송만 |
| 보안 | 플러그인·코어 취약점 관리 필요 | 서버에서 실행되는 코드가 없음 |
| 대신 잃는 것 | — | 댓글·검색·폼은 별도 해결 |
마지막 줄이 핵심입니다. 정적 사이트에는 서버에서 도는 코드가 없으니 댓글, 검색, 문의 폼처럼 서버가 필요한 기능은 다른 방법으로 붙여야 합니다. 이 블로그는 검색을 Pagefind로 붙였고(뒤에 나옵니다), 댓글은 아직 없습니다.
Simply Static은 정확히 뭘 하나요?
원리는 단순합니다. 자기 사이트를 자기가 크롤링해서 나오는 모든 URL을 파일로 저장합니다. 글 페이지, 목록, 카테고리, 이미지, CSS, JS까지 전부요.
이 블로그(글 11편) 기준 한 번 돌린 실측치입니다.
| 지표 | 값 |
|---|---|
| 훑은 URL | 2,395개 (성공 2,391 · 404 3 · 301 1) |
| 만들어진 파일 | 2,391개 (여기에 Pagefind 색인이 더해집니다) |
| 총 용량 | 93MB |
| HTML 페이지 | 56개 |
| 주요 구성 | CSS 861 · JS 598 · SVG 433 · 이미지 306 (jpg·png·gif 등) |
| 그 밖에 | JSON 116 · 폰트 11 · XML/XSL 9 등 |
글이 11편인데 HTML이 56개인 건 태그 페이지 36개 때문입니다. 여기에 카테고리 2, 작성자 2, 그리고 홈·글목록·목록 2페이지·검색·인증 파일 5개가 붙어 56개가 됩니다. 태그를 하나 만들 때마다 HTML이 한 장 늘어난다는 뜻입니다.
CSS가 861개나 되는 것도 이유가 있습니다. 그중 wp-includes/blocks 밑 파일만 596개, 전체의 69%입니다. 워드프레스가 블록마다 스타일 파일을 따로 갖고 있어서입니다.
어떻게 설정하나요?
설치는 플러그인 검색에서 Simply Static을 찾아 활성화하면 끝입니다. 중요한 건 그다음 설정입니다.

이 블로그의 실제 설정값입니다.
| 설정 | 값 | 이유 |
|---|---|---|
| Replacing URLs | Absolute URLs | 사이트맵·OG 태그에 완전한 주소가 필요 |
| Scheme + Host | https:// + 실제 도메인 |
로컬 주소를 실 도메인으로 치환 |
| Force URL replacements | 켬 | 인라인 CSS·JS 안의 주소까지 훑음 |
| Enhanced Crawl | 켬 | 링크로 연결 안 된 페이지도 워드프레스 함수로 수집 |
| Deployment method | Local Directory | 폴더로 뽑고 업로드는 따로 (다음 편) |
| Local Directory | 프로젝트 폴더의 build/ |
업로드 스크립트가 바로 집어갈 위치 |
Force URL replacements는 HTML 속성 바깥 — 인라인 CSS·JS 안에 박힌 주소까지 훑는 옵션입니다. 켜둔 채 내보낸 결과를 grep해 보면 링크·자원 주소로서의 .local 주소는 0회이고(본문에 일부러 적어둔 글자 한 곳만 남습니다), 이미지 주소는 전부 실제 도메인으로 바뀌어 있었습니다. 치환이 새면 배포 후 이미지가 통째로 깨집니다.
함정 5개
함정 ① 명령줄에서 돌리면 안 끝난 것처럼 보인다
관리자 화면에서 Generate를 누르면 진행률이 보이지만, 자동화하려고 CLI로 돌리면 얘기가 다릅니다. Simply Static은 백그라운드 작업이라 시작 명령만으로는 아무 일도 일어나지 않습니다.
# ① 작업 시작 — 이것만 하면 큐에 등록만 되고 멈춘다
wp eval 'Simply_Static\Plugin::instance()->run_static_export(0,"export");'
# ② 진행 — 끝날 때까지 반복 호출해야 한다
wp eval '$j=Simply_Static\Plugin::instance()->get_archive_creation_job(); if($j) $j->handle_cron_healthcheck();'
②를 한 번 부를 때마다 작업이 조금씩 진행됩니다. 그래서 실제로는 루프로 돌립니다.
for i in $(seq 1 300); do
OUT=$(wp eval '$j=Simply_Static\Plugin::instance()->get_archive_creation_job(); if($j){ $j->handle_cron_healthcheck(); echo $j->is_running() ? "RUNNING" : "DONE"; } else { echo "NOJOB"; }' 2>/dev/null | tail -1)
if [ "$OUT" != "RUNNING" ]; then echo "완료 ($OUT, ${i}회)"; break; fi
if [ "$i" = "300" ]; then echo "300회를 다 썼는데 아직 RUNNING — 사이트가 크면 숫자를 올리세요"; fi
done
2>/dev/null | tail -1이 중요합니다. 경고 한 줄만 섞여 나와도 첫 회차에 “완료”로 오판하고 빈 폴더를 배포하게 됩니다.
횟수는 사이트마다 다릅니다. 이 글을 쓰면서 세 번 돌렸는데 93회 · 187회 · 176회가 나왔습니다. 숫자 자체보다 “한 번으로는 안 끝난다”가 요점입니다.
참고로 Local 앱으로 워드프레스를 돌리고 있다면 wp 명령이 바로 먹지 않습니다. 사이트별 셸을 열거나 래퍼 스크립트를 거쳐야 합니다 — 만들기 02편에서 만든 그 래퍼입니다.
함정 ② 검색엔진 인증 파일은 워드프레스가 모른다
네이버 서치어드바이저나 IndexNow는 사이트 루트에 인증용 파일을 놓으라고 합니다. 그런데 이 파일들은 워드프레스 글도 페이지도 아니라서, 크롤링 방식인 Simply Static이 찾지 못합니다. 배포하면 조용히 사라지고 소유 확인이 풀립니다.
해결은 설정의 Additional Files and Directories 항목입니다. 파일의 절대 경로를 줄바꿈으로 나열하면 내보내기에 무조건 포함됩니다.
/Users/.../app/public/naver<32자 해시>.html
/Users/.../app/public/<IndexNow 키>.txt
사이트맵도 비슷합니다. Yoast가 만드는 사이트맵은 본문 링크로 연결돼 있지 않아 Additional URLs에 따로 적어야 합니다.
http://사이트.local/sitemap_index.xml
http://사이트.local/post-sitemap.xml
http://사이트.local/page-sitemap.xml
http://사이트.local/robots.txt
함정 ③ 검색 색인은 내보내기 뒤에 만들어야 한다
정적 사이트라 워드프레스 검색이 안 돌아갑니다. 이 블로그는 Pagefind를 씁니다. HTML 폴더를 훑어 검색 색인을 만들어주는 도구입니다.
npx pagefind --site build
순서가 중요합니다. 색인은 내보낸 결과물을 재료로 만들어지므로 반드시 내보내기 다음입니다. 그리고 이 순서 때문에 내보내기 로그에 404가 뜹니다.
404 /pagefind/pagefind-ui.js
404 /pagefind/pagefind-ui.css
테마가 Pagefind 파일을 부르는데 크롤링 시점엔 아직 그 파일이 없기 때문입니다. 정상입니다. 순서상 그럴 수밖에 없고, 색인을 만들고 나면 실제 폴더엔 파일이 생깁니다. 이 404를 없애겠다고 테마를 고치면 안 됩니다. (남은 404 하나는 wp-includes/js/crop/castle.jpg, 워드프레스 코어에 남아 있는 잔재라 무시해도 됩니다.)
함정 ④ 매번 폴더를 통째로 비운다
Clear Local Directory가 켜져 있으면 내보내기 전에 build 폴더를 비웁니다. 켜두는 게 맞습니다 — 안 그러면 지운 글의 HTML이 계속 남아 있게 됩니다. 대신 build 폴더에 손으로 만든 파일을 두면 안 됩니다. 필요한 파일은 Additional Files로 넣거나, 내보내기 뒤에 다시 만들어야 합니다.
함정 ⑤ 404 페이지는 만들어지지 않는다
Simply Static은 존재하는 URL을 크롤링해서 파일로 저장합니다. 그런데 404 페이지는 정의상 존재하지 않는 주소에서만 나옵니다. 그래서 아무리 내보내도 build/404.html은 생기지 않습니다.
문제는 그다음입니다. 404.html이 없으면 Cloudflare Pages는 없는 주소에 대해 홈 화면 HTML을 200으로 돌려줍니다. 실제로 확인해 보니 이랬습니다.
$ curl -o /dev/null -w '%{http_code}' https://내사이트/이런주소없음/
200 ← 404가 아니라 200
검색엔진 입장에서는 “존재하는 페이지가 무한히 많은 사이트”로 보입니다. 구글 서치콘솔이 소프트 404로 경고하고, 오타 주소나 죽은 링크가 전부 색인 후보가 됩니다.
해결은 내보내기 뒤에 404.html을 직접 만들어 넣는 것입니다. 로컬 워드프레스의 404 페이지를 그대로 받아 주소만 바꿔 저장하면 됩니다.
curl -s "http://사이트.local/__없는주소__/" \
| sed 's|http://사이트.local|https://실제도메인|g' > build/404.html
한 가지 더 있습니다. JSON-LD 구조화 데이터 안의 주소는 http:\/\/사이트.local 처럼 슬래시가 이스케이프된 형태로 들어 있어서 위 치환에 걸리지 않습니다. 이스케이프된 형태도 한 번 더 바꿔줘야 로컬 주소가 완전히 사라집니다. 이 블로그에서 실제로 그렇게 남아 있던 게 네 종류였습니다.
고치고 나면 없는 주소가 제대로 404를 돌려줍니다. 그리고 이 파일은 매번 다시 만들어야 합니다. 함정 ④에서 봤듯 내보내기 때마다 build 폴더가 비워지니까요.
전체 순서 정리
# ① 내보내기 (배경 작업이라 healthcheck 루프 필수)
wp eval 'Simply_Static\Plugin::instance()->run_static_export(0,"export");'
# … healthcheck 루프 …
# ② 404 페이지 만들기 (폴더가 비워졌으므로 매번)
./make404.sh
# ③ 검색 색인 (내보내기 결과물이 재료라 순서 고정)
npx pagefind --site build
# ④ 업로드 — 다음 편
여기까지 하면 build/ 폴더가 곧 사이트입니다. 열어보면 index.html, 글 슬러그별 폴더, wp-content/uploads/ 이미지가 그대로 들어 있습니다. 이 폴더를 통째로 어디든 올리면 사이트가 됩니다.
정리
- 워드프레스 정적 내보내기는 사이트를 HTML 파일 묶음으로 복사하는 일 — 서버에서 도는 코드가 사라진다
- 대신 댓글·검색·폼은 따로 붙여야 한다 (이 블로그는 검색을 Pagefind로)
- CLI 실행은 시작 명령만으로 끝나지 않는다 — healthcheck 루프가 필수, 출력은
tail -1로 잘라 받는다 - 인증 파일·사이트맵은 Additional Files / Additional URLs에 손으로 넣어야 살아남는다
- Pagefind 색인은 내보내기 뒤 — 그래서 로그의 pagefind 404는 정상이다
- 404.html은 안 만들어진다. 안 넣으면 없는 주소가 200을 돌려줘 색인이 오염된다
다음 편은 이 폴더를 실제로 인터넷에 올리는 Cloudflare Pages 배포와 도메인 연결입니다. 거기서도 에이전트 환경에서 wrangler가 배포를 거부하는 함정이 하나 기다리고 있습니다.