Workers Cache: 더 이상 Worker가 매번 렌더링하지 않아도 됩니다

Cloudflare Workers가 2017년 처음 등장했을 때만 해도 '오리진 앞에 두는 리버스 프록시' 역할이 주였습니다. 하지만 지금은? Worker 자체가 오리진입니다. Astro, Next.js, Remix, SvelteKit 등 주요 프레임워크가 모두 Cloudflare 어댑터를 제공하면서, Worker는 단순한 요청 변환 도구를 넘어 서버 그 자체로 진화했습니다.

문제는 'Worker가 오리진'일 때 발생합니다. 매 요청마다 코드가 실행되니까요. 아무리 Workers 런타임이 빠르다 해도, 매 페이지 로드마다 렌더링 비용이 발생합니다. CPU 시간이 청구되고, 사용자는 그만큼의 레이턴시를 감수해야 합니다.

Workers Cache는 이 구조를 완전히 뒤집습니다. Cloudflare의 캐시가 이제 Worker 앞단에 위치합니다.

사용자 → Cloudflare CDN 캐시 → (Cache Hit: Worker 실행 안 함) → 응답
사용자 → Cloudflare CDN 캐시 → (Cache Miss: Worker 실행) → 응답 저장 → 응답

Cache Hit이 발생하면 Worker는 전혀 실행되지 않습니다. CPU 비용은 0이고, 응답은 CDN 엣지에서 즉시 반환됩니다. 이게 바로 SSR 앱이 그토록 원했던 '정적 사이트 속도 + 동적 렌더링 신선함'의 조합입니다.

Cloudflare Workers Cache architecture diagram showing cache layers between user and origin IT Technology Image

설정은 단 한 줄, 핵심은 Cache-Control 헤더

Workers Cache를 활성화하는 방법은 놀라울 정도로 간단합니다. wrangler.jsonccache.enabled: true 한 줄이면 끝입니다.

{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2026-05-01",
  "cache": {
    "enabled": true
  }
}

이제 Worker의 응답에 표준 Cache-Control 헤더를 설정하는 것만으로 캐싱이 동작합니다. 별도의 캐시 규칙 엔진이나 페이지 규칙이 필요 없습니다.

// Worker 코드: 캐싱의 모든 것은 HTTP 헤더로 제어
export default {
  async fetch(request) {
    const html = await renderPage(request);
    return new Response(html, {
      headers: {
        "Content-Type": "text/html; charset=utf-8",
        // 5분간 신선, 이후 1시간 동안 stale-while-revalidate
        "Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
      },
    });
  },
};

stale-while-revalidate: 체감 성능의 핵심

stale-while-revalidate 지시자는 캐시가 만료된 후에도 즉시 낡은(stale) 응답을 반환하고, 백그라운드에서 새 응답을 가져오도록 합니다. 사용자는 절대 기다리지 않습니다.

  • Fresh window (max-age): 캐시 응답 반환. Worker 실행 안 함.
  • Stale window (stale-while-revalidate): 낡은 응답 반환. Worker는 백그라운드에서 새로고침.
  • Both expired: Worker 실행 후 응답. 사용자는 이 한 번만 기다림.

실무 팁: 제품 카탈로그라면 max-age=300, stale-while-revalidate=3600으로 설정하면 방문자는 거의 항상 캐시 속도를 경험하고, Worker는 5분에 한 번씩만 실행됩니다.

Developer configuring Workers Cache in wrangler.jsonc file on a laptop Development Concept Image

Vary 헤더, 멀티테넌트 캐시, 그리고 엔트리포인트 간 캐싱

Vary: 하나의 URL, 여러 표현

브라우저에 HTML, API 클라이언트에 JSON, 언어별로 다른 콘텐츠를 제공해야 한다면? Vary 헤더가 정답입니다.

export default {
  async fetch(request) {
    const accept = request.headers.get("Accept") ?? "";
    const wantsWebp = accept.includes("image/webp");
    const body = wantsWebp ? await fetchWebpImage() : await fetchJpegImage();
    return new Response(body, {
      headers: {
        "Content-Type": wantsWebp ? "image/webp" : "image/jpeg",
        "Cache-Control": "public, max-age=3600",
        "Vary": "Accept",  // Accept 헤더 값별로 별도 캐시
      },
    });
  },
};

