라우트 캐싱

이 페이지

Astro의 라우트 캐싱은 온디맨드 페이지의 렌더링된 응답을 저장하므로, 캐시 적중 시에는 렌더링을 건너뜁니다. EmDash는 페이지가 렌더링하는 콘텐츠에 대한 캐시 태그를 제공하고, 콘텐츠가 변경되면 해당 태그를 무효화합니다. 페이지를 며칠 동안 캐시해 두어도 게시한 내용은 보통 몇 초 안에 반영됩니다.

오브젝트 캐시는 쿼리 결과를 저장하며, 모든 페이지는 여전히 렌더링됩니다. 캐시 프로바이더를 설정하면 익명 렌더링은 오브젝트 캐시를 건너뛰고 데이터베이스에서 읽으므로, 무효화된 페이지를 다시 채우는 렌더링이 오래된 스냅샷에서 나오는 일이 없습니다(캐시되는 내용 참조). 그다음에는 라우트 캐시가 익명 방문자에게 응답하고, 오브젝트 캐시는 주로 로그인한 편집자의 렌더링 속도를 높입니다.

캐시 프로바이더 선택

라우트 캐싱에는 캐시 프로바이더가 필요합니다. 배포 환경에 맞는 프로바이더를 사용하세요.

배포 환경프로바이더가져오는 위치
Node.jsmemoryCache()astro/config
CloudflarecacheCloudflare()@astrojs/cloudflare/cache

memoryCache()는 서버 프로세스의 메모리에 항목을 보관합니다. cacheCloudflare()는 CDN 프로바이더로, 응답을 Cloudflare Workers Cache에 저장하며 캐시 적중 시에는 Worker가 실행되지 않습니다. Astro의 Netlify 및 Vercel 어댑터도 CDN 프로바이더를 제공합니다. 어댑터 캐시 프로바이더를 참조하세요. EmDash는 프로바이더와 무관한 Astro의 cache.invalidate() API를 통해 무효화하므로, 해당 태그는 어떤 프로바이더에서도 동작합니다. 프로바이더에 따라 달라지는 동작은 프로바이더별 차이점에서 다룹니다. astro dev는 캐시를 전혀 하지 않으므로, 캐싱은 프로덕션 빌드로 테스트하세요.

라우트 캐싱 설정

프로바이더를 등록하고, 어떤 라우트를 얼마 동안 어떤 태그로 캐시할 수 있는지 선언합니다. 이 예제는 Cloudflare 프로바이더를 사용합니다. Node에서는 astro/config에서 memoryCache를 가져와 cache: { provider: memoryCache() }를 사용하세요.

import { defineConfig } from "astro/config";
import { cacheCloudflare } from "@astrojs/cloudflare/cache";

export default defineConfig({
	// ...기존 output, 어댑터, 통합 설정
	cache: {
		provider: cacheCloudflare(),
	},
	routeRules: {
		"/": { maxAge: 86_400, swr: 604_800, tags: ["posts", "pages", "all"] },
		"/blog": { maxAge: 86_400, swr: 604_800, tags: ["posts", "all"] },
		"/blog/[slug]": { maxAge: 86_400, swr: 604_800, tags: ["posts", "all"] },
		"/sitemap.xml": { maxAge: 86_400, swr: 604_800, tags: ["posts", "pages", "all"] },
		"/sitemap-[collection].xml": { maxAge: 86_400, swr: 604_800, tags: ["posts", "pages", "all"] },
	},
});
  • maxAge는 라우트가 다시 렌더링되기 전까지 캐시가 항목을 제공하는 시간입니다. EmDash는 게시 시 오래된 항목을 무효화하지만(아래 참조), 무효화가 실패할 수 있으므로 maxAge는 페이지가 오래된 상태로 남을 수 있는 시간의 상한이기도 합니다. 여기에 더해 swr가 재검증하는 동안 오래된 응답이 한 번 반환됩니다. 긴 값을 선택하기 전에 프로바이더별 차이점을 확인하세요.
  • swr를 지정하면 캐시가 백그라운드에서 재검증하는 동안에도 오래된 항목을 계속 제공하므로, 페이지가 한 번 캐시된 후에는 방문자가 렌더링을 기다리는 일이 거의 없습니다.
  • tags는 무효화를 위해 항목에 이름을 붙입니다. 각 라우트에는 그 라우트가 렌더링하는 컬렉션을 컬렉션 이름으로 태그하세요. EmDash의 기본 제공 무효화는 컬렉션 이름을 사용하므로, 일치하는 태그가 자동으로 무효화됩니다. 캐시되는 모든 라우트에 범용 추가 태그(여기서는 all이며, 컬렉션 이름이 아니므로 EmDash가 스스로 무효화하는 일은 없습니다)를 붙여 두면 비상 스위치로 쓸 수 있습니다. 예를 들어 직접 보호하는 엔드포인트에서 context.cache.invalidate({ tags: ["all"] })로 이 태그를 무효화하면 모든 항목이 한 번에 지워집니다.

