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.

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.
Zastosowanie 1: Prerendering dla botów AI i search
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.tomlEfekt 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
1. Cookie heuristics — łatwo przestrzelić
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).
2. Set-Cookie na origin response
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.

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