ctx.props: 멀티테넌트 안전성

사용자별 데이터를 캐싱할 때 가장 큰 걱정은 '다른 사용자의 데이터가 노출되는 것'입니다. Workers Cache는 ctx.props를 캐시 키의 일부로 사용하여 이 문제를 해결합니다.

import { WorkerEntrypoint } from "cloudflare:workers";

interface Props { userId: string; }

export default class Backend extends WorkerEntrypoint<Env, Props> {
  async fetch(request: Request): Promise<Response> {
    // ctx.props.userId가 캐시 키의 일부가 됩니다.
    // 같은 URL이라도 userId가 다르면 별도 캐시 엔트리.
    const { userId } = this.ctx.props;
    const data = await loadUserData(userId);
    return new Response(JSON.stringify(data), {
      headers: {
        "Content-Type": "application/json",
        "Cache-Control": "public, max-age=300",
      },
    });
  }
}

엔트리포인트 간 캐싱: 진정한 컴포저블 아키텍처

가장 혁신적인 부분은 같은 Worker 내의 서로 다른 엔트리포인트 사이에 캐시를 끼워 넣을 수 있다는 점입니다. 인증은 매번 실행해야 하지만, 데이터 조회는 캐싱하고 싶다면?

{
  "cache": { "enabled": true },
  "exports": {
    "default": { "type": "worker", "cache": { "enabled": false } },
    "CachedBackend": { "type": "worker", "cache": { "enabled": true } }
  }
}
export default {
  async fetch(request, env, ctx) {
    // 인증: 항상 실행
    const userId = await authenticate(request);
    if (!userId) return new Response("Unauthorized", { status: 401 });

    // 캐싱된 백엔드 호출
    const forwarded = new Request(request);
    forwarded.headers.delete("Authorization");
    return ctx.exports.CachedBackend.fetch(forwarded, {
      props: { userId },
    });
  },
} satisfies ExportedHandler;

이 패턴을 활용하면 인증, URL 정규화, 트래킹 파라미터 제거, Durable Object 캐싱 등을 하나의 Worker 안에서 계층적으로 구성할 수 있습니다. 다른 플랫폼에서는 찾아보기 힘든 수준의 유연성입니다.

Server-side rendered application performance optimization with Cloudflare Workers Cache Algorithm Concept Visual

실무 적용 시 주의사항 및 결론

주의사항

  1. 요청 요금: 캐시 히트는 CPU 시간이 청구되지 않지만, 요청 건수는 표준 요금이 부과됩니다. 정적 에셋 요청도 이제 캐시 조회를 거치므로 요금이 발생할 수 있습니다.
  2. 멀티테넌트 캐시 키 설계: ctx.props를 너무 세분화하면 캐시 히트율이 떨어집니다. 사용자 ID 대신 '권한 그룹' 단위로 키를 설계하는 것도 고려해보세요.
  3. Vary 헤더 과사용: Vary: *는 캐싱을 완전히 비활성화합니다. 꼭 필요한 헤더만 지정하세요.

국내 개발 생태계에서의 적용 맥락

국내 SI/스타트업 환경에서 Workers Cache는 서버리스 SSR 비용 최적화에 큰 도움이 됩니다. 기존에는 SSR을 하려면 EC2나 Cloud Run 같은 컨테이너 기반 서버를 항시 띄워야 했지만, Workers Cache를 사용하면 트래픽이 적은 시간대에는 거의 모든 요청이 캐시 히트되어 비용이 급감합니다. 다만, 한국 리전의 Cloudflare 네트워크 상황과 Smart Placement의 데이터 근접성 최적화를 함께 고려해야 합니다.

다음 단계 학습 방향

  • Cloudflare Workers Cache 공식 문서에서 전체 기능과 예제를 확인하세요.
  • Astro, TanStack Start 등 프레임워크 통합 예제를 직접 실습해보세요.
  • stale-while-revalidateCache-Tag를 활용한 무효화 패턴을 익히면 더 정교한 캐싱 전략을 구축할 수 있습니다.

함께 보면 좋은 글

본 콘텐츠는 신뢰할 수 있는 출처를 바탕으로 AI 도구를 활용하여 초안이 작성되었으며, 편집자의 검토를 거쳐 발행되었습니다. 전문가의 조언을 대체하지 않습니다.