쿼리 헬퍼는 페이지 안에서 Astro.cache.set()에 전달할 수 있는 cacheHint도 반환합니다. 그 태그는 정확한 항목(getEmDashEntry), 컬렉션(getEmDashCollection), 또는 크롬 리소스(getMenuWithCacheHint, getSiteSettingsWithCacheHint, getTaxonomyTermsWithCacheHint, getWidgetAreaWithCacheHint)를 가리킵니다. 이는 EmDash가 쓰기 시점에 무효화하는 것과 같은 태그이므로, 힌트를 전달하는 페이지는 자신이 렌더링하는 항목이나 크롬 리소스가 기록될 때 무효화되며, 라우트 규칙에 이름이 없는 것도 포함됩니다.

---
import { getEmDashEntry } from "emdash";

const { slug } = Astro.params;
if (!slug) return Astro.rewrite("/404");

const { entry, cacheHint } = await getEmDashEntry("posts", slug);
if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
---

Astro.cache.set()은 라우트 규칙과 병합되며, 태그는 여러 호출에 걸쳐 누적됩니다. 캐시 힌트는 태그를 가지고 있지만 maxAge는 없으므로, 페이지를 얼마나 오래 캐시할지는 라우트 규칙이 결정합니다.

모든 공개 라우트에 명시적인 규칙을 지정하세요. 규칙은 요청 경로와 일치하므로 공개 라우트를 하나씩 나열하세요. 사이트 루트의 /[...slug] 같은 나머지 매개변수는 /_emdash/ 아래의 관리자 및 API 라우트와도 일치합니다.

게시 시 무효화는 기본 제공

게시된 콘텐츠가 변경될 때마다 EmDash의 콘텐츠 API 라우트는 라우트 캐시를 무효화합니다. 항목의 게시, 게시 취소, 삭제, 휴지통에서 복원, 그리고 게시된 항목에 반영되는 변경 사항의 저장은 태그 [collection, id]를 무효화합니다. 항목을 생성하거나 복제해도 아무것도 무효화되지 않습니다. 새 항목은 초안이기 때문입니다. 따라서 (위처럼) 컬렉션 이름으로 태그된 라우트 규칙은 maxAge가 길더라도 보통 게시 후 몇 초 안에 새로 고쳐집니다.

사이트 크롬도 포함됩니다. 사이트 설정을 저장하면 emdash:settings가 무효화되고, 메뉴 편집은 emdash:menu:{name}을, 분류 체계와 위젯 영역의 쓰기는 그에 대응하는 emdash:taxonomy:{name} / emdash:widget-area:{name} 태그를 무효화합니다. 페이지가 getMenuWithCacheHint(), getSiteSettingsWithCacheHint(), getTaxonomyTermsWithCacheHint() 또는 getWidgetAreaWithCacheHint()의 cacheHint를 사용하면 이 태그들이 자동으로 적용됩니다.

메뉴 태그는 메뉴 편집만 포괄합니다. 메뉴가 링크하는 항목을 게시, 게시 취소, 휴지통으로 이동 또는 복원하거나, 링크된 항목 또는 택소노미 용어의 슬러그를 변경해도 메뉴의 태그는 무효화되지 않습니다. 메뉴를 표시하는 페이지는 해당 페이지의 태그에 그 항목, 그 컬렉션 또는 그 용어의 택소노미도 포함되어 있을 때 이러한 변경을 반영하며, 그렇지 않으면 페이지가 만료될 때 반영합니다.

리디렉션을 생성, 편집 또는 삭제하면 해당 리디렉션의 소스 경로에 있는 캐시된 페이지가 (끝에 슬래시가 있는 경우와 없는 경우 모두) 무효화되어, 다음 요청부터 리디렉션이 적용됩니다.

이전 리비전 복원, 그리고 바이라인, 미디어 라이브러리, 댓글의 변경은 아무것도 무효화하지 않습니다. 이들의 영향을 받는 캐시된 페이지는 만료되거나 이를 렌더링하는 항목이 다시 저장될 때 업데이트됩니다. 예약 게시가 게시된 항목의 태그를 무효화하는 것은 Cloudflare에서만 해당합니다. 프로바이더별 차이점을 참조하세요.

이것이 태그 규칙이 중요한 이유이기도 합니다. EmDash는 컬렉션 이름, 항목 ID, emdash:* 크롬 태그를 무효화합니다. 라우트 규칙에서 다른 태그 이름을 사용하면 기본 제공 무효화가 일치하지 않으며, 오래된 항목은 maxAge가 만료될 때까지 남아 있게 됩니다.

