엣지 캐싱: 기존 CDN을 넘어

기존 CDN은 표준 HTTP 헤더를 따라 정적 자산을 캐시합니다. Cloudflare 작업자 개념을 훨씬 더 발전시킵니다. 캐시 API, 귀하의 코드 TypeScript는 캐시된 내용과 기간을 정밀하게 제어합니다. 캐시가 무효화되는 방법과 캐시가 오래되었을 때 적용할 대체 논리.

결과는 하나 프로그래밍 가능한 CDN: 규칙을 구성하는 대신 대시보드에서 정적으로, 즉각적으로 결정하는 비즈니스 로직을 작성하세요. 어떤 TTL과 어떤 캐시 키를 사용하여 응답을 캐시할 수 있는지입니다. 이러한 유연성 이는 REST API, 사용자 정의 페이지 및 반동적 콘텐츠에 특히 유용합니다.

무엇을 배울 것인가

  • Cloudflare Workers Cache API의 작동 방식 및 브라우저 캐시 API와의 차이점
  • 캐싱 전략: 캐시 우선, 네트워크 우선, 오래된 재검증 중
  • 사용자 정의 캐시 키: 사용자, 언어, 버전별로 캐시를 분리합니다.
  • Cloudflare Zone Purge API를 사용하여 URL, 태그, 접두사 무효화
  • 다양한 헤더: Accept-Encoding, Accept-Language를 위한 차별화된 캐시
  • 고급 패턴: KV를 사용한 캐시 워밍, 유예 기간 및 회로 차단기
  • 흔히 발생하는 실수와 이를 방지하는 방법

Cache API: 브라우저와의 기본 사항 및 차이점

Workers에 노출된 Cache API는 다음과 동일한 인터페이스를 따릅니다. 서비스 워커 캐시 API 브라우저이지만 중요한 차이점이 있습니다. Workers에서 캐시는 다음과 같습니다. 분산 모든 Cloudflare PoP에서: 프랑크푸르트의 작업자가 응답을 캐시하면, 그 대답은 런던이나 암스테르담에서는 자동으로 제공되지 않습니다. 모든 PoP에는 자신의 로컬 캐시.

// Accesso alla cache nel Worker
// In Workers esiste un unico "default" cache namespace
const cache = caches.default;

// Oppure cache named (isolata per nome, utile per namespace logici)
const apiCache = await caches.open('api-v2');

// Le operazioni fondamentali:
// cache.match(request) -> Response | undefined
// cache.put(request, response) -> void
// cache.delete(request) -> boolean

캐시 API: 중요한 제약조건

  • HTTP 요청만(브라우저와 같은 임의의 URL은 아님)
  • 다음을 사용하여 응답을 캐시할 수 없습니다. Vary: *
  • 캐시는 PoP별로 이루어집니다. 다음과 같은 자동 데이터 센터 간 무효화가 없습니다. cache.delete()
  • 상태 206(부분 콘텐츠)의 응답은 캐시할 수 없습니다.
  • 적용되는 최대 TTL은 31일입니다.

전략 1: 대체 네트워크를 사용한 캐시 우선

데이터가 거의 변경되지 않는 API에 대한 가장 일반적인 전략: 캐시에서 제공 사용 가능한 경우 그렇지 않으면 소스로 이동하여 캐시를 채웁니다.

// worker.ts - Cache-First Strategy

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    // Solo richieste GET sono cacheable
    if (request.method !== 'GET') {
      return fetch(request);
    }

    const cache = caches.default;

    // 1. Controlla la cache
    let response = await cache.match(request);

    if (response) {
      // Cache HIT: aggiungi header diagnostico e restituisci
      const headers = new Headers(response.headers);
      headers.set('X-Cache-Status', 'HIT');
      return new Response(response.body, {
        status: response.status,
        headers,
      });
    }

    // 2. Cache MISS: fetch dall'origin
    response = await fetch(request);

    // 3. Clona la risposta (il body e un ReadableStream, consumabile una sola volta)
    const responseToCache = response.clone();

    // 4. Metti in cache con ctx.waitUntil() per non bloccare la risposta al client
    ctx.waitUntil(
      cache.put(request, responseToCache)
    );

    // 5. Restituisci la risposta originale con header diagnostico
    const headers = new Headers(response.headers);
    headers.set('X-Cache-Status', 'MISS');
    return new Response(response.body, {
      status: response.status,
      headers,
    });
  },
};

