Cloudflare
Pages Functions
Workers
API Routes
Serverless

Pages Functions: quando usar em vez de Workers puros

Co-localizar frontend e API no mesmo repositório com preview deployments automáticos é o caso central de Pages Functions, não uma limitação técnica da plataforma.

Tem um equívoco comum sobre Pages Functions: que são uma versão simplificada ou limitada de Workers, adequada para casos triviais e insuficiente para produção séria. Isso não é verdade. Pages Functions rodam no mesmo V8 runtime de Workers, acessam os mesmos bindings, respeitam os mesmos limites. A diferença entre as duas formas de deploy não é de capacidade — é de contexto operacional.

O que Pages Functions são, tecnicamente

Pages Functions são Workers implantados via convenção de sistema de arquivos dentro de um projeto Pages. Você cria um diretório /functions na raiz do repositório, e cada arquivo TypeScript ou JavaScript ali se torna uma rota. O arquivo /functions/api/users/[id].ts responde em /api/users/:id. O arquivo /functions/webhooks/stripe.ts responde em /webhooks/stripe.

O runtime que executa esse código é idêntico ao de um Worker autônomo: V8 isolates, cold start abaixo de 1ms, 128MB de memória por invocação (hard limit), 30 segundos de CPU por requisição no plano pago, 1.000 subrequests por invocação. Os bindings disponíveis são os mesmos: D1 para banco SQLite no edge, KV para armazenamento chave-valor, R2 para objetos, Durable Objects para estado consistente, AI para inferência, Service Bindings para chamadas diretas a outros Workers.

A distinção existe no deploy: uma Pages Function nasce como parte de um projeto Pages que também tem assets estáticos. Não é possível fazer deploy de uma Pages Function sem um projeto Pages. Isso não é uma limitação técnica — é uma escolha de produto que define quando faz sentido usar um ou outro.

Quando Pages Functions ganham

O caso mais forte para Pages Functions é co-localização: frontend e backend no mesmo repositório, com o mesmo ciclo de deploy. Um projeto Next.js, Astro ou SvelteKit com rotas de API fica naturalmente co-localizado — você altera um componente e a rota de API que ele consome no mesmo commit, o mesmo pull request, o mesmo preview deployment.

Esse preview por branch é o diferencial operacional mais concreto. Cada push para qualquer branch gera uma URL de preview com o formato hash-nome-da-branch.seuproject.pages.dev, onde tanto os assets estáticos quanto as Functions estão rodando. Isso significa que o revisor do PR pode testar a feature completa — interface e API — sem fazer deploy em nenhum ambiente separado. Workers puros não têm esse fluxo nativamente.

Se o roteamento da sua API mapeia naturalmente para paths de URL e você não precisa de lógica de roteamento complexa fora da estrutura de diretórios, Pages Functions eliminam a necessidade de um Worker separado com suas próprias rotas configuradas no wrangler.toml.

A estrutura de diretório e o padrão de middleware

Uma estrutura realista de projeto Pages com Functions:

/functions
  _middleware.ts          ← executa antes de toda Function no diretório
  /api
    _middleware.ts        ← executa antes de toda Function em /api
    users/
      [id].ts             ← GET /api/users/:id, PUT /api/users/:id
      index.ts            ← GET /api/users, POST /api/users
    webhooks/
      stripe.ts           ← POST /api/webhooks/stripe

O arquivo _middleware.ts é um mecanismo de composição que muita gente ignora. Ele recebe a requisição antes que a Function específica do path seja executada, e pode curto-circuitar com uma resposta própria ou passar para o próximo handler via ctx.next(). Isso serve para autenticação, logging centralizado e CORS sem repetir lógica em cada Function:

// /functions/api/_middleware.ts export async function onRequest(ctx: EventContext<Env, any, any>) { const token = ctx.request.headers.get("Authorization"); if (!token || !isValidToken(token, ctx.env.JWT_SECRET)) { return new Response("Unauthorized", { status: 401 }); } const response = await ctx.next(); response.headers.set("X-Content-Type-Options", "nosniff"); return response; }

O middleware no /functions raiz cobre todas as Functions. O middleware em /functions/api cobre só as rotas de API. Você pode empilhar os dois — o do diretório pai executa primeiro.

Quando Workers puros são a escolha certa

Pages Functions não suportam cron triggers. Se você precisa de um job que roda às 3h da manhã para processar cobranças, sincronizar um feed externo ou limpar sessões expiradas, isso precisa ser um Worker autônomo com [triggers] crons no wrangler.toml. Não existe alternativa dentro do modelo Pages.

Queues consumers — Workers que processam mensagens do Cloudflare Queues de forma assíncrona — também não existem em Pages. Se sua arquitetura usa filas para desacoplar processamento pesado do caminho crítico da requisição, o consumer precisa ser um Worker separado.

Workers for Platforms, o mecanismo de dispatch para scripts implantados por usuários (caso de SaaS multi-tenant onde cada cliente tem seu próprio código), é exclusivo de Workers. Email Workers, que recebem e processam e-mails recebidos, idem.

A regra prática: se o trigger não é uma requisição HTTP, é um Worker puro. Pages Functions são exclusivamente HTTP.

Compartilhando código entre Pages Functions e Workers separados

Uma arquitetura comum usa os dois: projeto Pages para o frontend e a API principal em /functions, mais Workers separados para jobs de background. O problema é que o código de negócio pode precisar ser compartilhado — validação de dados, acesso ao banco D1, lógica de autorização.

A solução são pacotes npm internos (usando workspaces do npm/pnpm) ou um Worker dedicado como "service layer" acessado via Service Binding pelos outros. O Service Binding permite que uma Pages Function chame um Worker diretamente, na mesma rede interna da Cloudflare, sem custo de rede e sem passar pela internet pública:

// /functions/api/orders/index.ts export async function onRequestPost(ctx: EventContext<Env, any, any>) { // Chama o Worker de processamento via Service Binding const result = await ctx.env.ORDER_PROCESSOR.fetch( new Request("https://internal/process", { method: "POST", body: ctx.request.body, }) ); return result; }

O ORDER_PROCESSOR aqui é um Worker autônomo que tem acesso a filas, crons, e qualquer outro primitivo que Pages Functions não suportam. Pages Functions ficam na camada HTTP; Workers autônomos ficam nos triggers assíncronos. Os dois compartilham bindings D1 e KV apontando para os mesmos recursos.

O critério de decisão

Se você está construindo um projeto com interface — qualquer coisa que gera assets estáticos no build — comece com Pages. As Functions que você adiciona em /functions têm exatamente o mesmo poder de um Worker autônomo para casos HTTP. Você ganha preview por branch e pipeline de build integrado sem pagar nenhum custo operacional adicional.

Se você está construindo um serviço sem frontend, com triggers não-HTTP, ou que exige environments nomeados com bindings distintos para staging e produção, Workers é o caminho mais direto. A configuração explícita em wrangler.toml é mais auditável e o modelo de deploy é mais flexível para serviços puros de backend.

Os dois não são mutuamente exclusivos. A maioria dos projetos sérios acaba com os dois: Pages para o que é HTTP e co-localizado com o frontend, Workers para o que é assíncrono ou agendado.

Leia também

Pages Functions: quando usar em vez de Workers puros | Matheus Breguêz