Generar PDFs del lado del servidor suele implicar lidiar con cold starts de Lambda, gestionar binarios de Chromium o depender de APIs de terceros que añaden latencia y costos. Si alguna vez ha levantado un contenedor Docker de más de 1 GB solo para convertir HTML a PDF, conoce el problema.
Kits de inicio de alta calidad con flujo de autenticación integrada (Auth.js), carga de objetos (AWS, Clouflare R2, Firebase Storage, Supabase Storage), pagos integrados (Stripe, LemonSqueezy), flujo de verificación de correo electrónico (Resend, Postmark, Sendgrid) y mucho más . Compatible con cualquier base de datos (Redis, Postgres, MongoDB, SQLite, Firestore).
Get all 3 kits Bundle ↗- Next.js Starter Kit
- SvelteKit Starter Kit
- Astro Starter Kit
One-time license · Lifetime updates
Cloudflare Browser Rendering invierte este modelo. Es un servicio gestionado de Chromium headless que se ejecuta en la red edge de Cloudflare, es decir, sin binarios que empaquetar, sin cold starts y sin capas de Docker. Obtiene APIs compatibles con Puppeteer que se ejecutan de forma globalmente distribuida con tiempos de arranque inferiores a 100 ms.
En esta guía, construirá un sistema de generación de PDFs listo para producción usando Astro, Cloudflare KV y Cloudflare Workers. Creará páginas de recibos imprimibles con CSS personalizado, conectará un endpoint de API que lanza Chrome headless en el edge y almacenará en caché los resultados en Cloudflare KV para evitar renderizados redundantes.
Requisitos previos
Necesitará lo siguiente:
- Node.js 20 o posterior
- Una cuenta de Cloudflare
¿Por qué Cloudflare Browser Rendering para PDFs?
- Sin gestionar Chromium: Cloudflare ejecuta y mantiene actualizado Chromium headless por usted.
- Proximidad en el edge: Renderice PDFs cerca del solicitante; ideal para paneles, facturas e imágenes OG.
- Streaming: Envíe PDFs directamente desde el worker sin archivos temporales.
- Consciente de costos: Pague por uso sin mantener una flota de renderizadores.
Descripción general de la arquitectura
1. Página Astro (src/pages/receipt/[id].astro) → Ruta SSR que renderiza su HTML de recibo/factura + CSS de impresión
2. API de PDF (src/pages/api/pdf/[id].ts) → Crea una sesión de Browser Rendering → Carga la URL de la ruta Astro → Llama a page.pdf() y transmite el binario
3. Adaptador de Cloudflare + binding de Browser → Despliega Astro en Workers/Pages → Proporciona un binding BROWSER al workerCrear una nueva aplicación Astro
Comencemos creando un nuevo proyecto Astro. Ejecute el siguiente comando:
npm create astro@latest my-astro-kv-pdf-appcd my-astro-kv-pdf-appnpm install wrangler @cloudflare/puppeteerCuando se le solicite, elija:
Use minimal (empty) templatecuando se le pregunte cómo iniciar el nuevo proyecto.Yescuando se le pregunte si desea instalar dependencias.Yescuando se le pregunte si desea inicializar un repositorio git.
Una vez hecho esto, puede moverse al directorio del proyecto e iniciar la aplicación:
npm run devLa aplicación debería estar ejecutándose en localhost:4321.
Integrar el adaptador de Cloudflare en su proyecto Astro
Para desplegar su proyecto Astro en Cloudflare Workers y usar Cloudflare KV, necesita instalar el adaptador de Cloudflare. Ejecute el comando siguiente:
npx astro add cloudflare --yesActualice astro.config.mjs para que output sea server:
import { defineConfig } from 'astro/config';import cloudflare from '@astrojs/cloudflare';
export default defineConfig({ output: 'server', adapter: cloudflare()});Configurar Browser Rendering en wrangler.jsonc
Añada un binding BROWSER para que el worker pueda lanzar Chromium headless:
{ "main": "dist/_worker.js/index.js", "name": "my-astro-kv-pdf-app", "compatibility_date": "2025-12-06", "compatibility_flags": [ "nodejs_compat", "global_fetch_strictly_public" ], "assets": { "binding": "ASSETS", "directory": "./dist" }, "observability": { "enabled": true }, "browser": { "binding": "BROWSER" }}El bloque
browserprovisiona el servicio Browser Rendering de Cloudflare y lo expone a su worker comoenv.BROWSER.
Añadir KV para caché
Cree un namespace KV para almacenar en caché los PDFs generados:
npx wrangler kv namespace create PDF_CACHEEsto mostrará un id que añadirá a wrangler.jsonc. Actualice el archivo para incluir el binding KV:
{ "main": "dist/_worker.js/index.js", "name": "my-astro-kv-pdf-app", "compatibility_date": "2025-12-06", "compatibility_flags": [ "nodejs_compat", "global_fetch_strictly_public" ], "assets": { "binding": "ASSETS", "directory": "./dist" }, "observability": { "enabled": true }, "browser": { "binding": "BROWSER" }, "kv_namespaces": [ { "binding": "PDF_CACHE", "id": "generated-id", "remote": true } ]}Añadir tipos de TypeScript
Cree src/env.d.ts para añadir los tipos adecuados para los bindings de Cloudflare:
/// <reference types="astro/client" />
type KVNamespace = import('@cloudflare/workers-types').KVNamespace
type ENV = { PDF_CACHE: KVNamespace BROWSER: Fetcher}
type Runtime = import('@astrojs/cloudflare').Runtime<ENV>
declare namespace App { interface Locals extends Runtime {}}Esto le proporciona seguridad de tipos completa al acceder a locals.runtime.env.PDF_CACHE y locals.runtime.env.BROWSER en sus rutas.
Construir una página Astro imprimible
Cree una página de recibo con estilos aptos para impresión:
---const { id } = Astro.params;const order = { id, customer: 'Ada Lovelace', total: '$128.00', items: [ { name: 'Edge Functions', price: '$50.00' }, { name: 'PDF Rendering', price: '$78.00' } ]};---
<html lang="en"> <head> <meta charset="UTF-8" /> <title>Receipt #{order.id}</title> <style> :root { font-family: 'Inter', system-ui, -apple-system, sans-serif; } body { margin: 0; padding: 32px; color: #0f172a; } header { display: flex; align-items: center; justify-content: space-between; } table { width: 100%; border-collapse: collapse; margin-top: 24px; } th, td { padding: 12px 8px; text-align: left; border-bottom: 1px solid #e2e8f0; } .total { text-align: right; font-size: 1.125rem; font-weight: 700; } @page { margin: 24mm; } @media print { body { background: white; } a[href]::after { content: ''; } } </style> </head> <body> <header> <div> <h1>Receipt #{order.id}</h1> <p>Issued to {order.customer}</p> </div> <strong>launchfa.st</strong> </header> <table> <thead> <tr><th>Item</th><th>Price</th></tr> </thead> <tbody> {order.items.map(item => ( <tr> <td>{item.name}</td> <td>{item.price}</td> </tr> ))} </tbody> </table> <p class="total">Total: {order.total}</p> </body></html>Guárdela como src/pages/receipt/[id].astro. El CSS de impresión (@page) establece los márgenes, y las reglas @media print eliminan las decoraciones de enlaces innecesarias.
Crear la ruta de API de PDF
Así es como crear una ruta de API al estilo Worker que usa el browser rendering de Cloudflare para generar y almacenar en caché una versión PDF de su página HTML de recibo Astro.
Configuraremos un endpoint de API (src/pages/api/pdf/[id].ts) que:
- comprueba si un PDF ya está en caché en Cloudflare KV y lo devuelve de inmediato si está disponible
- lanza un navegador headless mediante la API de Cloudflare Browser Rendering si no está en caché
- navega a la página imprimible y genera un PDF
- almacena el PDF resultante en KV durante 7 días para solicitudes futuras
import type { APIRoute } from "astro";import puppeteer from "@cloudflare/puppeteer";
// Helper to create a proper PDF HTTP responseconst pdfResponse = (pdf: Buffer<ArrayBuffer>, id: string) => { return new Response(pdf, { headers: { 'Content-Type': 'application/pdf', 'CDN-Cache-Control': 'max-age=604800', 'Content-Disposition': `inline; filename="receipt-${id}.pdf"` } });}
export const GET: APIRoute = async ({ locals, params, request }) => { const id = params.id as string; const origin = new URL(request.url).origin; const targetUrl = `${origin}/receipt/${id}`; const cache = locals.runtime.env.PDF_CACHE;
// Try KV cache first to avoid unnecessary browser work const cachedPdf = await cache.get(targetUrl, "arrayBuffer"); if (cachedPdf) return pdfResponse(Buffer.from(cachedPdf), id);
// Only run browser in Cloudflare's env if (!locals.runtime.env.BROWSER) return new Response('Browser binding not available locally', { status: 501 });
// Launch browser and render HTML to PDF const session = await puppeteer.launch(locals.runtime.env.BROWSER); const page = await session.newPage(); await page.goto(targetUrl, { waitUntil: 'networkidle0' }); const pdf = await page.pdf({ format: 'A4', printBackground: true, margin: { top: '24mm', right: '18mm', bottom: '24mm', left: '18mm' } }); await session.close();
const result = Buffer.from(pdf); // Cache for a week (604800 seconds) await cache.put(targetUrl, result, { expirationTtl: 60 * 60 * 24 * 7 }); return pdfResponse(result, id);};Ahora, cuando acceda a /api/pdf/123, recibirá un PDF generado en vivo desde /receipt/123. Las solicitudes posteriores devuelven el PDF en caché hasta que expire.
Proteger y almacenar en caché sus PDFs
- Autenticación: Exija un token de sesión o una cadena de consulta firmada antes de renderizar. Rechace las solicitudes no autenticadas de forma temprana.
- Caché: Almacene en caché facturas estáticas durante periodos breves (
Cache-Control: public, max-age=300) para evitar volver a renderizar. Para contenido dinámico, mantenga la caché privada. - Límites de tasa: Añada un limitador de tasa (KV) para endpoints de alto tráfico.
Desplegar en Cloudflare Workers
Despliegue su aplicación de generación de PDFs en producción:
# Build the projectnpm run build
# Deploy to Cloudflare Workersnpx wrangler deployTras el despliegue, su API estará disponible en https://your-worker.workers.dev/api/pdf/[id].
Conclusión
Cloudflare Browser Rendering transforma la generación de PDFs de una operación que consume muchos recursos en una función edge ligera. Al combinar las capacidades SSR de Astro con Chromium headless en el edge, puede generar facturas, recibos e informes bajo demanda sin gestionar infraestructura ni lidiar con cold starts.
Para casos de uso avanzados, explore documentos de varias páginas, encabezados y pies de página personalizados, o incluso la generación de capturas de pantalla para imágenes Open Graph. La API de Browser Rendering le ofrece control completo de Puppeteer, todo ejecutándose en el edge de Cloudflare.
Si tiene alguna pregunta o comentario, no dude en comunicarse conmigo en Twitter.