Serverless dla frontendowca — functions, edge i deployment bez serwera

Serverless dla frontend developera w praktyce. Functions, edge, cold starts, limity, koszty i wybór między Vercel, Netlify a Cloudflare.

Opublikowano

10 września 2025 10:25

Czytanie

6 min czytania

Aktualizacja

15 kwietnia 2026 11:52

Serverless to model uruchamiania kodu, w którym nie zarządzasz ręcznie serwerem, a płacisz zwykle za wykonania lub użycie. nie usuwa serwerów z równania. Usuwa przede wszystkim obowiązek zarządzania nimi. Dla frontend developera to często najszybszy sposób, żeby dowieźć backend, webhook, formularz albo lekkie API, czyli Application Programming Interface, definiuje sposób komunikacji między aplikacjami lub modułami. bez własnej infrastruktury.

Krótka odpowiedź: Vercel i Netlify dla Next.js/React — serverless functions z pełnym Node.js, naturalny wybór. Cloudflare Workers dla edge (niższy cold start, globalnie blisko użytkownika, ograniczony runtime). Serverless nie nadaje się dla: long-running jobs, stanowych WebSocketów, dużych zbiorów danych. Sprawdzaj limity i pricing przed wdrożeniem.

Dla frontend developera to game changer. Możesz zbudować pełny backend bez DevOps, bez Dockera, bez konfiguracji serwerów. To naturalny krok po poznaniu backendu dla frontendowca.

Czym jest serverless?

Tradycyjny backend

Code
Ty zarządzasz:
- Serwer (EC2, VPS)
- System operacyjny
- Runtime (Node.js)
- Aplikacja
- Skalowanie
- Monitoring
- Backup
- Security patches

Serverless

Code
Ty zarządzasz:
- Kod funkcji

Platforma zarządza:
- Wszystkim innym

Model działania

Code
Request → Platforma uruchamia funkcję → Response
              ↑
         Cold start (jeśli brak instancji)

Cechy:

  • Pay per use — płacisz za wykonania, nie za idle time
  • Auto-scaling — 0 do tysięcy instancji automatycznie
  • Stateless — każde wywołanie niezależne

Vercel Functions

Najpopularniejsze dla Next.js.

API Routes (Next.js)

Code
// app/api/users/route.ts (App Router)
import { NextResponse } from 'next/server'
 
export async function GET() {
  const users = await db.user.findMany()
  return NextResponse.json(users)
}
 
export async function POST(request: Request) {
  const body = await request.json()
  const user = await db.user.create({ data: body })
  return NextResponse.json(user, { status: 201 })
}
Code
// pages/api/users.ts (Pages Router)
import type { NextApiRequest, NextApiResponse } from 'next'
 
export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method === 'GET') {
    const users = await db.user.findMany()
    return res.json(users)
  }
  
  if (req.method === 'POST') {
    const user = await db.user.create({ data: req.body })
    return res.status(201).json(user)
  }
}

Standalone Functions

Code
// api/hello.ts
export default function handler(req, res) {
  res.json({ message: 'Hello from Vercel!' })
}

Deploy: vercel deploy — automatycznie wykrywa funkcje.

Konfiguracja

Code
// vercel.json
{
  "functions": {
    "api/*.ts": {
      "memory": 1024,
      "maxDuration": 10
    }
  }
}

Netlify Functions

Podobne do Vercel, oparte na AWS Lambda.

Struktura

Code
/netlify
  /functions
    hello.js
    users.js

Przykład

Code
// netlify/functions/hello.js
exports.handler = async (event, context) => {
  return {
    statusCode: 200,
    body: JSON.stringify({ message: 'Hello!' }),
    headers: {
      'Content-Type': 'application/json'
    }
  }
}

Z TypeScript

Code
// netlify/functions/users.ts
import { Handler } from '@netlify/functions'
 
const handler: Handler = async (event, context) => {
  const { httpMethod, body } = event
  
  if (httpMethod === 'GET') {
    return {
      statusCode: 200,
      body: JSON.stringify({ users: [] })
    }
  }
  
  return { statusCode: 405, body: 'Method not allowed' }
}
 