interface Env {}

전략 2: 무효화 중 재검증

전략 재검증하는 동안 오래된 그것은 최선을 다하는 사람이다 데이터 신선도와 인지된 속도 사이의 절충: 클라이언트는 언제나 백그라운드에서 실행 중이더라도 캐시로부터 즉각적인 응답 작업자는 다음 요청을 위해 캐시를 업데이트합니다.

// Stale-While-Revalidate implementato manualmente
// (Cloudflare supporta anche il header standard, ma questa versione offre più controllo)

const CACHE_TTL = 60;        // Secondi prima che la cache sia "fresh"
const STALE_TTL = 300;       // Secondi aggiuntivi in cui la cache e "stale but usable"

interface CacheMetadata {
  cachedAt: number;
  ttl: number;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    if (request.method !== 'GET') return fetch(request);

    const cache = caches.default;

    // Crea una Request con una custom cache key che include i metadata
    const cacheKey = new Request(request.url, {
      headers: request.headers,
    });

    const cached = await cache.match(cacheKey);

    if (cached) {
      const cachedAt = parseInt(cached.headers.get('X-Cached-At') ?? '0');
      const age = (Date.now() - cachedAt) / 1000;

      if (age < CACHE_TTL) {
        // FRESH: servi dalla cache senza revalidazione
        return addCacheHeaders(cached, 'FRESH', age);
      }

      if (age < CACHE_TTL + STALE_TTL) {
        // STALE: servi dalla cache ma revalida in background
        ctx.waitUntil(revalidate(cacheKey, cache));
        return addCacheHeaders(cached, 'STALE', age);
      }
    }

    // MISS o troppo vecchio: fetch sincrono
    return fetchAndCache(cacheKey, cache, ctx);
  },
};

async function revalidate(cacheKey: Request, cache: Cache): Promise<void> {
  const fresh = await fetch(cacheKey.url);
  if (fresh.ok) {
    const toCache = addTimestamp(fresh);
    await cache.put(cacheKey, toCache);
  }
}

async function fetchAndCache(
  cacheKey: Request,
  cache: Cache,
  ctx: ExecutionContext
): Promise<Response> {
  const response = await fetch(cacheKey.url);

  if (response.ok) {
    const toCache = addTimestamp(response.clone());
    ctx.waitUntil(cache.put(cacheKey, toCache));
  }

  const headers = new Headers(response.headers);
  headers.set('X-Cache-Status', 'MISS');
  return new Response(response.body, { status: response.status, headers });
}

function addTimestamp(response: Response): Response {
  const headers = new Headers(response.headers);
  headers.set('X-Cached-At', String(Date.now()));
  // Cache-Control: max-age elevato per far "sopravvivere" la risposta in cache
  headers.set('Cache-Control', 'public, max-age=86400');
  return new Response(response.body, { status: response.status, headers });
}

function addCacheHeaders(response: Response, status: string, age: number): Response {
  const headers = new Headers(response.headers);
  headers.set('X-Cache-Status', status);
  headers.set('Age', String(Math.floor(age)));
  return new Response(response.body, { status: response.status, headers });
}

interface Env {}

사용자 정의 캐시 키: 상황별 캐시 분리

기본적으로 캐시 키는 요청의 전체 URL입니다. 그러나 종종 당신은 필요합니다 언어, API 버전, 사용자 또는 장치 계층으로 구분된 캐시입니다. 해결책은 하나 만들어 맞춤 캐시 키 객체로서 Request 짧은 URL로.

// Cache differenziata per lingua e versione API

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const cache = caches.default;
    const url = new URL(request.url);

    // Estrai parametri rilevanti per la cache key
    const lang = request.headers.get('Accept-Language')?.split(',')[0]?.split('-')[0] ?? 'en';
    const apiVersion = url.searchParams.get('v') ?? 'v1';
    const tier = request.headers.get('X-User-Tier') ?? 'free';

    // Costruisci una URL sintetica come cache key
    // Non deve essere una URL reale, solo identificativa
    const cacheKeyUrl = new URL(request.url);
    cacheKeyUrl.searchParams.set('_ck_lang', lang);
    cacheKeyUrl.searchParams.set('_ck_v', apiVersion);
    // Non includiamo 'tier' nella key se vuoi condividere la cache tra tier

    const cacheKey = new Request(cacheKeyUrl.toString(), {
      method: 'GET',
      // Importante: non copiare headers di autenticazione nella cache key
      // altrimenti ogni utente avrebbe la propria cache entry
    });

    // Cerca nella cache con la custom key
    let response = await cache.match(cacheKey);

    if (response) {
      return response;
    }

    // Fetch dall'origin passando la richiesta originale (con auth headers)
    const originResponse = await fetch(request);

    if (originResponse.ok && isCacheable(originResponse)) {
      const toCache = setCacheHeaders(originResponse.clone(), 300);
      ctx.waitUntil(cache.put(cacheKey, toCache));
    }

    return originResponse;
  },
};