응답을 캐시에서 제외하기

라우트 규칙은 요청 경로와 일치하므로, 긴 TTL로 캐시하고 싶지 않은 응답에도 적용됩니다. EmDash는 로그인한 사용자를 위해 렌더링된 응답, Cache-Control: private 또는 no-store와 함께 전송된 응답, 그리고 404 응답을 자동으로 라우트 캐시에서 제외합니다. 그 밖의 오류 응답과 직접 작성한 미들웨어 리디렉션은 템플릿의 몫이며, 요청마다 Astro.cache.set(false)로 제외합니다.

Astro.cache.set(false)는 다음 Astro.cache.set() 호출이 있을 때까지만 유지됩니다. 레이아웃은 페이지 프런트매터 이후에 렌더링되므로, 캐시 힌트를 설정하는 레이아웃은 제외했던 페이지의 캐싱을 다시 켭니다. 제외 설정을 레이아웃에 (예: prop으로) 전달하고, 그것이 설정되어 있으면 레이아웃의 Astro.cache.set() 호출을 건너뛰세요.

미리보기 및 편집 링크는 자동으로 처리됩니다

EmDash의 미들웨어는 다음 응답을 스스로 라우트 캐시에서 제외하므로 템플릿 코드가 필요 없습니다. ?_preview= 토큰이 포함된 요청(유효 여부와 상관없습니다. 이 URL은 토큰마다 다르며, 그렇지 않으면 토큰이 만료된 후에도 캐시된 초안에 접근할 수 있기 때문입니다), 그리고 편집기 툴바가 삽입된 응답입니다. toolbar: "client"를 사용하면 ?_edit URL에도 동일하게 적용됩니다. 유효한 미리보기 응답과 툴바가 삽입된 응답에는 브라우저와 다운스트림 CDN을 위해 Cache-Control: private, no-store가 추가로 표시됩니다.

오류 응답

EmDash는 모든 404를 라우트 캐시에서 제외하므로, 없던 항목을 나중에 게시하면 바로 표시됩니다. 캐시된 라우트에서 렌더링된 500 같은 다른 오류 응답은 규칙의 TTL을 그대로 따릅니다. Astro.cache.set(false)로 제외하세요.

미들웨어의 리디렉션

리디렉션으로 처리를 중단하는 미들웨어(예: 호스트 정규화)는 context.cache?.set(false)로 제외해야 합니다. 그렇지 않으면 해당 리디렉션이 라우트 규칙의 TTL로 캐시되며, 호스트 이름별로 캐시 항목을 구분하지 않는 CDN 프로바이더에서는 정식 호스트 이름에 대한 요청에도 제공되어 즉시 리디렉션 루프가 발생할 수 있습니다. 옵셔널 체이닝을 사용하세요. 어떤 라우트와도 일치하지 않는 URL의 404 페이지처럼 일부 응답에서는 context.cache가 undefined입니다.

편집자와 캐시된 페이지

라우트 캐시는 URL당 하나의 공유 변형을 저장하며 캐시 키는 쿠키를 무시하므로, 캐시 적중은 편집자의 세션을 반영하지 않습니다. 기본 서버 렌더링 툴바에서는 공개 사이트를 둘러보는 편집자에게 보통 툴바가 없는 캐시된 익명 변형이 제공되며, emdash-edit-mode 쿠키는 캐시된 URL에 영향을 주지 않습니다. 로그인한 편집자를 위해 렌더링된 페이지는 공유 캐시에 저장되지 않으며, 다음 익명 방문자가 이를 채웁니다.

라우트 캐싱을 사용하는 사이트에서는 toolbar: "client"를 설정하세요. 그러면 작은 스크립트가 캐시된 페이지에서 로그인한 편집자에게 편집 알약을 표시하고, 편집 보기는 항상 새로 렌더링되는 ?_edit URL에서 로드됩니다. 미리보기 링크는 어느 모드에서든 캐시를 우회합니다.

프로바이더별 차이점

memoryCache()를 사용하는 Node.js

  • 각 서버 프로세스는 자체 캐시를 유지하며, 무효화는 쓰기를 처리한 프로세스만 비웁니다. 여러 프로세스나 인스턴스를 실행하는 경우, 나머지는 maxAge가 만료될 때까지 캐시된 페이지를 계속 제공합니다.
  • 재시작하면 캐시가 비워집니다.
  • memoryCache()는 maxAge가 있는 응답만 저장하므로, 규칙이 없는 라우트는 페이지가 캐시 힌트와 함께 Astro.cache.set()을 호출하더라도 캐시되지 않습니다.
  • Node에서는 예약 게시가 라우트 캐시를 무효화하지 않습니다. 예약된 항목을 나열하는 페이지는 캐시된 사본이 만료된 후에야 해당 항목을 표시하므로, 예약된 콘텐츠를 나열하는 라우트에서는 maxAge를 짧게 유지하세요.