export { handler }

Cloudflare Workers

Działa na edge — bliżej użytkownika, szybszy cold start.

Podstawowy Worker

Code
// src/index.js
export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url)
    
    if (url.pathname === '/api/hello') {
      return new Response(JSON.stringify({ message: 'Hello!' }), {
        headers: { 'Content-Type': 'application/json' }
      })
    }
    
    return new Response('Not found', { status: 404 })
  }
}

Hono (framework dla Workers)

Code
import { Hono } from 'hono'
 
const app = new Hono()
 
app.get('/api/users', async (c) => {
  const users = await c.env.DB.prepare('SELECT * FROM users').all()
  return c.json(users.results)
})
 
app.post('/api/users', async (c) => {
  const body = await c.req.json()
  await c.env.DB.prepare('INSERT INTO users (name) VALUES (?)').bind(body.name).run()
  return c.json({ success: true }, 201)
})
 
export default app

Cloudflare D1 (baza danych)

Code
// Wbudowana SQLite na edge
const result = await env.DB.prepare(
  'SELECT * FROM users WHERE id = ?'
).bind(userId).first()

Cloudflare KV (key-value store)

Code
// Zapisz
await env.KV.put('user:123', JSON.stringify(userData))
 
// Odczytaj
const user = await env.KV.get('user:123', 'json')

Edge vs Serverless Functions

Serverless Functions (Vercel, Netlify, AWS Lambda)

Code
Lokalizacja: Jeden lub kilka regionów
Cold start: zależny od platformy, bundla i runtime
Runtime: Node.js (pełny)
Limity: Więcej pamięci, dłuższy czas
Use case: Cięższe operacje, bazy danych

Edge Functions (Cloudflare, Vercel Edge)

Code
Lokalizacja: Globalna sieć edge
Cold start: zwykle niższy niż klasyczny serverless, ale nadal zależny od platformy
Runtime: lekkie środowisko edge / isolates
Limity: Mniej pamięci, krótszy czas CPU
Use case: Personalizacja, auth, routing

Kiedy który?

ScenariuszServerlessEdge
API CRUD to skrót od Create, Read, Update, Delete, czyli podstawowych operacji wykonywanych na danych.⚠️
Auth / sessions
Personalizacja⚠️
A/B testing⚠️
Heavy computation
Database queries⚠️
Image processing

Najważniejsza praktyczna zasada: edge nie jest "lepszym serverless". To po prostu inny kompromis. Jeśli logika jest lekka i ważna jest lokalizacja blisko użytkownika, edge ma sens. Jeśli potrzebujesz cięższych bibliotek, dłuższego czasu wykonania, stabilnych integracji z bazą i bardziej przewidywalnego runtime, zwykłe functions bywają lepszym wyborem.

Vercel Edge Functions

Code
// app/api/geo/route.ts
import { NextResponse } from 'next/server'
 
export const runtime = 'edge'  // ← Kluczowe!
 
export async function GET(request: Request) {
  const country = request.headers.get('x-vercel-ip-country')
  const city = request.headers.get('x-vercel-ip-city')
  
  return NextResponse.json({
    country,
    city,
    message: `Hello from ${city}, ${country}!`
  })
}

Praktyczny przykład: API z bazą danych

Vercel + Prisma + PostgreSQL — pełny tutorial w artykule Prisma + Next.js fullstack

Code
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'
 
const globalForPrisma = global as unknown as { prisma: PrismaClient }
 
export const prisma = globalForPrisma.prisma || new PrismaClient()
 
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
Code
// app/api/posts/route.ts
import { prisma } from '@/lib/prisma'
import { NextResponse } from 'next/server'
 
export async function GET() {
  const posts = await prisma.post.findMany({
    include: { author: true }
  })
  return NextResponse.json(posts)
}
 
export async function POST(request: Request) {
  const body = await request.json()
  
  const post = await prisma.post.create({
    data: {
      title: body.title,
      content: body.content,
      authorId: body.authorId
    }
  })
  
  return NextResponse.json(post, { status: 201 })
}

