Next.js 정적 사이트의 Workers 이전 기록
목차8

다른 글에서 클라우드플레어 무료 플랜 하나로 블로그와 문서, 비공개 노트, 방문자 수까지 운영한다고 적었다. 그 글에서는 한도를 표로 정리했고, 스크립트를 거치는 요청을 줄이는 설정은 이 글로 넘겼다. 이 글에는 그 배포를 한 순서대로 적는다.
처음엔 Pages 였다
10월 23일 graphite.is 를 처음 설정할 때는 Pages 로 시작했다. 그리고 같은 날 Workers 정적 자산으로 옮겼다.
클라우드플레어는 새 프로젝트라면 Pages 대신 Workers 정적 자산을 쓰라고 권한다. Pages 도 계속 동작하지만 새 기능은 Workers 에 먼저 들어간다. 옮기는 일은 간단했다. next.config 는 손대지 않았고 wrangler.jsonc 파일 하나만 더했다. 기존 Pages 프로젝트를 옮기는 순서는 클라우드플레어의 Pages 에서 Workers 로 이전 가이드에 따로 정리되어 있다.
서버가 할 일이 남아 있지 않은지
이 방식은 Next.js 사이트를 빌드 시점에 정적 파일로 만들어 올린다. 그러니 요청이 들어온 순간에 서버가 해야 하는 일이 하나라도 남아 있으면 곤란하다. 미들웨어, ISR, next.config 의 rewrites 와 redirects 와 headers, 기본 이미지 최적화는 정적 내보내기에서 동작하지 않는다. 이 가운데 redirects 는 나중에 Worker 코드로 따로 처리했다. 지원하지 않는 기능 전체는 Next.js 정적 내보내기 문서에서 볼 수 있다.
준비물은 많지 않다.
| 항목 | 조건 |
|---|---|
| Next.js | 요청 시점에 서버가 필요한 기능을 쓰지 않는 사이트. 미들웨어, ISR, next.config 의 rewrites redirects headers, 기본 이미지 최적화는 정적 내보내기에서 동작하지 않음 |
| Cloudflare | 사용할 도메인이 Cloudflare 에 연결된 계정 |
| Wrangler | 4.x. npm i -D wrangler 로 설치하고 npx wrangler login 으로 로그인 |
output 한 줄
Next.js 쪽에서 할 일은 한 줄이다. next.config.mjs 의 output 을 "export" 로 바꾼다.
const config = {
output: "export",
};
export default config;이 상태로 next build 를 돌리면 화면마다 HTML 파일이 out 폴더에 생긴다. /about 화면은 out/about.html 이 된다. 여기까지는 클라우드플레어와 아무 상관이 없다.
out 폴더를 정적 자산으로 올리기
클라우드플레어 쪽은 wrangler.jsonc 가 맡는다. assets.directory 를 ./out 으로 지정하면 out 폴더가 통째로 정적 자산이 된다. 아래는 graphite.is 에서 쓰는 설정인데 제외 패턴만 일부 줄였다.
{
"name": "graphite",
"compatibility_date": "2026-10-19",
"main": "worker/redirect.js",
"routes": [
{ "pattern": "graphite.is", "custom_domain": true },
{ "pattern": "www.graphite.is", "custom_domain": true }
],
"workers_dev": false,
"preview_urls": false,
"assets": {
"directory": "./out",
"binding": "ASSETS",
"not_found_handling": "404-page",
"html_handling": "auto-trailing-slash",
"run_worker_first": ["/*", "!/_next/*", "!/*.png", "!/*.xml"]
}
}줄마다 하는 일은 이렇다.
| 이름 | 설명 |
|---|---|
| assets.directory | 올릴 폴더. Next.js 정적 내보내기 결과인 ./out |
| assets.not_found_handling | 404-page 는 없는 주소에 404.html 을 404 상태로 응답 |
| assets.html_handling | auto-trailing-slash 는 /about 요청에 about.html 을 응답 |
| main, assets.binding | 301 이동이나 API 를 Worker 코드로 처리할 때만 지정. 순수 정적 사이트는 생략 |
| routes 의 custom_domain | 사용자 지정 도메인 연결. DNS 레코드와 인증서를 Cloudflare 가 만듦 |
| workers_dev, preview_urls | *.workers.dev 주소와 미리보기 주소의 사용 여부 |
파일만 올리는 사이트라면 main 과 assets.binding 은 빼도 된다. 나는 이 둘을 남겨 두었는데 이유는 조금 뒤에 나온다.
npm run deploy
빌드와 업로드는 package.json 스크립트 하나로 묶었다.
{
"scripts": {
"deploy": "next build && wrangler deploy"
}
}npm run deploy배포가 끝나면 연결된 도메인과 버전 ID 가 찍힌다.
Deployed graphite triggers
graphite.is (custom domain)
www.graphite.is (custom domain)
Current Version ID: 44c6a94f-c07c-40ed-83d7-9bb07aa21e00custom_domain 으로 넣은 두 주소가 나란히 보이면 도메인 연결까지 된 것이다. DNS 레코드와 인증서는 클라우드플레어가 만들어 둔다.
정적 사이트에 남은 301 이동
앞에서 미뤄 둔 redirects 이야기다. 정적 내보내기에서는 next.config 의 redirects 가 동작하지 않는데 내게는 보내야 할 주소가 있었다. www.graphite.is 와 *.workers.dev 로 들어온 요청은 graphite.is 로 보내야 했고 예전 /blog/… 주소는 /posts/… 로 넘겨야 했다. 이 일을 worker/redirect.js 가 맡는다. 그래서 파일만 올리는 사이트인데도 main 을 지정했다.
같은 Worker 에는 /api/visit 도 들어 있다. 방문자 수를 D1 에 날짜별 합계로만 쌓는 작은 API 다. 정적 사이트에 API 하나 붙이자고 서버를 따로 세울 필요는 없었다.
어떤 요청이 Worker 를 거치나
Worker 코드를 붙이고 나면 요청을 세는 방식이 달라진다. 정적 자산 요청은 무료이고 횟수 제한도 없다. Worker 스크립트를 거치는 요청만 Workers 요금 기준으로 센다.
run_worker_first 에 /* 를 넣으면 모든 요청이 Worker 를 먼저 지나간다. 스크립트나 이미지, 데이터 파일은 Worker 가 손댈 일이 없으니 느낌표로 시작하는 제외 패턴으로 빼서 정적 자산이 바로 응답하게 했다. 위 설정의 run_worker_first 에서 첫 항목 뒤에 붙은 것들이 그 제외 패턴이다.
이게 비용만의 문제는 아니었다. 무료 요금제에서 한도를 넘기면 Worker 를 거치는 요청에는 429 가 돌아간다. 정적 자산으로 대신 응답해 주지도 않는다. 제외 패턴으로 뺀 요청은 한도와 상관없이 정적 자산이 응답한다. 과금 기준은 요금과 제한 문서에 있다.
로그인 없이 열리던 주소
비공개 화면은 Cloudflare Access 로 막아 두었다. 그런데 Access 앱의 대상 주소가 graphite.is 였다. 같은 Worker 는 *.workers.dev 주소로도 열리고 그 주소로 들어가면 같은 파일이 로그인 없이 열렸다.
고친 건 두 줄이다. workers_dev 와 preview_urls 를 false 로 두어 두 주소를 껐다. 위 설정에서 두 값이 false 인 이유다.
배포 명령은 npm run deploy 한 줄이다. wrangler.jsonc 의 나머지 줄은 대부분 Worker 를 거치지 않을 경로와 꺼 둘 주소를 정하는 줄이다.