Architektura

WordPress + Cloudflare Workers — pre-rendering, edge cache i A/B testy bez wtyczek

Jak wykorzystać Cloudflare Workers do prerenderingu dla botów, HTML cache na edge'u i A/B testów bez wtyczek WordPress. Kod i realne konfiguracje z produkcji.

DDawid Penkala
12 min czytania
Infrastruktura Cloudflare — edge computing

Cloudflare Workers to serverless JavaScript działający na 300+ edge’owych lokalizacjach Cloudflare. Dla WordPressa to game-changer — można rozwiązywać problemy, które w PHP wymagałyby ciężkich wtyczek albo custom infrastruktury.

W Devance używamy Workers dla trzech celów: prerendering dla botów AI i search, edge cache HTML dla ruchu anonimowego, A/B testy bez wtyczek. Poniżej — kod i konfiguracja z produkcji devance.agency + klientów.

Koszt i ograniczenia

  • Free tier: 100 000 requestów/dzień, 10 ms CPU na jedno żądanie. Dla małych sklepów wystarczy.
  • Paid ($5/mies.): 10 mln req/mies., 50 ms CPU na jedno żądanie, KV storage, Durable Objects.
  • Workers dla domeny wymaga DNS przez Cloudflare + “Orange Cloud” (ikona pomarańczowej chmury — tryb pełnego proxy w Cloudflare) (tryb pełnego proxy).

Ograniczenia: brak direct filesystem, brak traditional DB — tylko KV, D1, R2. Ale WP jest już gdzieś (oryginał hosting), Workers to tylko proxy / middleware.

Problem: nowoczesne WP strony używają React islands (Astro, Gatsby, Next.js static). Boty (Googlebot, GPTBot, Perplexity) teoretycznie widzą SSR HTML, ale praktyka bywa inna — JavaScript execution bywa niepełne, LCP obraz może nie być zaindeksowany.

Rozwiązanie: Worker detekuje bota po User-Agent, serwuje HTML z wyciętym(zachowując JSON-LD dla schema.org). Bot dostaje czyste, parsable HTML bez efekt ubocznys.

Kod (uproszczona wersja naszego workera na devance.agency):

const BOT_AGENTS = [
  'googlebot', 'bingbot', 'gptbot', 'chatgpt-user',
  'claudebot', 'perplexitybot', 'google-extended',
  // ... 79 patternów
];

function detectBot(userAgent) {
  const lower = (userAgent || '').toLowerCase();
  return BOT_AGENTS.find(bot => lower.includes(bot)) || null;
}