Cloudflare + D1

Code
// src/index.ts
import { Hono } from 'hono'
 
type Bindings = {
  DB: D1Database
}
 
const app = new Hono<{ Bindings: Bindings }>()
 
app.get('/api/posts', async (c) => {
  const { results } = await c.env.DB.prepare(`
    SELECT posts.*, users.name as author_name
    FROM posts
    JOIN users ON posts.author_id = users.id
  `).all()
  
  return c.json(results)
})
 
app.post('/api/posts', async (c) => {
  const { title, content, authorId } = await c.req.json()
  
  await c.env.DB.prepare(`
    INSERT INTO posts (title, content, author_id)
    VALUES (?, ?, ?)
  `).bind(title, content, authorId).run()
  
  return c.json({ success: true }, 201)
})
 
export default app

Cold starts — problem i rozwiązania

Co to cold start?

Gdy funkcja nie była używana, platforma musi:

  1. Uruchomić kontener/isolate
  2. Załadować kod
  3. Zainicjalizować runtime

To zwykle trwa od ułamków sekundy do kilkuset milisekund, zależnie od platformy, regionu i wielkości bundla.

Jak minimalizować?

1. Mniejszy Bundle to paczka JavaScriptu lub innych zasobów wysyłana do przeglądarki jako część aplikacji.:

Code
// ❌ Import całego lodash
import _ from 'lodash'
 
// ✅ Import tylko potrzebnej funkcji
import debounce from 'lodash/debounce'

2. Lazy loading oznacza ładowanie zasobów dopiero wtedy, gdy są potrzebne, na przykład po przewinięciu strony.:

Code
// ❌ Import na górze
import { heavyLibrary } from 'heavy-library'
 
// ✅ Import gdy potrzebne
export async function handler() {
  const { heavyLibrary } = await import('heavy-library')
}

3. Connection pooling (bazy danych):

Code
// Użyj connection pooler jak PgBouncer lub Prisma Accelerate

4. Keep warm (Vercel/Netlify):

Code
// Cron job pingujący funkcję co kilka minut

Limity i pricing

Limity i cenniki zmieniają się regularnie, więc nie warto uczyć się ich na pamięć z pojedynczego artykułu. Ważniejsze jest zrozumienie, co zwykle jest limitowane:

  • czas wykonania pojedynczego requestu,
  • pamięć i CPU,
  • liczba requestów albo GB-hours,
  • bandwidth i egress,
  • limity dla cronów, buildów i środowisk preview,
  • limity platformowych baz i kolejek, jeśli używasz ich razem z functions.

Kiedy to za mało?

  • Heavy computation → użyj dedykowanego serwera
  • Long-running jobs → użyj kolejek (AWS SQS, BullMQ)
  • Real-time → użyj WebSocket umożliwia dwukierunkową, stałą komunikację między klientem a serwerem w czasie rzeczywistym. (Pusher, Ably)

Gdzie serverless nie jest najlepszym wyborem

Serverless bywa świetny, ale nie jest darmowym zamiennikiem każdego backendu.

Najczęstsze punkty bólu:

  • Long-running jobs: importy, duże raporty, transkodowanie wideo, ciężkie AI workloads
  • Stanowe połączenia: długie WebSockety, procesy trzymające pamięć między requestami
  • Silna zależność od jednego regionu i jednej bazy: edge blisko użytkownika nic nie da, jeśli każde żądanie i tak leci do dalekiej bazy
  • Koszty przy dużym ruchu lub złej architekturze: dużo małych wywołań i niekontrolowane integracje zewnętrzne potrafią zrobić rachunek większy niż VPS

Przed wyborem platformy sprawdź zawsze oficjalny pricing i quotas dla konkretnego planu. W praktyce to bywa ważniejsze niż sama składnia funkcji.

Deployment

Vercel

Code
# CLI
npm i -g vercel
vercel
 
# Lub połącz repo z GitHub — auto-deploy na push

Netlify

