Despliegue en Cloudflare Workers

Por: Artiko
vinextcloudflare-workersdeploykv-cachewrangler

vinext deploy

El comando vinext deploy automatiza todo el proceso de despliegue en Cloudflare Workers:

vinext deploy

¿Qué hace internamente?

El proceso tiene 7 pasos:

  1. Detección de proyecto — Identifica App Router vs Pages Router, ISR, MDX, módulos nativos
  2. Gestión de dependencias — Instala @cloudflare/vite-plugin, wrangler, y @vitejs/plugin-rsc si faltan
  3. Preparación de módulos — Agrega "type": "module", renombra configs CJS a .cjs
  4. Generación de configuración — Crea wrangler.jsonc, worker/index.ts, vite.config.ts si no existen
  5. Build de Vite — Compila la aplicación
  6. TPR (opcional) — Pre-renderiza páginas de alto tráfico
  7. Deploy con Wrangler — Sube el worker a Cloudflare

Opciones del deploy

vinext deploy --preview           # Deploy a entorno preview
vinext deploy --env staging       # Deploy a entorno específico
vinext deploy --name mi-app       # Nombre custom del worker
vinext deploy --skip-build        # Solo deploy (asume build previo)
vinext deploy --dry-run           # Simula sin desplegar

Configuración manual

wrangler.jsonc

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "mi-app-vinext",
  "compatibility_date": "2026-02-12",
  "compatibility_flags": ["nodejs_compat"],
  "main": "./worker/index.ts",
  "preview_urls": true,
  "assets": {
    "not_found_handling": "none",
    "binding": "ASSETS"
  }
}

vite.config.ts para Workers

App Router:

import { defineConfig } from "vite";
import vinext from "vinext";
import { cloudflare } from "@cloudflare/vite-plugin";

export default defineConfig({
  plugins: [
    vinext(),
    cloudflare({
      viteEnvironment: {
        name: "rsc",
        childEnvironments: ["ssr"],
      },
    }),
  ],
});

Pages Router:

import { defineConfig } from "vite";
import vinext from "vinext";
import { cloudflare } from "@cloudflare/vite-plugin";

export default defineConfig({
  plugins: [vinext(), cloudflare()],
});

Worker entry point

// worker/index.ts (App Router)
import handler from "vinext/server/app-router-entry";

export default handler;

Si no necesitas lógica custom en el worker, puedes apuntar directamente en wrangler:

{
  "main": "vinext/server/app-router-entry"
}

Worker con optimización de imágenes

// worker/index.ts
import handler from "vinext/server/app-router-entry";

interface Env {
  ASSETS: Fetcher;
  IMAGES: { input: any; transform: any; output: any };
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname === "/_vinext/image") {
      const src = url.searchParams.get("url");
      const width = parseInt(url.searchParams.get("w") || "0");
      const quality = parseInt(url.searchParams.get("q") || "75");

      const asset = await env.ASSETS.fetch(new URL(src!, request.url));
      const result = await env.IMAGES
        .input(asset)
        .transform({ width, quality, format: "auto" })
        .output();

      return new Response(result.image(), {
        headers: { ...result.headers, "cache-control": "public, max-age=31536000" },
      });
    }

    return handler.fetch(request);
  },
};

KV Cache Handler (ISR)

Para Incremental Static Regeneration en Workers, usa el KV Cache Handler:

Configurar KV namespace

En wrangler.jsonc:

{
  "kv_namespaces": [
    { "binding": "CACHE", "id": "tu-kv-namespace-id" }
  ]
}

Activar en el worker

import { KVCacheHandler } from "vinext/cloudflare";
import { setCacheHandler } from "next/cache";

// En el worker entry
setCacheHandler(new KVCacheHandler(env.CACHE));

Cómo funciona el KV Cache

CaracterísticaComportamiento
Stale-while-revalidateRetorna datos stale mientras regenera en background
DeduplicaciónUna sola regeneración concurrente por key
Invalidación por tagsrevalidateTag() borra la entry del cache
TTL10x el periodo de revalidación (entre 60s y 30 días)

ISR en acción

// app/posts/page.tsx
export const revalidate = 60; // Regenerar cada 60 segundos

export default async function PostsPage() {
  const posts = await fetch("https://api.example.com/posts", {
    next: { revalidate: 60 },
  }).then(r => r.json());

  return (
    <ul>
      {posts.map((p: { id: number; title: string }) => (
        <li key={p.id}>{p.title}</li>
      ))}
    </ul>
  );
}

La primera visita renderiza la página y la guarda en KV. Las siguientes 60 segundos sirven desde cache. Después, la siguiente visita dispara regeneración en background.

Traffic-aware Pre-Rendering (TPR)

TPR pre-renderiza las páginas más visitadas al momento del deploy, basándose en analytics de Cloudflare:

vinext deploy --experimental-tpr

Opciones de TPR

vinext deploy --experimental-tpr --tpr-coverage 95   # Cubrir 95% del tráfico
vinext deploy --experimental-tpr --tpr-limit 500     # Máximo 500 páginas
vinext deploy --experimental-tpr --tpr-window 48     # Últimas 48h de analytics

Requisitos

Cómo funciona

  1. Consulta analytics de la zona al momento del deploy
  2. Identifica las páginas con más tráfico
  3. Pre-renderiza solo esas páginas
  4. Sube el HTML pre-renderizado a KV

El resultado: latencia nivel SSG para las páginas populares, sin pre-renderizar todo el sitio.

Variables de entorno

Las variables NEXT_PUBLIC_* se inlinan en build time:

# .env.local
NEXT_PUBLIC_API_URL=https://api.miapp.com
SECRET_KEY=mi-secreto

En el código:

Para Workers, configura secrets con wrangler:

wrangler secret put SECRET_KEY

Siguiente paso

En el último capítulo aprenderemos a migrar un proyecto Next.js existente a Vinext.