도메인 앞에 Worker를 세우는 순간 모든 방문자가 그 코드를 지나간다. 그래서 첫 배포는 아무것도 하지 않는 Worker였다. 들어온 요청을 그대로 폰으로 넘기기만 하는 코드다. 이 글은 그 “통과만 하는 Worker"에서 시작해 하나씩 역할을 빼앗아 온 과정이다.

1단계: fetch(request) 한 줄

DNS는 그대로 폰의 터널을 가리키고 있었다. 이 상태에서 라우트 mhub.uk/*를 Worker에 붙이면, Worker 안에서 fetch(request)를 호출했을 때 Cloudflare가 원래 목적지(폰)로 요청을 보낸다. 즉 Worker는 투명한 중계기가 된다.

export default {
  async fetch(request) {
    return fetch(request); // 일단 전부 폰으로
  },
};

이걸 먼저 배포한 이유는 “라우트를 붙였을 때 뭔가 깨지는가"를 서비스 중단 없이 확인하기 위해서였다. 깨지면 라우트만 떼면 그만이다. 아무 문제 없이 돌아가는 것을 보고 다음으로 갔다.

2단계: 스위치 하나로 정적 전환

정적 파일을 Worker의 assets로 내주는 코드를 넣되, 환경 변수 SERVE_STATIC이 "true"일 때만 켜지게 했다. 변수가 없으면 여전히 폰으로 통과한다. 이렇게 하면 코드 배포와 전환 시점을 분리할 수 있다. 코드는 미리 올려 두고, 변수를 켜는 순간이 전환이다. 잘못되면 변수를 끄면 폰으로 돌아간다.

전환 직전에 폰에서 발행된 글 20개를 GitHub에 동기화하고, baseURL을 https://mhub.uk/로 바꿨다. 그 전까지는 blog.mhub.uk가 대표 주소였기 때문에 전환 전 임시로 baseURL을 되돌렸다가 다시 바꾸는 소동도 있었다. 결국 10월 11일 아침에 변수를 켰고, 방문자가 보는 화면은 그대로인 채 서빙 주체만 폰에서 Cloudflare로 바뀌었다.

3단계: 폰으로 가는 길을 접두어로 좁히기

정적 전환 직후에는 Worker에 없는 파일이 있을 수 있었다. 폰에서 방금 올린 글이 아직 GitHub에 동기화되지 않은 경우다. 그래서 assets에서 404가 나면 폰으로 다시 물어보는 폴백을 두되, 아무 경로나 넘기지 않고 /posts/와 /uploads/로 시작하는 경로만 넘기게 했다. 글과 첨부 이미지만 폰에 있을 수 있고, 나머지 404는 진짜 404이기 때문이다. 이 접두어 목록은 나중에 폰 역할을 완전히 없앨 때 통째로 지워졌다.

www와 404 다듬기

포팅 정확도를 맞추는 자잘한 일도 이 단계에서 했다.

  • www.mhub.uk로 들어오면 https://mhub.uk 같은 경로로 301 이동. 쿼리 문자열도 유지한다.
  • 없는 주소는 폰으로 넘기지 않고 Worker가 assets의 /404 페이지를 404 상태 코드로 내준다. 상태 코드가 200인 “404처럼 생긴 페이지"는 검색엔진에 나쁘다.
  • 내부용 산출물(예약 일정 파일, 글 목록 색인 등)은 외부 요청에 404로 답하고 Worker 안에서만 읽는다.

스위치는 나중에 사라졌다

SERVE_STATIC 변수는 전환이 끝나고 이틀 뒤에 코드에서 지웠다. 폰으로 가는 폴백 자체가 사라져서 “끌 수 있는 스위치"가 더 이상 의미가 없어졌기 때문이다. 과도기 장치는 과도기가 끝나면 지워야 다음 사람이 헷갈리지 않는다. 인수인계 문서에도 “이 변수는 없다"고 명시했다.

돌아보며

무중단 전환의 핵심은 기술보다 순서였다. 통과만 하는 코드로 라우트를 먼저 검증하고, 기능은 꺼진 채 배포하고, 전환은 스위치로 하고, 폴백은 좁게 두고, 끝나면 지운다. 다음 글은 정적 사이트에서 가장 까다로운 문제, 예약 발행을 클라우드에서 어떻게 풀었는지다.