function isCacheable(response: Response): boolean {
  // Non mettere in cache risposte con dati personali o Set-Cookie
  if (response.headers.has('Set-Cookie')) return false;
  if (response.headers.get('Cache-Control')?.includes('private')) return false;
  if (response.headers.get('Cache-Control')?.includes('no-store')) return false;
  return true;
}

function setCacheHeaders(response: Response, maxAge: number): Response {
  const headers = new Headers(response.headers);
  headers.set('Cache-Control', `public, max-age=${maxAge}, s-maxage=${maxAge}`);
  // Rimuovi header che potrebbero impedire il caching
  headers.delete('Set-Cookie');
  return new Response(response.body, { status: response.status, headers });
}

interface Env {}

캐시 헤더: s-maxage, 재검증 중 오래된 것, 오류가 있는 경우 오래된 것

Cloudflare는 다음을 존중합니다. 표준 캐시 제어 헤더 그리고 그것을 확장 의미. 다음 지침을 이해하는 것이 중요합니다.

지령 의미 Esempio
max-age=N 브라우저 및 CDN용 TTL(N초) max-age=300
s-maxage=N CDN/프록시 전용 TTL(Cloudflare의 최대 기간 재정의) s-maxage=3600, max-age=60
stale-while-revalidate=N 재검증하는 동안 오래된 서비스를 제공하는 추가 시간(초) s-maxage=60, stale-while-revalidate=300
stale-if-error=N 원본이 오류를 반환하는 경우 오래된 서비스를 제공하는 데 걸리는 시간(초) stale-if-error=86400
no-store 어떤 상황에서도 캐시하지 마세요 민감한 데이터의 경우
private CDN이 아닌 캐시 브라우저만 해당 인증된 응답의 경우
// Esempio: API con s-maxage e stale-while-revalidate via header

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);

    // Routing con TTL differenziati per tipo di risorsa
    if (url.pathname.startsWith('/api/products')) {
      return fetchWithCacheHeaders(request, {
        sMaxAge: 300,           // 5 minuti fresh in CDN
        staleWhileRevalidate: 3600, // 1 ora stale accettabile
        staleIfError: 86400,    // 1 giorno stale in caso di errore origin
      });
    }

    if (url.pathname.startsWith('/api/user')) {
      // Dati utente: non cacheare in CDN
      return fetchWithCacheHeaders(request, {
        sMaxAge: 0,
        private: true,
      });
    }

    if (url.pathname.startsWith('/static')) {
      // Asset statici: cache aggressiva
      return fetchWithCacheHeaders(request, {
        sMaxAge: 31536000, // 1 anno
        immutable: true,
      });
    }

    return fetch(request);
  },
};

interface CacheOptions {
  sMaxAge?: number;
  staleWhileRevalidate?: number;
  staleIfError?: number;
  private?: boolean;
  immutable?: boolean;
}

async function fetchWithCacheHeaders(
  request: Request,
  options: CacheOptions
): Promise<Response> {
  const response = await fetch(request);
  const headers = new Headers(response.headers);

  let cacheControl = '';

  if (options.private) {
    cacheControl = 'private, no-store';
  } else {
    const parts: string[] = ['public'];
    if (options.sMaxAge !== undefined) parts.push(`s-maxage=${options.sMaxAge}`);
    if (options.staleWhileRevalidate) parts.push(`stale-while-revalidate=${options.staleWhileRevalidate}`);
    if (options.staleIfError) parts.push(`stale-if-error=${options.staleIfError}`);
    if (options.immutable) parts.push('immutable');
    cacheControl = parts.join(', ');
  }

  headers.set('Cache-Control', cacheControl);
  return new Response(response.body, { status: response.status, headers });
}