function stripScripts(html) {
  const SCRIPT_RE = /<script\b([^>]*)>[^<]*(?:<(?!\/script>)[^<]*)*<\/script>/gi;
  return html.replace(SCRIPT_RE, (match, attrs) => {
    if (/type\s*=\s*["']?application\/ld\+json/i.test(attrs)) return match;
    if (/googletagmanager\.com/i.test(match)) return match;
    return '';
  });
}

export default {
  async fetch(request, env, ctx) {
    const userAgent = request.headers.get('User-Agent') || '';
    const bot = detectBot(userAgent);

    if (!bot) {
      return fetch(request); // passthrough dla ludzi
    }

    const cache = caches.default;
    const cacheKey = new Request(request.url + '?__bot=1');
    let cached = await cache.match(cacheKey);
    if (cached) return cached;

    const originResponse = await fetch(request);
    if (!originResponse.headers.get('Content-Type')?.includes('text/html')) {
      return originResponse;
    }

    const html = await originResponse.text();
    const stripped = stripScripts(html);

    const response = new Response(stripped, {
      headers: {
        ...Object.fromEntries(originResponse.headers),
        'X-Prerendered-Bot': bot,
        'Cache-Control': 'public, max-age=3600',
      }
    });

    ctx.waitUntil(cache.put(cacheKey, response.clone()));
    return response;
  }
};

Wdrożenie:

wrangler deploy --config wrangler-prerender.toml

Efekt u nas: boty Search i AI crawlery odbiorą ~5x szybszą odpowiedź (edge cache) z czystym HTML. Perplexity i ChatGPT od miesięcy cytują nasz llms.txt.

Zastosowanie 2: Edge cache dla ludzi z bypass dla dynamiki

Tradycyjny WordPress bez edge cache: user w Warszawie → request do serwera w Amsterdamie → PHP generuje → 200-500 ms TTFB.

Worker cache’uje HTML na edge, bypassując dynamiczne ścieżki:

const HUMAN_CACHE_TTL = 600; // 10 minut

const BYPASS_PATHS = ['/konto', '/zamowienie', '/sukces', '/anulowano'];

function shouldBypass(pathname, request) {
  if (BYPASS_PATHS.some(p => pathname.startsWith(p))) return true;

  const cookie = request.headers.get('Cookie') || '';
  if (/(auth|session|wp-settings|stripe_mid)=/i.test(cookie)) return true;

  if (request.url.includes('?token=') || request.url.includes('?session_id=')) return true;

  return false;
}

export default {
  async fetch(request, env, ctx) {
    if (request.method !== 'GET' || shouldBypass(new URL(request.url).pathname, request)) {
      return fetch(request);
    }

    const cache = caches.default;
    const cacheKey = new Request(request.url + '?__h=1');
    const cached = await cache.match(cacheKey);

    if (cached) {
      const headers = new Headers(cached.headers);
      headers.set('X-Cache', 'HIT');
      return new Response(cached.body, { status: cached.status, headers });
    }

    const originResponse = await fetch(request);
    if (originResponse.headers.get('Set-Cookie')) return originResponse;

    const html = await originResponse.text();
    const response = new Response(html, {
      status: originResponse.status,
      headers: {
        ...Object.fromEntries(originResponse.headers),
        'Cache-Control': `public, max-age=60, s-maxage=${HUMAN_CACHE_TTL}`,
        'X-Cache': 'MISS',
      }
    });

    ctx.waitUntil(cache.put(cacheKey, response.clone()));
    return response;
  }
};

Efekt: użytkownik w Warszawie — TTFB 30-50 ms z edge cache HIT. Porównanie: Cloudflare Pro APO kosztuje 5 $/mies., custom Worker kosztuje ~2 $/mies. przy typowym ruchu WP.

Invalidation: po publikacji posta w WP, wtyczka hook na save_post wysyła DELETE na https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache z listą URL-i do purge.

Zastosowanie 3: A/B testy bez wtyczek

Klient chce testować dwa warianty strony głównej — bez Google Optimize (wycofany w 2023) i bez płatnego Optimizely ($5k+/mies.). Worker robi to w 50 linijkach:

const EXPERIMENT_KEY = 'devance-hero-v2';
const VARIANT_COOKIE = 'exp_variant';

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);

    // Only A/B test homepage
    if (url.pathname !== '/') return fetch(request);

    // Determine variant — sticky per user via cookie
    const cookie = request.headers.get('Cookie') || '';
    const match = cookie.match(/exp_variant=(A|B)/);
    let variant = match ? match[1] : (Math.random() < 0.5 ? 'A' : 'B');

    // Rewrite request based on variant
    let fetchUrl = request.url;
    if (variant === 'B') {
      fetchUrl = request.url.replace('/', '/variant-b/');
    }

    const originResponse = await fetch(fetchUrl, request);
    const response = new Response(originResponse.body, originResponse);

    // Set cookie for sticky variant (90 days)
    if (!match) {
      response.headers.append('Set-Cookie',
        `${VARIANT_COOKIE}=${variant}; Path=/; Max-Age=7776000; SameSite=Lax`);
    }

    // Expose variant to analytics via header
    response.headers.set('X-Experiment-Variant', variant);

    return response;
  }
};

Na stronie klienckiej masz:

  • / (wariant A, default)
  • /variant-b/ (wariant B, noindex)

