Cloudflare Workers, kod uruchamiany blisko użytkownika
Cloudflare zaczynał jako sieć dostarczania treści, a stał się platformą, na której da się postawić całą aplikację: kod, baza danych, magazyn plików, kolejki i modele językowe. Kod wykonuje się w setkach lokalizacji, więc użytkownik z Warszawy nie czeka na odpowiedź z Wirginii.
Czym różni się od zwykłego hostingu
Trzy różnice decydują o tym, czy platforma pasuje do projektu.
Pierwsza to model uruchomienia. Kod nie działa w procesie czekającym na żądania, tylko startuje przy każdym wywołaniu i kończy się po odpowiedzi. Znika problem zimnego startu znany z funkcji w innych chmurach, ale znika też możliwość trzymania stanu w pamięci między żądaniami.
Druga to środowisko wykonania. Workers nie są Node, tylko środowiskiem opartym o standardy przeglądarkowe. Większość nowoczesnych bibliotek działa, natomiast pakiety sięgające do systemu plików albo do modułów natywnych nie zadziałają. Sprawdź to przed przepisaniem aplikacji, bo lista niezgodności bywa krótka, ale zawiera rzeczy, których się nie spodziewasz.
Trzecia to rozproszenie. Kod jest wszędzie, ale dane muszą gdzieś leżeć. Jeśli aplikacja odpytuje bazę w jednym regionie, zysk z uruchomienia kodu blisko użytkownika znika, bo i tak czekasz na przelot przez ocean. Dlatego platforma dorzuciła własne magazyny danych, zaprojektowane pod ten model.
Pierwszy worker
npm create cloudflare@latest moja-aplikacja
cd moja-aplikacja
npm run devKod obsługujący żądanie korzysta ze standardowych obiektów, tych samych, które znasz z przeglądarki i z tras API w Next.js.
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url)
if (url.pathname === '/zdrowie') {
return new Response('ok')
}
const wynik = await env.DB.prepare('SELECT * FROM zamowienia WHERE id = ?')
.bind(url.searchParams.get('id'))
.first()
return Response.json(wynik ?? { blad: 'nie znaleziono' }, {
status: wynik ? 200 : 404,
})
},
}Obiekt env zawiera powiązania do zasobów: baz, magazynów, kolejek i sekretów. Konfiguruje się je w pliku ustawień projektu, a nie przez zmienne środowiskowe z adresami, co odróżnia ten model od typowego backendu.
{
"name": "moja-aplikacja",
"main": "src/index.ts",
"compatibility_date": "2026-08-01",
"d1_databases": [
{ "binding": "DB", "database_name": "sklep", "database_id": "..." }
],
"kv_namespaces": [
{ "binding": "USTAWIENIA", "id": "..." }
],
"r2_buckets": [
{ "binding": "PLIKI", "bucket_name": "zalaczniki" }
],
"queues": {
"producers": [{ "binding": "KOLEJKA", "queue": "zadania" }],
"consumers": [{ "queue": "zadania", "max_batch_size": 10 }]
}
}Nazwa powiązania jest jedyną rzeczą, którą widzi kod, więc podmiana bazy pod tą samą nazwą nie wymaga zmiany ani jednej linii. Data zgodności w trzeciej linii jest tu istotniejsza, niż wygląda: zamraża zachowanie środowiska uruchomieniowego, więc aktualizacja platformy nie zmieni działania Twojego kodu bez Twojej decyzji.
Wdrożenie to jedno polecenie, a aplikacja pojawia się we wszystkich lokalizacjach jednocześnie. Nie ma wyboru regionu, bo nie ma jednego miejsca, w którym kod działa.
Zasoby danych i po co każdy z nich
Platforma ma cztery magazyny i mylenie ich zastosowań to najczęstszy błąd projektowy.
D1 to relacyjna baza oparta o SQLite. Nadaje się do danych aplikacyjnych o umiarkowanym wolumenie, tam gdzie potrzebujesz zapytań z warunkami i połączeń między tabelami.
KV to magazyn klucz wartość zoptymalizowany pod odczyt. Zapis rozchodzi się po sieci z opóźnieniem, więc nadaje się do konfiguracji, flag funkcji i danych rzadko zmienianych, a nie do stanu wymagającego natychmiastowej spójności.
R2 to magazyn plików zgodny z interfejsem S3. Jego główna zaleta to brak opłat za transfer wychodzący, co przy serwowaniu obrazów albo plików do pobrania robi w rachunku różnicę większą niż sama cena miejsca.
Durable Objects to pojedyncze obiekty z własnym stanem i gwarancją, że w danym momencie istnieje tylko jedna instancja danego obiektu. To narzędzie do rzeczy wymagających koordynacji: liczników, pokoi czatu, blokad, kolejek zdarzeń dla jednego użytkownika.
Kolejki uzupełniają ten zestaw tam, gdzie zadanie nie musi zakończyć się w trakcie żądania. Worker przyjmuje zlecenie, odkłada je do kolejki i odpowiada użytkownikowi, a osobny proces przetwarza je we własnym tempie.
export default {
async fetch(request: Request, env: Env) {
const { email } = await request.json<{ email: string }>()
await env.KOLEJKA.send({ typ: 'powitanie', email })
return new Response(null, { status: 202 })
},
async queue(batch: MessageBatch<Zadanie>, env: Env) {
for (const wiadomosc of batch.messages) {
try {
await wyslijPowitanie(wiadomosc.body.email, env)
wiadomosc.ack()
} catch {
wiadomosc.retry()
}
}
}
}Potwierdzanie pojedynczych wiadomości zamiast całej paczki jest tu tym, co odróżnia działającą kolejkę od takiej, która przy jednym błędzie ponawia dziewięć poprawnie przetworzonych zadań. To standardowy sposób obejścia limitu czasu procesora przy operacjach, które z natury trwają dłużej.
Do tego dochodzi Workers AI, czyli modele uruchamiane na infrastrukturze dostawcy, rozliczane w jednostkach obliczeniowych, z darmowym progiem dziesięciu tysięcy jednostek dziennie.
Ile to realnie kosztuje
| Element | Plan darmowy | Plan płatny |
|---|---|---|
| Workers | 100 000 żądań dziennie, 10 ms procesora na wywołanie | od 5 USD miesięcznie za konto; w cenie 10 mln żądań i 30 mln milisekund procesora, potem 0,30 USD za milion żądań |
| KV | ograniczony próg dzienny | 10 mln odczytów w cenie, potem 0,50 USD za milion, zapisy 5 USD za milion |
| D1 | próg dzienny | 0,001 USD za milion odczytów, 1 USD za milion zapisów |
| R2 | próg miesięczny | 0,015 USD za GB miesięcznie, bez opłat za transfer wychodzący |
| Durable Objects | brak w planie darmowym | 0,15 USD za milion żądań plus czas działania |
| Workers AI | 10 000 jednostek dziennie | 0,011 USD za tysiąc jednostek |
Kwota pięciu dolarów w nagłówku jest opłatą minimalną, a nie realnym rachunkiem. Aplikacja produkcyjna korzystająca z workerów, magazynu klucz wartość, bazy i plików kosztuje zwykle od kilkunastu do pięćdziesięciu dolarów miesięcznie, czyli kilkukrotnie więcej niż próg wejścia, choć nadal niewiele wobec typowego serwera.
Dwie pozycje potrafią zaskoczyć. Zapisy w magazynie klucz wartość są dziesięciokrotnie droższe od odczytów, więc używanie go jako bazy zapisu kończy się rachunkiem, którego nikt nie planował. Durable Objects rozliczają się także za czas działania, więc obiekt trzymany aktywnie przez całą dobę kosztuje więcej, niż wynika z liczby żądań.
Warto też wiedzieć, że opłata minimalna obejmuje kilka usług naraz, więc dołożenie kolejnego zasobu do istniejącego konta nie zawsze podnosi rachunek. Dopóki mieścisz się w progach zawartych w planie, płacisz te same pięć dolarów niezależnie od tego, czy używasz jednego workera, czy pięciu wraz z bazą i kolejką.
Limit czasu procesora na wywołanie w planie darmowym wynosi dziesięć milisekund i to on, a nie liczba żądań, najczęściej wymusza przejście na plan płatny. Dziesięć milisekund wystarcza na obsługę żądania z jednym zapytaniem do bazy, ale nie na przetwarzanie obrazu czy generowanie dokumentu.
W planie płatnym limit domyślny wynosi trzydzieści sekund i można go podnieść do pięciu minut, przy czym warto rozumieć, co ten limit mierzy.
{
"limits": { "cpu_ms": 60000 }
}Liczy się wyłącznie czas pracy procesora, a nie czas oczekiwania na odpowiedź z bazy czy z zewnętrznej usługi. Worker czekający dwie sekundy na cudze API zużywa ułamek milisekundy limitu, więc podnoszenie tej wartości ma sens przy przetwarzaniu danych, a nie przy wolnych zależnościach sieciowych.
Warstwa sieciowa, od której to wszystko się zaczęło
Zanim platforma stała się miejscem uruchamiania kodu, była siecią przed Twoim serwerem, i ta warstwa nadal odpowiada za większość realnej wartości dla typowej strony.
Sieć dostarczania treści przechowuje pliki statyczne blisko użytkownika. Domyślna konfiguracja obejmuje obrazy, arkusze stylów i skrypty, natomiast odpowiedzi generowane dynamicznie wymagają świadomego ustawienia reguł. Dobrze ustawiona pamięć podręczna potrafi zdjąć z serwera źródłowego dziewięćdziesiąt procent ruchu, a przy stronie treściowej ta różnica jest widoczna zarówno w rachunku, jak i w czasie ładowania.
Ochrona przed atakami wolumetrycznymi działa bez konfiguracji i to ona najczęściej jest powodem, dla którego małe serwisy w ogóle tu trafiają. Zapora aplikacyjna wymaga już decyzji, bo reguły blokujące zbyt agresywnie potrafią odciąć prawdziwych użytkowników, a zbyt luźne nie chronią przed niczym.
Osobną wartością jest zarządzanie domenami i certyfikatami. Certyfikat odnawia się sam, a przekierowania i reguły przepisywania adresów ustawia się w jednym miejscu, bez dotykania konfiguracji serwera.
Przy migracji istniejącego serwisu warto zacząć właśnie od tej warstwy, bez ruszania kodu. Przełączenie serwerów nazw i włączenie pamięci podręcznej to zmiana odwracalna, która pokazuje efekt w ciągu godziny, a decyzję o przepisywaniu aplikacji można podjąć później, gdy widać już realne dane o ruchu.
Cloudflare kontra alternatywy
| Platforma | Mocna strona | Słabość | Kiedy wybrać |
|---|---|---|---|
| Cloudflare | Kod blisko użytkownika, brak opłat za transfer z R2, komplet zasobów | Środowisko nie jest Node, część bibliotek nie zadziała | Aplikacja globalna, dużo transferu, wrażliwość na koszt |
| Vercel | Najlepsze wsparcie dla Next.js, wygoda wdrożeń | Koszt transferu i funkcji przy dużym ruchu | Projekt oparty o Next.js, priorytetem tempo pracy |
| Netlify | Prosty model wdrożeń, dobra obsługa stron statycznych | Mniej rozbudowana warstwa danych | Strony i aplikacje o umiarkowanej złożoności |
| Własny serwer | Pełna kontrola, dowolne biblioteki | Utrzymanie, skalowanie i dostępność po Twojej stronie | Zależności natywne, wymagania co do lokalizacji danych |
Te platformy da się też łączyć. Częsty układ to aplikacja na Vercelu z plikami serwowanymi z R2, gdzie brak opłat za transfer wychodzący daje największą oszczędność bez przepisywania czegokolwiek.
Wybór sprowadza się zwykle do dwóch pytań. Czy Twoje zależności działają w środowisku innym niż Node, oraz czy transfer wychodzący jest istotną pozycją w rachunku. Odpowiedź twierdząca na drugie pytanie sama w sobie bywa wystarczającym argumentem, bo różnica w kosztach przy serwowaniu plików liczy się w setkach dolarów miesięcznie.
Jak zaprojektować aplikację pod ten model
Przeniesienie istniejącego backendu bez zmian rzadko wypada dobrze. Kilka decyzji architektonicznych decyduje o tym, czy platforma da przewagę, czy tylko utrudni pracę.
Rozdziel odczyty od zapisów. Odczyty warto obsługiwać jak najbliżej użytkownika, z pamięci podręcznej albo z magazynu zoptymalizowanego pod odczyt. Zapisy mogą iść do jednego miejsca, bo są rzadsze i zwykle nikt nie zauważy dodatkowych stu milisekund przy zapisaniu formularza.
Trzymaj stan tam, gdzie należy. Sesja użytkownika w podpisanym ciasteczku nie wymaga zapytania do bazy przy każdym żądaniu. Licznik wymagający dokładności to zadanie dla obiektu z gwarancją pojedynczej instancji, a nie dla magazynu klucz wartość.
Projektuj pod krótkie wywołania. Limit czasu procesora wymusza rozbicie długich operacji na etapy, a do zadań w tle służą kolejki. Generowanie raportu w odpowiedzi na żądanie użytkownika nie mieści się w tym modelu, natomiast przyjęcie zlecenia i wykonanie go asynchronicznie mieści się bez problemu.
Unikaj bibliotek, które robią więcej, niż potrzebujesz. Duża zależność podnosi rozmiar paczki, a ten wpływa na czas startu przy każdym wywołaniu. Przy prostym API różnica między dwoma a dwustoma kilobajtami kodu bywa zauważalna w statystykach opóźnień.
Ostatnia rzecz dotyczy testowania. Środowisko lokalne odtwarza platformę dość wiernie, ale zachowanie pamięci podręcznej i propagacji zapisów zobaczysz dopiero po wdrożeniu. Warto mieć osobne środowisko na własnej subdomenie, żeby te różnice wychodziły przed produkcją.
Warto też z góry zdecydować, jak mocno chcesz się z tą platformą związać. Kod samego workera opiera się o standardowe obiekty żądania i odpowiedzi, więc przenosi się względnie łatwo, natomiast magazyny danych są jej wyłączną częścią i poza nią nie istnieją. Przy bazie relacyjnej ten węzeł da się rozluźnić zgodną usługą zewnętrzną, na przykład Turso, gdzie ta sama składnia SQLite działa jako osobna usługa z dostępem po HTTP, więc worker rozmawia z nią tak samo jak z dowolnym innym API. Zyskujesz niezależność od dostawcy, tracisz to, że baza przestaje leżeć wewnątrz tego samego środowiska, a każdy odczyt staje się wywołaniem sieciowym.
Typowe błędy
Pierwszy to traktowanie magazynu klucz wartość jak bazy danych. Odczyt jest tani i szybki, ale zapis kosztuje dziesięć razy więcej i rozchodzi się z opóźnieniem, więc licznik odwiedzin zapisywany tam przy każdym żądaniu to prosta droga do zaskakującego rachunku.
Drugi to zakładanie stanu w pamięci między żądaniami. Worker może zostać uruchomiony w innej lokalizacji przy kolejnym wywołaniu, więc zmienna globalna nie jest miejscem na cokolwiek trwałego.
Trzeci to przepisywanie aplikacji przed sprawdzeniem zależności. Jedna biblioteka sięgająca do modułu natywnego potrafi zablokować całą migrację, a dowiesz się o tym po dwóch dniach pracy.
Czwarty to pomijanie limitu czasu procesora. Kod działający lokalnie w pięćdziesiąt milisekund przekroczy limit planu darmowego, a komunikat błędu nie zawsze wskazuje wprost na przyczynę.
Piąty to trzymanie sekretów w pliku konfiguracyjnym zamiast w magazynie sekretów. Plik ustawień trafia do repozytorium, a klucze powinny być wgrywane osobnym poleceniem.
FAQ
Czy plan darmowy wystarczy do produkcji?
Do niewielkiego projektu tak, bo sto tysięcy żądań dziennie to sporo. Ograniczeniem jest zwykle nie liczba żądań, tylko dziesięć milisekund czasu procesora na wywołanie i brak Durable Objects. Aplikacja robiąca cokolwiek poza prostym odczytem z bazy zwykle wymaga planu płatnego.
Ile kosztuje typowa aplikacja produkcyjna?
Zwykle od kilkunastu do pięćdziesięciu dolarów miesięcznie przy korzystaniu z workerów, bazy, magazynu klucz wartość i plików. Opłata minimalna wynosi pięć dolarów, ale realny rachunek jest wielokrotnością tej kwoty, przy czym nadal pozostaje niski wobec typowego serwera z porównywalną dostępnością.
Czy Next.js działa na tej platformie?
Tak, przez dedykowany adapter, przy czym część funkcji frameworka zachowuje się inaczej niż w środowisku Node. Przy prostszych aplikacjach różnice są niezauważalne, przy rozbudowanych warto sprawdzić zgodność, zanim zdecydujesz o przenosinach z Vercela.
Czym D1 różni się od zwykłej bazy?
D1 opiera się na SQLite i jest zaprojektowane pod ten model uruchomienia, więc nadaje się do danych aplikacyjnych o umiarkowanym wolumenie. Przy dużych zbiorach i skomplikowanych zapytaniach lepiej sprawdzi się Postgres, na przykład z Neona, podpięty przez warstwę łączenia połączeń.
Czy da się uruchomić model językowy na tej platformie?
Tak, przez Workers AI, gdzie modele działają na infrastrukturze dostawcy i rozliczają się w jednostkach obliczeniowych, z darmowym progiem dziesięciu tysięcy dziennie. Do zadań wymagających najmocniejszych modeli nadal wywołasz zewnętrzne API, na przykład Claude.
Dokumentacja stoi na developers.cloudflare.com, a cennik w sekcji o rozliczeniach.