interface Env {}

무효화: URL, 태그 및 접두사 제거

cache.delete(request) 작업자가 실행되는 로컬 PoP에서만 캐시를 삭제하세요. 캐시를 무효화하려면 전 세계 모든 PoP에 걸쳐, API를 사용해야 합니다. Cloudflare 영역 퍼지 REST. 이것이 콘텐츠 관리를 위한 올바른 메커니즘입니다. 그리고 배포하세요.

// Invalidation globale tramite Cloudflare API
// Da usare tipicamente da un webhook CMS o da un Worker admin

interface PurgeOptions {
  files?: string[];      // URL specifici
  tags?: string[];       // Cache-Tag headers
  prefixes?: string[];   // URL prefix
  hosts?: string[];      // Tutti gli URL di un host
}

async function purgeCloudflareCache(
  zoneId: string,
  apiToken: string,
  options: PurgeOptions
): Promise<void> {
  const response = await fetch(
    `https://api.cloudflare.com/client/v4/zones/${zoneId}/purge_cache`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiToken}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(options),
    }
  );

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Purge failed: ${JSON.stringify(error)}`);
  }
}

// Worker che funge da webhook per invalidazione CMS
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    if (request.method !== 'POST') {
      return new Response('Method Not Allowed', { status: 405 });
    }

    // Verifica il secret del webhook
    const secret = request.headers.get('X-Webhook-Secret');
    if (secret !== env.WEBHOOK_SECRET) {
      return new Response('Unauthorized', { status: 401 });
    }

    const body = await request.json() as WebhookPayload;

    // Invalida le URL specifiche aggiornate dal CMS
    if (body.type === 'post.updated') {
      await purgeCloudflareCache(env.ZONE_ID, env.CF_API_TOKEN, {
        files: [
          `https://example.com/blog/${body.slug}`,
          `https://example.com/api/posts/${body.id}`,
          `https://example.com/sitemap.xml`,
        ],
      });
    }

    // Invalida per tag (richiede Cache-Tag header sulle risposte origin)
    if (body.type === 'category.updated') {
      await purgeCloudflareCache(env.ZONE_ID, env.CF_API_TOKEN, {
        tags: [`category-${body.categorySlug}`],
      });
    }

    return new Response(JSON.stringify({ purged: true }), {
      headers: { 'Content-Type': 'application/json' },
    });
  },
};

interface WebhookPayload {
  type: string;
  id?: string;
  slug?: string;
  categorySlug?: string;
}

interface Env {
  ZONE_ID: string;
  CF_API_TOKEN: string;
  WEBHOOK_SECRET: string;
}

캐시 태그: 의미 체계 무효화

I 캐시 태그 이는 무효화를 위한 가장 강력한 메커니즘입니다. 선택적. 헤더를 추가하여 작동합니다. Cache-Tag 답변에 : 각 응답에는 여러 개의 태그가 있을 수 있으며 모든 URL을 무효화할 수 있습니다. 단일 API 호출로 태그와 연결됩니다.

// Origin server o Worker che aggiunge Cache-Tag alle risposte

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const response = await fetch(request);
    const headers = new Headers(response.headers);

    // Aggiungi tag semantici basati sul contenuto
    const tags: string[] = [];

    // Tag per tipo di risorsa
    if (url.pathname.startsWith('/api/products')) {
      const productId = url.pathname.split('/')[3];
      tags.push('products');                    // Invalida tutti i prodotti
      if (productId) tags.push(`product-${productId}`); // Invalida questo prodotto specifico
    }

    if (url.pathname.startsWith('/api/categories')) {
      const catId = url.pathname.split('/')[3];
      tags.push('categories');
      if (catId) tags.push(`category-${catId}`);
    }

    // Tag per versione dell'API
    const apiVersion = url.pathname.split('/')[2];
    if (apiVersion?.startsWith('v')) {
      tags.push(`api-${apiVersion}`);
    }

    if (tags.length > 0) {
      // Cache-Tag: lista separata da virgole, max 16KB
      headers.set('Cache-Tag', tags.join(','));
    }

    return new Response(response.body, { status: response.status, headers });
  },
};

// Esempio di invalidazione per tag dopo un aggiornamento:
// POST /api/admin/purge
// { "tags": ["product-123", "categories"] }
// Invalida tutte le URL che hanno Cache-Tag: product-123 o categories