W GTM / GA4 slowujesz event z X-Experiment-Variant header. Statystyki (conversion rate A vs B) liczysz w GA4.

Dla projektu z dłuższą listą experiments — można użyć Cloudflare KV do trzymania aktywnych experimentów i konfiguracji, żeby dodawać nowe bez redeploy Worker.

Zastosowanie 4 (bonus): Geo-routing

Klient chce serwować polską wersję strony polskim userom, angielską reszcie. W WP można to zrobić WPML, ale WPML jest ciężki. Worker rozwiązuje w 10 linijkach:

const country = request.cf.country; // automatic geo-IP

if (country === 'PL') {
  url.pathname = '/pl' + url.pathname;
} else {
  url.pathname = '/en' + url.pathname;
}

return fetch(new Request(url.toString(), request));

request.cf.country jest darmowe w Cloudflare (wymaga “Orange Cloud” (ikona pomarańczowej chmury — tryb pełnego proxy w Cloudflare)). Decyzja przed origin = bez kosztu dla WP.

Gotchas z produkcji

Cache bypass na podstawie cookies zadziała różnie w zależności od tego, jakie wtyczki ustawiają cookies. WooCommerce ustawia woocommerce_items_in_cart za każdym odwiedzeniem z pustym koszykiem = cache miss. Fix: dodać ten cookie do whitelist (cacheable).

Jeśli origin wysyła Set-Cookie (np. login response), NIE cache’uj. Sprawdzamy to w kodzie wyżej. Pominięcie = każdy user dostaje cookie poprzedniego (disaster).

3. WordPress WP_CACHE constant

Jeśli używasz też WP Rocket, define('WP_CACHE', true); może wysłać Cache-Control i nagłówek „Vary” które wpłyną na Worker cache keys. Sprawdź Chrome DevTools → Network → Response Headers.

4. HTMLRewriter ma ograniczenia

Cloudflare Workers ma API HTMLRewriter do strumieniowej modyfikacji HTML (bez parsowania całości do stringa). Świetne do wstrzykiwania zawartości (np. testy A/B), ale nie obsługuje złożonych zapytań DOM. Do prerenderingu wystarczy zwykłe podstawienie wyrażeniem regularnym.

5. Diagnostyka

console.log w Workerze trafia do Cloudflare Dashboard → Workers → Logs (na żywo). Świetne do diagnozy na produkcji. Nie loguj wrażliwych danych — request.headers może zawierać tokeny uwierzytelniające.

Koszty real-life

Dla devance.agency:

  • 1 worker (prerender + edge cache + A/B test w jednym) — free tier wystarczy (< 100k req/dzień).
  • R2 dla cache persistence long-term — 0.015 $/GB, <1 $ mies.
  • Logging do Cloudflare Analytics — free.

Dla klienta sklep 50 000 req/dzień:

  • Workers: ~$5/mies (paid tier).
  • KV dla experiment configs: ~$0.50/mies.
  • Razem: ~$6/mies za zaawansowaną infrastruturę edge.

Dla porównania: Cloudflare APO to 5 $/mies. ale zamknięty system, bez custom logic. Worker = ta sama cena, ale pełna kontrola.


Jeśli prowadzisz sklep WordPress z wymaganiami wydajnościowymi (Core Web Vitals, globalny ruch, A/B testing) i chcesz zobaczyć jak Workers mogą wyglądać u Ciebie — w ramach opieki Devance setup workerów i utrzymanie wchodzą w godziny pakietu. Potrzebujesz tylko Cloudflare account i zgody na DNS cutover.

Tagi:Cloudflare WorkersWordPressedge cacheprerenderA/B testingwydajność
Dawid Penkala
Dawid Penkala

Doświadczony WordPress Developer z ponad 14-letnim stażem w tworzeniu zaawansowanych stron i sklepów internetowych. Specjalizuje się w WordPressie, dedykowanych wtyczkach i motywach.

Więcej o autorze