Code
# CLI
npm i -g netlify-cli
netlify deploy --prod
 
# Lub połącz repo z GitHub

Cloudflare Workers

Code
# Wrangler CLI
npm i -g wrangler
wrangler login
wrangler deploy

Best practices

1. Trzymaj funkcje małe

Code
// ❌ Jedna funkcja do wszystkiego
export async function handler(req) {
  if (req.path === '/users') { ... }
  if (req.path === '/posts') { ... }
  if (req.path === '/comments') { ... }
}
 
// ✅ Osobne funkcje
// /api/users/route.ts
// /api/posts/route.ts
// /api/comments/route.ts

2. Obsługuj błędy

Code
export async function GET() {
  try {
    const data = await fetchData()
    return NextResponse.json(data)
  } catch (error) {
    console.error('Error:', error)
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    )
  }
}

3. Waliduj input

Code
import { z } from 'zod'
 
const createUserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
})
 
export async function POST(request: Request) {
  const body = await request.json()
  const result = createUserSchema.safeParse(body)
  
  if (!result.success) {
    return NextResponse.json({ errors: result.error.flatten() }, { status: 400 })
  }
  
  // ... create user
}

FAQ

Czym jest serverless i jak różni się od tradycyjnego serwera?

Na tradycyjnym serwerze (VPS, EC2) zarządzasz OS, runtime, skalowaniem, patchami i monitoringiem. Serverless: piszesz tylko funkcję, platforma robi resztę — uruchamia, skaluje, zatrzymuje. Billing per wywołanie (nie za czas działania serwera). Dla frontend developera to najszybsza droga do backendu bez DevOps. Ograniczenia: limit czasu wykonania (zwykle 10-60s), cold starts, stateless.

Jaka różnica między Serverless Functions a Edge Functions?

Serverless Functions (Vercel, Netlify, AWS Lambda): zwykle działają region-first, oferują pełny Node.js runtime, więcej pamięci i czasu wykonania, a ich cold start zależy od platformy, bundla i obciążenia. Dobre dla CRUD API i operacji z bazą danych. Edge Functions (Cloudflare Workers, Vercel Edge): działają bliżej użytkownika w globalnej sieci edge, mają lżejszy runtime i zwykle krótszy czas startu, ale bardziej ograniczone API i limity wykonania. Dobre dla personalizacji, auth, A/B testów i lekkiego routingu.

Którą platformę wybrać — Vercel, Netlify czy Cloudflare?

Vercel: naturalny wybór dla Next.js (od tego samego producenta), doskonała integracja z App Router, Preview Deployments. Netlify: dobre dla statycznych stron i JAMstack, popularne Forms i Identity. Cloudflare: jeśli zależy Ci na niskim latency globalnie, chcesz edge jako default, lub pracujesz z Workers/D1/KV ekosystemem. Dla typowego projektu Next.js na starcie: Vercel. Gdy szukasz alternatywy lub potrzebujesz edge: Cloudflare.

Co to jest cold start i jak go minimalizować?

Cold start to opóźnienie przy pierwszym wywołaniu funkcji po okresie nieaktywności — platforma musi uruchomić środowisko, załadować kod i zainicjalizować runtime. W praktyce edge często startuje szybciej niż klasyczny serverless, ale dokładny czas zależy od platformy, regionu, wielkości bundla i użytych zależności. Jak minimalizować: (1) mniejszy bundle — importuj tylko potrzebne moduły; (2) lazy loading ciężkich zależności; (3) connection pooling dla baz danych (PgBouncer, Prisma Accelerate); (4) keep-warm cron (ping co kilka minut, ale nie zawsze dostępny/opłacalny).

Kiedy serverless nie jest dobrym wyborem?

Cztery główne scenariusze gdzie serverless nie sprawdza się: (1) Long-running jobs — importy CSV, transkodowanie wideo, ciężkie obliczenia AI (przekraczają limity czasu); (2) Stanowe WebSockety — serverless jest stateless, nie trzyma połączeń; (3) Bardzo duży ruch z intensywną bazą — wiele połączeń od wielu funkcji może przeciążyć bazę (connection pooler obowiązkowy); (4) Kosztowne obliczenia — opłata per GB-s może zaskoczyć przy heavy workloads.