interface Env {}

고급 패턴: KV를 L2로 사용하는 캐시

Cache API에는 중요한 제한 사항이 있습니다. 프로그래밍 방식으로 액세스할 수 없습니다. 임의 읽기/쓰기의 경우 HTTP 요청에만 해당됩니다. 더 많은 패턴을 보려면 복잡한(예: 조정 무효화, 회로 차단기 또는 개체 캐시) 비 HTTP), 사용 L2 캐시로서의 작업자 KV.

// Cache a due livelli: Cache API (L1, HTTP) + KV (L2, programmabile)

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const url = new URL(request.url);
    const cacheKey = buildCacheKey(url);

    // L1: Cache API (piu veloce, locale al PoP)
    const l1Cache = caches.default;
    const l1Hit = await l1Cache.match(request);

    if (l1Hit) {
      return addHeader(l1Hit, 'X-Cache', 'L1-HIT');
    }

    // L2: KV Store (globale, programmabile, con TTL gestito da KV)
    const kvCached = await env.API_CACHE.getWithMetadata<CacheMetadata>(cacheKey);

    if (kvCached.value !== null) {
      const { value, metadata } = kvCached;

      // Ricostruisci una Response dalla stringa KV
      const cachedResponse = new Response(value, {
        headers: {
          'Content-Type': metadata?.contentType ?? 'application/json',
          'Cache-Control': 'public, max-age=60',
          'X-Cache': 'L2-HIT',
          'X-Cached-At': String(metadata?.cachedAt ?? 0),
        },
      });

      // Popola anche L1 per richieste successive nello stesso PoP
      ctx.waitUntil(l1Cache.put(request, cachedResponse.clone()));

      return cachedResponse;
    }

    // MISS su entrambi i livelli: fetch dall'origin
    const originResponse = await fetch(request);

    if (originResponse.ok) {
      const body = await originResponse.text();
      const contentType = originResponse.headers.get('Content-Type') ?? 'application/json';

      const metadata: CacheMetadata = {
        cachedAt: Date.now(),
        contentType,
        url: url.toString(),
      };

      // Salva in KV con TTL di 5 minuti
      ctx.waitUntil(
        env.API_CACHE.put(cacheKey, body, {
          expirationTtl: 300,
          metadata,
        })
      );

      // Salva anche in L1
      const toL1 = new Response(body, {
        headers: {
          'Content-Type': contentType,
          'Cache-Control': 'public, max-age=60',
          'X-Cache': 'MISS',
        },
      });
      ctx.waitUntil(l1Cache.put(request, toL1));

      return new Response(body, {
        headers: {
          'Content-Type': contentType,
          'X-Cache': 'MISS',
        },
      });
    }

    return addHeader(originResponse, 'X-Cache', 'BYPASS-ERROR');
  },
};

function buildCacheKey(url: URL): string {
  // Normalizza l'URL per la cache key (rimuovi query params non semantici)
  const params = new URLSearchParams(url.searchParams);
  params.delete('utm_source');
  params.delete('utm_medium');
  params.delete('_t'); // timestamp di cache-busting
  params.sort(); // ordine deterministico
  return `${url.pathname}?${params.toString()}`;
}

function addHeader(response: Response, key: string, value: string): Response {
  const headers = new Headers(response.headers);
  headers.set(key, value);
  return new Response(response.body, { status: response.status, headers });
}

interface CacheMetadata {
  cachedAt: number;
  contentType: string;
  url: string;
}

interface Env {
  API_CACHE: KVNamespace;
}

캐시 워밍: 캐시 미리 채우기

Il 캐시 워밍업 캐시를 먼저 미리 채우는 관행입니다. 실제 요청이 도착하면 배포 후 콜드 캐시 문제가 제거됩니다. 대규모 무효화. Cron Trigger를 통해 예약된 작업자로 구현됩니다.

// wrangler.toml - Cron Trigger per cache warming
// [triggers]
// crons = ["*/15 * * * *"]  # Ogni 15 minuti

// worker.ts - Cache Warming Worker

const URLS_TO_WARM = [
  'https://api.example.com/products?featured=true',
  'https://api.example.com/categories',
  'https://api.example.com/homepage',
  'https://api.example.com/navigation',
];