cacheCloudflare()를 사용하는 Cloudflare

  • 이 프로바이더는 Cloudflare-CDN-Cache-Control 및 Cache-Tag 응답 헤더를 기록합니다. Workers Cache가 두 헤더를 모두 사용하며, 클라이언트에는 보이지 않고, 페이지가 브라우저용으로 설정한 Cache-Control 헤더에도 영향을 주지 않습니다.
  • 캐시 프로바이더가 설정되어 있으면 @astrojs/cloudflare 어댑터가 Workers Cache를 자동으로 활성화합니다. 빌드 시 wrangler deploy가 사용하는 생성된 Wrangler 구성에 "cache": { "enabled": true }가 기록됩니다.
  • 규칙이 없는 라우트는 no-store로 표시되지만, 렌더링되는 동안 Astro.cache.set()을 호출하는 것이 아무것도 없을 때에 한합니다. getSiteSettingsWithCacheHint()와 getMenuWithCacheHint()가 반환하는 것처럼 maxAge가 없는 캐시 힌트는 수명이 없는 Cloudflare-CDN-Cache-Control: public을 생성하므로, Workers Cache는 기본 캐시 수명(200 응답의 경우 2시간)으로 되돌아갑니다. 레이아웃이 이러한 힌트를 설정하면, 그 레이아웃을 사용하는 모든 페이지는 규칙 유무와 관계없이 캐시 가능해집니다.
  • 기본적으로 Worker 버전이 캐시 키의 일부이므로, 배포할 때마다 빈 캐시에서 시작하며 배포 직후의 첫 요청은 Worker에서 렌더링됩니다. 배포 간에 항목을 유지하려면 Wrangler 구성에 "cache": { "enabled": true, "cross_version_cache": true }를 설정하세요(버전 간 캐싱 참조). 이 경우 템플릿 변경 사항은 영향을 받는 항목이 만료되거나 무효화된 후에야 표시됩니다.
  • 예약 게시는 Cron Trigger가 게시하는 항목의 태그를 무효화합니다.

퍼지 속도 제한

Cloudflare 프로바이더는 Workers Cache를 퍼지하는 방식으로 무효화합니다. Workers Cache 퍼지에는 계정의 요금제와 관계없이 항상 Cloudflare의 무료 요금제 퍼지 제한이 적용됩니다. 계정당 분당 5개 요청이며 버스트는 25입니다(퍼지 속도 제한 참조). 대부분의 콘텐츠 쓰기는 각각 퍼지 요청을 1회 소모합니다. 게시된 콘텐츠를 변경하는 저장(게시된 항목에 직접 저장하는 컬렉션의 자동 저장 포함), 게시, 게시 취소, 삭제, 복원, 예약, 항목에 분류 체계 용어 할당이 해당하며, 설정, 메뉴, 분류 체계, 위젯의 모든 편집도 포함됩니다. 리디렉션 쓰기는 소스 경로 변형마다 퍼지 요청을 1회 소모하므로, 리디렉션의 소스가 변경되면 최대 4회가 됩니다.

속도 제한에 걸린 퍼지는 관리자 화면에 오류 없이 실패하며, 영향을 받는 페이지는 maxAge가 만료될 때까지 캐시된 버전을 유지합니다. 일괄 편집 중이거나 여러 편집자가 동시에 작업할 때는, 오래된 콘텐츠가 남는 최악의 경우로 감수할 수 있는 maxAge를 선택하세요.

브라우저 캐싱은 별개입니다

라우트 캐시는 EmDash가 무효화하기 때문에 긴 TTL을 사용할 수 있습니다. 브라우저 캐시는 무효화되지 않습니다. EmDash 힌트는 퍼지 태그를 제공할 뿐, 자동 Last-Modified 검증자나 Astro의 암묵적인 Cache-Control: no-cache 헤더를 제공하지 않습니다. 항목 하나의 수정 시각은 설정, 관련 목록, 삭제된 항목을 포함하지 않으므로, 이를 페이지 검증자로 사용하면 조건부 304 응답을 통해 오래된 본문이 그대로 유지될 수 있습니다.

브라우저 캐싱은 Astro.response.headers.set("Cache-Control", ...)로 설정하세요. 방문할 때마다 새로 확인하려면 no-cache를, 그렇지 않으면 짧은 수명(일 단위가 아니라 분 단위)을 사용하세요. 조건부 요청을 사용하려면 응답의 어떤 의존 대상이 바뀔 때마다 타임스탬프가 앞으로 가는 자체 lastModified 힌트를 제공하세요.

항목에 표시할 수정 날짜에는 EmDash가 자동으로 채우지 않는 cacheHint.lastModified 대신 entry.data.updatedAt을 사용하세요.