Jak wdrożyć Next.js na Vercel?

Trzy kroki: (1) Połącz repozytorium GitHub z Vercel (vercel.com → Add New Project); (2) Vercel automatycznie wykrywa Next.js i konfiguruje build; (3) Każdy push na main to automatyczny deploy, każdy PR to Preview Deployment. Zmienne środowiskowe: Settings → Environment Variables. Dla zaawansowanych: vercel.json do konfiguracji routingu i regionów. CLI: npm i -g vercel i vercel w katalogu projektu.

Jakie są limity w darmowych planach Vercel i Netlify?

Limity zmieniają się regularnie — zawsze sprawdzaj aktualny pricing i limity w dokumentacji platformy. Najważniejsza praktyka jest prosta: nie projektuj architektury pod marketingową tabelkę z planami, tylko pod własny profil ruchu, czas wykonania funkcji, liczbę połączeń do bazy i wymagania regionowe. Dla małych projektów darmowe plany często wystarczają, ale dla produkcji i ruchu reklamowego sprawdzaj limity własnego case'u przed wdrożeniem.

Źródła i dokumentacja

Podsumowanie

PlatformaNajlepsze doEdge
VercelNext.js, React
NetlifyStatic sites, JAMstack
CloudflareGlobal, low latency✅ (native)
AWS LambdaEnterprise, complex

Serverless to najprostszy sposób na backend dla frontend developera, ale najlepsze efekty pojawiają się dopiero wtedy, gdy rozumiesz kompromisy runtime'u, limity czasu wykonania i koszt złej architektury. Vercel, Netlify i Cloudflare rozwiązują podobny problem, ale robią to w trochę inny sposób.

Zacznij od platformy, która pasuje do Twojego stacku i przepływu danych, nie od tej, która jest najmodniejsza. Warto też poznać Server Actions, które w części przypadków pozwalają jeszcze bardziej uprościć warstwę backendową.


Chcesz zbudować fullstack app? Sprawdź tutorial Prisma + Next.js lub poznaj pobieranie danych w Next.js.

Pracuję z tym zawodowo.

Jeśli chcesz przełożyć ten temat na lepszą architekturę frontendu, uporządkować React lub Next.js i podnieść jakość pracy zespołu, skontaktuj się ze mną. Pomagam zamieniać wiedzę z artykułów w praktyczne decyzje technologiczne.

O autorze

Maciej Sala

Maciej Sala — project manager i frontendowiec z doświadczeniem w marketingu internetowym. Na co dzień pracuję z Reactem, Next.js i TypeScriptem, łącząc perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat związany z branżą gier wideo jako project manager i game designer.

Absolwent historii na Uniwersytecie Jagiellońskim i studiów podyplomowych z marketingu internetowego na Akademii Górniczo-Hutniczej w Krakowie. Poza pracą trenuje na siłowni, maluje figurki i realizuje własne projekty.

Biblioteka wiedzy

Czytaj dalej

Zobacz więcej wpisów
Anthropic uderza w Figmę i Adobe — oto Claude Design

Anthropic uderza w Figmę i Adobe — oto Claude Design

Anthropic wypuścił właśnie narzędzie AI do tworzenia stron, landing page'ów i prezentacji z promptu. Oto co wiemy o Claude Design i Opus 4.7 — i co to oznacza dla developerów.

Maciej Sala

Maciej Sala

Founder Strivelab

Astro.js vs Next.js — które narzędzie wybrać w 2026 roku?

Astro.js vs Next.js — które narzędzie wybrać w 2026 roku?

Fachowe porównanie Astro.js i Next.js z perspektywy developera pracującego na co dzień w Next.js. Architektura, wydajność, SEO, DX, koszty i konkretne use case — z benchmarkami i przykładami kodu.

Maciej Sala

Maciej Sala

Founder Strivelab