export default {
  // Scheduled handler per Cron Trigger
  async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise<void> {
    console.log(`Cache warming started at ${new Date(event.scheduledTime).toISOString()}`);

    const results = await Promise.allSettled(
      URLS_TO_WARM.map(url => warmUrl(url))
    );

    const succeeded = results.filter(r => r.status === 'fulfilled').length;
    const failed = results.filter(r => r.status === 'rejected').length;

    console.log(`Cache warming complete: ${succeeded} success, ${failed} failed`);
  },

  async fetch(request: Request): Promise<Response> {
    return new Response('Cache Warmer Worker', { status: 200 });
  },
};

async function warmUrl(url: string): Promise<void> {
  // Forza bypass della cache aggiungendo header speciale
  // (da gestire lato Worker principale con whitelist IP o secret)
  const response = await fetch(url, {
    headers: {
      'Cache-Control': 'no-cache', // Forza revalidazione
      'X-Cache-Warm': 'true',
    },
  });

  if (!response.ok) {
    throw new Error(`Failed to warm ${url}: ${response.status}`);
  }
}

interface Env {}
interface ScheduledEvent {
  scheduledTime: number;
  cron: string;
}

캐시 디버깅 및 모니터링

Cloudflare는 캐시 상태를 이해하기 위해 응답으로 진단 헤더를 공개합니다. 가장 중요한 것은 CF-Cache-Status:

CF-캐시-상태 의미
HIT Cloudflare 캐시에서 제공
MISS 캐시되지 않음, 원본에서 요청됨
EXPIRED 캐시되었지만 TTL이 만료되었습니다. 원본에 요청하세요.
STALE 오래된 것 제공(재검증하는 동안 오래된 것)
BYPASS 캐시 우회(쿠키, 인증 헤더 등)
DYNAMIC 캐시 불가능(동적 응답)
REVALIDATED 원본으로 검증된 캐시(304 Not Modified)
// Worker che logga le metriche di cache su KV Analytics
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const response = await fetch(request);

    const cacheStatus = response.headers.get('CF-Cache-Status') ?? 'UNKNOWN';

    // Logga la metrica in background
    ctx.waitUntil(
      logCacheMetric(env, {
        url: request.url,
        status: cacheStatus,
        timestamp: Date.now(),
        country: request.cf?.country ?? 'unknown',
        datacenter: request.cf?.colo ?? 'unknown',
      })
    );

    return response;
  },
};

async function logCacheMetric(env: Env, metric: CacheMetric): Promise<void> {
  // Aggrega per finestre di 1 minuto
  const minuteKey = `metrics:${Math.floor(metric.timestamp / 60000)}:${metric.status}`;
  const current = parseInt(await env.METRICS_KV.get(minuteKey) ?? '0');
  await env.METRICS_KV.put(minuteKey, String(current + 1), { expirationTtl: 3600 });
}

interface CacheMetric {
  url: string;
  status: string;
  timestamp: number;
  country: string;
  datacenter: string;
}

interface Env {
  METRICS_KV: KVNamespace;
}

결론 및 다음 단계

Cloudflare Workers Cache API는 CDN을 수동 도구에서 구성 요소로 변환합니다. 건축 분야에서 활동 중입니다. 이 문서에 설명된 전략을 사용하면 다음과 같은 전략을 구축할 수 있습니다. 로드를 줄이는 세분화되고 의미론적이며 무효화 가능한 캐싱 계층 일반적인 공개 API 워크로드의 경우 오리진에서 70~90%.

기억해야 할 핵심 사항: 사용 ctx.waitUntil() 응답을 차단하지 않도록 클라이언트에 대해 사용자 정의 캐시 키를 구축하여 다양한 컨텍스트를 분리하고 캐시 태그를 사용합니다. 의미론적 무효화를 위해, 그리고 더 복잡한 패턴을 위해 캐시 API를 KV와 결합합니다.

시리즈의 다음 기사

  • 제9조: 현지 작업자 테스트 — Miniflare, Vitest e Wrangler Dev: 배포 없이 작업자에 대한 단위 테스트 및 통합 테스트를 작성하는 방법
  • 제10조: 엣지의 풀스택 아키텍처 — 사례 연구 제로에서 프로덕션으로: GitHub Actions의 Workers + D1 + R2 및 CI/CD가 포함된 완전한 REST API입니다.