{"schemaVersion":1,"id":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework","title":"Deno 2: Compatibilidad Total con Node, Fresh 2.0 y Benchmarks Comparativos Contra Node 22 y Bun","description":"Una evaluación técnica honesta de la capa de compatibilidad Node.js de Deno 2, npm specifiers, la arquitectura de islands de Fresh 2.0, y benchmarks de performance que cuentan una historia más matizada de lo que sugiere el marketing.","language":"es","datePublished":"2025-11-09","dateModified":null,"contentFormat":"text/markdown","content":"JavaScript Runtimes\n\n# Deno 2: Compatibilidad Total con Node, Fresh 2.0 y Benchmarks Comparativos Contra Node 22 y Bun\n\n2025-11-098 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/deno-2-node-compat-fresh-framework>)\n\n## La Capa de Compatibilidad con Node.js\n\nLa compatibilidad de [Deno](<https://deno.com/>) 2 con Node.js se construye sobre tres pilares:\n\n### 1. npm Specifiers\n\nEs posible importar cualquier paquete npm directamente:\n\n```typescript\nimport express from \"npm:express@4\";\nimport { PrismaClient } from \"npm:@prisma/client\";\nimport chalk from \"npm:chalk@5\";\n```\n\nPor debajo, Deno descarga el paquete a un cache global (` ~/.cache/deno/npm/ `) y reescribe el import map en tiempo de resolución. La diferencia clave con Node: **no hay carpeta ` node_modules ` por defecto.** Deno resuelve dependencias desde el cache global, deduplicando entre proyectos.\n\nEsto realmente resuelve uno de los puntos de dolor más viejos de Node. Si teners 10 proyectos que todos usan ` lodash@4.17.21 `, Node almacena 10 copias. Deno almacena una.\n\n```bash\n# Ver dónde se resuelve un paquete\n$ deno info npm:express@4\nlocal: /home/user/.cache/deno/npm/registry.npmjs.org/express/4.21.1/\n```\n\n### 2. Polyfills de Módulos Built-in de Node\n\nDeno implementa el protocolo ` node: ` para módulos built-in:\n\n```typescript\nimport { readFile } from \"node:fs/promises\";\nimport { createServer } from \"node:http\";\nimport { join } from \"node:path\";\nimport { Buffer } from \"node:buffer\";\n```\n\nLa cobertura es impresionante pero no completa. En Deno 2.1, la realidad es así:\n\n```\nTotalmente implementados: node:fs, node:path, node:url, node:buffer,\n node:crypto, node:stream, node:events,\n node:util, node:os, node:process, node:http,\n node:https, node:net, node:tls, node:zlib,\n node:assert, node:child_process, node:worker_threads\n\nParcialmente implementados: node:cluster (sin sticky sessions),\n node:dgram (solo UDP básico),\n node:dns (sin resolveNs/resolveSrv),\n node:vm (sin runInContext)\n\nNo implementados: node:v8 (heap snapshots de V8),\n node:trace_events,\n node:perf_hooks (parcial)\n```\n\nPara la mayoría de las aplicaciones web, la lista de completamente implementados cubre todo lo que se necesita. Donde surgen problemas es en paquetes nicho que dependen de ` node:vm ` para sandboxing o ` node:v8 ` para análisis de heap.\n\n### 3. Soporte de package.json\n\nDeno 2 lee ` package.json ` y respeta sus campos:\n\n```json\n{\n \"name\": \"my-app\",\n \"scripts\": {\n \"dev\": \"deno task dev\",\n \"build\": \"deno task build\"\n },\n \"dependencies\": {\n \"hono\": \"^4.0.0\",\n \"drizzle-orm\": \"^0.30.0\"\n }\n}\n```\n\n```bash\n# Deno lee package.json e instala dependencias\n$ deno install\n# O simplemente ejecutar, Deno auto-instala en el primer uso\n$ deno run -A server.ts\n```\n\nEsta es la feature que hace la migración realista. Es posible tomar un proyecto Node.js existente, apuntar Deno a él, y la mayoría de las cosas simplemente funcionan.\n\n## Fresh 2.0: El Framework que Hace Islands Bien\n\n[Fresh](<https://fresh.deno.dev/>) es el framework web full-stack de Deno, y la versión 2.0 es un replanteamiento significativo. La idea core no cambió, rendering server-side por defecto con \"islands\" de interactividad, pero la implementación es mucho más refinada.\n\n### Cómo Funcionan los Islands\n\n```\nSPA Tradicional:\n Servidor renderiza HTML → Cliente descarga toda la app React →\n Hidrata todo → La página se vuelve interactiva\n\nFresh Islands:\n Servidor renderiza HTML → Cliente descarga SOLO JS de islands →\n Hidrata SOLO islands → Resto de la página es HTML estático (cero JS)\n```\n\nA continuación se muestra cómo se ve en la práctica:\n\n```tsx\n// routes/products/[id].tsx, Ruta renderizada en servidor (cero JS cliente)\nimport { Handlers, PageProps } from \"$fresh/server.ts\";\nimport { ProductDetails } from \"../components/ProductDetails.tsx\";\nimport AddToCartIsland from \"../islands/AddToCart.tsx\";\nimport ReviewsIsland from \"../islands/Reviews.tsx\";\n\nexport const handler: Handlers<Product> = {\n async GET(_req, ctx) {\n const product = await db.products.findUnique({\n where: { id: ctx.params.id },\n });\n if (!product) return ctx.renderNotFound();\n return ctx.render(product);\n },\n};\n\nexport default function ProductPage({ data }: PageProps<Product>) {\n return (\n <div class=\"max-w-4xl mx-auto\">\n {/* Esto se renderiza en el servidor. Cero JS al cliente. */}\n <ProductDetails product={data} />\n\n {/* Esto es un island. Solo el JS de este componente se envía. */}\n <AddToCartIsland productId={data.id} price={data.price} />\n\n {/* Otro island. Se carga independientemente. */}\n <ReviewsIsland productId={data.id} />\n </div>\n );\n}\n```\n\n```tsx\n// islands/AddToCart.tsx, Esto envía JS al cliente\nimport { useSignal } from \"@preact/signals\";\n\nexport default function AddToCart({ productId, price }: Props) {\n const quantity = useSignal(1);\n const loading = useSignal(false);\n\n async function handleAddToCart() {\n loading.value = true;\n await fetch(\"/api/cart\", {\n method: \"POST\",\n body: JSON.stringify({ productId, quantity: quantity.value }),\n });\n loading.value = false;\n }\n\n return (\n <div class=\"flex items-center gap-4\">\n <select\n value={quantity.value}\n onChange={(e) => quantity.value = Number(e.currentTarget.value)}\n >\n {[1, 2, 3, 4, 5].map((n) => <option value={n}>{n}</option>)}\n </select>\n <button\n onClick={handleAddToCart}\n disabled={loading.value}\n class=\"bg-blue-600 text-white px-4 py-2 rounded\"\n >\n Agregar al Carrito, ${(price * quantity.value).toFixed(2)}\n </button>\n </div>\n );\n}\n```\n\nEl componente ` ProductDetails ` envía cero JavaScript. El island ` AddToCart ` envía quizás 3KB de JS (runtime de señales Preact + componente). Para una página de producto que podría ser 50KB de HTML, se está enviando 3KB de JS en vez de los 150KB+ que una SPA React requeriría.\n\n### Partials: Navegación SPA Sin SPAs\n\nFresh 2.0 introdujo Partials, una forma de actualizar secciones de la página sin navegación completa:\n\n```tsx\nimport { Partial } from \"$fresh/runtime.ts\";\n\nexport function ProductList({ products, page }) {\n return (\n <Partial name=\"product-list\">\n <div class=\"grid grid-cols-3 gap-4\">\n {products.map((p) => (\n <a href={`/products?page=${page}`} f-partial=\"/partials/products\">\n <ProductCard product={p} />\n </a>\n ))}\n </div>\n <Pagination page={page} />\n </Partial>\n );\n}\n```\n\nClickear un link con atributo ` f-partial ` fetches el nuevo fragmento HTML del servidor y intercambia solo esa sección del DOM. Sin librería de routing client-side. Sin state management de React. Solo HTML-over-the-wire con un runtime minúsculo (~2KB).\n\n## Benchmarks: Análisis Contextual\n\nEste es el punto donde una perspectiva contraria es justificada. Todos benchmarkean runtimes con servidores HTTP de \"hello world\". Ese es benchmarks son inútiles para aplicaciones del mundo real. Esto es lo que realmente importa.\n\n### Setup de Tests\n\nTodos los tests en la misma máquina (AMD Ryzen 9 7950X, 32GB RAM, Ubuntu 24.04):\n\n- **Node 22.11.0** con Fastify 5.0\n- **Deno 2.1.4** con Deno.serve()\n- **Bun 1.1.38** con Bun.serve()\n\n### Hello World HTTP (requests/sec)\n\n```\nNode 22 (Fastify): 128,000 req/s\nDeno 2.1 (Deno.serve): 152,000 req/s\nBun 1.1 (Bun.serve): 167,000 req/s\n```\n\nDeno supera a Node por ~19%. Bun supera a Deno por ~10%. Estos números coinciden con el marketing. Pero la cosa es esta, este benchmark está midiendo la implementación del servidor HTTP, no el runtime.\n\n### API JSON con Acceso a Base de Datos\n\nMás realista, fetch de SQLite, serializar JSON, retornar:\n\n```\nNode 22 (Fastify + better-sqlite3): 24,500 req/s\nDeno 2.1 (Deno.serve + deno-sqlite): 22,800 req/s\nBun 1.1 (Bun.serve + bun:sqlite): 26,100 req/s\n```\n\nLa brecha se achica dramáticamente. Ahora estamos pasando la mayor parte del tiempo en V8 (los tres lo usan) y SQLite. La capa HTTP del runtime apenas importa.\n\n### Server-Side Rendering (Preact/JSX)\n\nRenderizar una página de producto con 50 items:\n\n```\nNode 22 (Fastify + preact-render-to-string): 3,800 req/s\nDeno 2.1 (Fresh, SSR): 3,650 req/s\nBun 1.1 (Elysia + preact-render-to-string): 3,900 req/s\n```\n\nEsencialmente idénticos. SSR es enteramente CPU-bound en V8. El runtime no importa.\n\n### Tiempo de Startup\n\nEste es el punto donde las diferencias son marcadas:\n\n```\nNode 22 (script vacío): 35ms\nNode 22 (servidor Fastify): 120ms\nDeno 2.1 (script vacío): 45ms\nDeno 2.1 (con type-check): 280ms\nDeno 2.1 (--no-check): 50ms\nBun 1.1 (script vacío): 8ms\nBun 1.1 (servidor Elysia): 22ms\n```\n\nBun destruye absolutamente a Node y Deno en startup. Para funciones serverless donde el cold start importa, esta es la característica más destacada de Bun.\n\n### Uso de Memoria\n\nServidor HTTP idle:\n\n```\nNode 22 (Fastify): 45MB RSS\nDeno 2.1 (Deno.serve): 38MB RSS\nBun 1.1 (Bun.serve): 32MB RSS\n```\n\n## Cuándo Elegir Qué\n\nBasándose en el uso productivo de los tres runtimes en diferentes proyectos, esta es una evaluación honesta:\n\n**Elegir Deno 2 cuando:**\n\n- Se desea TypeScript built-in sin paso de build\n- La seguridad importa (el modelo de permise es es único y valioso)\n- Se está desplegando a Deno Deploy (su runtime edge es excelente)\n- Se está arrancando un proyecto nuevo y se desea la arquitectura de islands de Fresh\n- Se desea una sola herramienta para formateo, linting, testing, y ejecución\n\n**Elegir Node 22 cuando:**\n\n- Teners un codebase Node.js existente\n- Se depende de addons nativos (node-gyp)\n- Tu equipo conoce Node y el ecosistema profundamente\n- Se necesita la compatibilidad de paquetes más amplia posible\n\n**Elegir Bun cuando:**\n\n- El tiempo de cold start es crítico (serverless, herramientas CLI)\n- Se desea la experiencia de desarrollo más rápida posible\n- Se necesita SQLite built-in\n- Se está dispuesto a aceptar un runtime menos maduro por ganancias de velocidad\n\nLa verdad incómoda que ni el equipo de marketing de Deno ni el de Bun te van a decir es esta: **para la mayoría de las aplicaciones web, el runtime no importa.** Tu cuello de botella son las queries a la base de datos, el I/O de red, y la lógica de negocio. La diferencia entre 128K y 167K requests por segundo en un benchmark de hello world nunca va a ser relevante en producción.\n\nLo que sí importa es la experiencia de desarrollo, la compatibilidad del ecosistema, y la historia de deployment. En esas dimensiones, Deno 2 dio un salto masivo hacia adelante. Ya no te está pidiendo que abandones el ecosistema npm, te está dejando usarlo mientras ofrece algo mejor para código nuevo.\n\nEse es un pitch mucho más fuerte que \"somos X% más rápidos sirviendo respuestas HTTP vacías.\"\n\n## Preguntas frecuentes\n\n### ¿Puedo usar cualquier paquete npm con Deno 2?\n\nLa mayoría de los paquetes funcionan out of the box con specifiers npm:. Paquetes que dependen de addons nativos (node-gyp), scripts postinstall, o APIs internas específicas de Node.js pueden necesitar el flag --node-modules-dir o directamente no funcionar. En la práctica, alrededor del 95% de los top 1000 paquetes npm funcionan sin problemas.\n\n### ¿Deno es más rápido que Node.js?\n\nDepende del workload. Para servir HTTP, el servidor basado en Hyper de Deno es 15-20% más rápido. Para trabajo CPU-bound, son esencialmente idénticos (ambos usan V8). Para tiempo de startup, Deno es más lento por el type-checking de TypeScript (a menos que uses --no-check). Bun es más rápido que ambos para startup.\n\n### ¿Debería migrar mi proyecto Node.js a Deno 2?\n\nSolo si valorás el TypeScript built-in de Deno, el modelo de permisos, o el runtime edge de Deno Deploy. Para proyectos Node.js existentes con pipelines de build complejos, el costo de migración raramente justifica los beneficios. Para proyectos nuevos, Deno 2 es un contendiente serio.\n\n## Tips\n\n1. **Resolución de npm specifiers:** Deno 2 resuelve specifiers ` npm: ` a través de un cache global en ` $DENO_DIR/npm/ `, no en ` node_modules/ `. Si un paquete usa scripts postinstall (addons nativos), se necesita el flag ` --node-modules-dir ` para crear node\\_modules local. Verificar con ` deno info npm:nombre-paquete ` para ver el grafo de resolución antes de depurar problemas de imports.\n2. **Hidratación parcial de Fresh 2.0:** Fresh usa señales de Preact para reactividad en islands. Envolver componentes interactivos con ` island() `, todo fuera de los islands envía cero JS al cliente. La clave: utilizar componentes ` <Partial> ` para actualizar secciones del DOM sin navegación completa de página. Esto te da transiciones estilo SPA con arquitectura MPA.\n3. **Reutilización de V8 isolates en Deno Deploy:** Deno Deploy mantiene V8 isolates calientes entre requests. El primer request a un isolate frío tarda ~50ms, los siguientes reutilizan el isolate con ~1ms de overhead. Estructurar el código a nivel de módulo para que corra una vez (conexiones a DB, parsing de config) ya que persiste entre requests dentro del mismo ciclo de vida del isolate.\n4. **Deno.serve() vs node:http en performance:** ` Deno.serve() ` usa Hyper (Rust) por debajo, bypaseando V8 para el manejo TCP. Para la capa HTTP específicamente, benchmarkea 15-20% más rápido que ` node:http ` de Node. Pero si tu handler es CPU-bound (rendering de templates, serialización JSON), la diferencia de runtime desaparece porque ambos pasan 95%+ del tiempo en V8.\n\n- deno\n- node-js\n- bun\n- fresh-framework\n- javascript-runtimes\n- typescript\n- performance\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)","headings":[{"level":1,"text":"Deno 2: Compatibilidad Total con Node, Fresh 2.0 y Benchmarks Comparativos Contra Node 22 y Bun","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#title"},{"level":2,"text":"La Capa de Compatibilidad con Node.js","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#la-capa-de-compatibilidad-con-nodejs"},{"level":3,"text":"1. npm Specifiers","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#1-npm-specifiers"},{"level":3,"text":"2. Polyfills de Módulos Built-in de Node","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#2-polyfills-de-mdulos-built-in-de-node"},{"level":3,"text":"3. Soporte de package.json","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#3-soporte-de-packagejson"},{"level":2,"text":"Fresh 2.0: El Framework que Hace Islands Bien","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#fresh-20-el-framework-que-hace-islands-bien"},{"level":3,"text":"Cómo Funcionan los Islands","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#cmo-funcionan-los-islands"},{"level":3,"text":"Partials: Navegación SPA Sin SPAs","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#partials-navegacin-spa-sin-spas"},{"level":2,"text":"Benchmarks: Análisis Contextual","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#benchmarks-anlisis-contextual"},{"level":3,"text":"Setup de Tests","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#setup-de-tests"},{"level":3,"text":"Hello World HTTP (requests/sec)","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#hello-world-http-requestssec"},{"level":3,"text":"API JSON con Acceso a Base de Datos","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#api-json-con-acceso-a-base-de-datos"},{"level":3,"text":"Server-Side Rendering (Preact/JSX)","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#server-side-rendering-preactjsx"},{"level":3,"text":"Tiempo de Startup","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#tiempo-de-startup"},{"level":3,"text":"Uso de Memoria","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#uso-de-memoria"},{"level":2,"text":"Cuándo Elegir Qué","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#cundo-elegir-qu"},{"level":2,"text":"Preguntas frecuentes","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#questions"},{"level":3,"text":"¿Puedo usar cualquier paquete npm con Deno 2?","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#question-1"},{"level":3,"text":"¿Deno es más rápido que Node.js?","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#question-2"},{"level":3,"text":"¿Debería migrar mi proyecto Node.js a Deno 2?","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#question-3"},{"level":2,"text":"Tips","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#article-tips"}],"sections":[{"heading":"Deno 2: Compatibilidad Total con Node, Fresh 2.0 y Benchmarks Comparativos Contra Node 22 y Bun","level":1,"url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework","content":"JavaScript Runtimes"},{"heading":"Deno 2: Compatibilidad Total con Node, Fresh 2.0 y Benchmarks Comparativos Contra Node 22 y Bun","level":1,"url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#title","content":"2025-11-098 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/deno-2-node-compat-fresh-framework>)"},{"heading":"La Capa de Compatibilidad con Node.js","level":2,"url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#la-capa-de-compatibilidad-con-nodejs","content":"La compatibilidad de [Deno](<https://deno.com/>) 2 con Node.js se construye sobre tres pilares:"},{"heading":"1. npm Specifiers","level":3,"url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#1-npm-specifiers","content":"Es posible importar cualquier paquete npm directamente:\n\n```typescript\nimport express from \"npm:express@4\";\nimport { PrismaClient } from \"npm:@prisma/client\";\nimport chalk from \"npm:chalk@5\";\n```\n\nPor debajo, Deno descarga el paquete a un cache global (` ~/.cache/deno/npm/ `) y reescribe el import map en tiempo de resolución. La diferencia clave con Node: **no hay carpeta ` node_modules ` por defecto.** Deno resuelve dependencias desde el cache global, deduplicando entre proyectos.\n\nEsto realmente resuelve uno de los puntos de dolor más viejos de Node. Si teners 10 proyectos que todos usan ` lodash@4.17.21 `, Node almacena 10 copias. Deno almacena una.\n\n```bash\n# Ver dónde se resuelve un paquete\n$ deno info npm:express@4\nlocal: /home/user/.cache/deno/npm/registry.npmjs.org/express/4.21.1/\n```"},{"heading":"2. Polyfills de Módulos Built-in de Node","level":3,"url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#2-polyfills-de-mdulos-built-in-de-node","content":"Deno implementa el protocolo ` node: ` para módulos built-in:\n\n```typescript\nimport { readFile } from \"node:fs/promises\";\nimport { createServer } from \"node:http\";\nimport { join } from \"node:path\";\nimport { Buffer } from \"node:buffer\";\n```\n\nLa cobertura es impresionante pero no completa. En Deno 2.1, la realidad es así:\n\n```\nTotalmente implementados: node:fs, node:path, node:url, node:buffer,\n node:crypto, node:stream, node:events,\n node:util, node:os, node:process, node:http,\n node:https, node:net, node:tls, node:zlib,\n node:assert, node:child_process, node:worker_threads\n\nParcialmente implementados: node:cluster (sin sticky sessions),\n node:dgram (solo UDP básico),\n node:dns (sin resolveNs/resolveSrv),\n node:vm (sin runInContext)\n\nNo implementados: node:v8 (heap snapshots de V8),\n node:trace_events,\n node:perf_hooks (parcial)\n```\n\nPara la mayoría de las aplicaciones web, la lista de completamente implementados cubre todo lo que se necesita. Donde surgen problemas es en paquetes nicho que dependen de ` node:vm ` para sandboxing o ` node:v8 ` para análisis de heap."},{"heading":"3. Soporte de package.json","level":3,"url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#3-soporte-de-packagejson","content":"Deno 2 lee ` package.json ` y respeta sus campos:\n\n```json\n{\n \"name\": \"my-app\",\n \"scripts\": {\n \"dev\": \"deno task dev\",\n \"build\": \"deno task build\"\n },\n \"dependencies\": {\n \"hono\": \"^4.0.0\",\n \"drizzle-orm\": \"^0.30.0\"\n }\n}\n```\n\n```bash\n# Deno lee package.json e instala dependencias\n$ deno install\n# O simplemente ejecutar, Deno auto-instala en el primer uso\n$ deno run -A server.ts\n```\n\nEsta es la feature que hace la migración realista. Es posible tomar un proyecto Node.js existente, apuntar Deno a él, y la mayoría de las cosas simplemente funcionan."},{"heading":"Fresh 2.0: El Framework que Hace Islands Bien","level":2,"url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#fresh-20-el-framework-que-hace-islands-bien","content":"[Fresh](<https://fresh.deno.dev/>) es el framework web full-stack de Deno, y la versión 2.0 es un replanteamiento significativo. La idea core no cambió, rendering server-side por defecto con \"islands\" de interactividad, pero la implementación es mucho más refinada."},{"heading":"Cómo Funcionan los Islands","level":3,"url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#cmo-funcionan-los-islands","content":"```\nSPA Tradicional:\n Servidor renderiza HTML → Cliente descarga toda la app React →\n Hidrata todo → La página se vuelve interactiva\n\nFresh Islands:\n Servidor renderiza HTML → Cliente descarga SOLO JS de islands →\n Hidrata SOLO islands → Resto de la página es HTML estático (cero JS)\n```\n\nA continuación se muestra cómo se ve en la práctica:\n\n```tsx\n// routes/products/[id].tsx, Ruta renderizada en servidor (cero JS cliente)\nimport { Handlers, PageProps } from \"$fresh/server.ts\";\nimport { ProductDetails } from \"../components/ProductDetails.tsx\";\nimport AddToCartIsland from \"../islands/AddToCart.tsx\";\nimport ReviewsIsland from \"../islands/Reviews.tsx\";\n\nexport const handler: Handlers<Product> = {\n async GET(_req, ctx) {\n const product = await db.products.findUnique({\n where: { id: ctx.params.id },\n });\n if (!product) return ctx.renderNotFound();\n return ctx.render(product);\n },\n};\n\nexport default function ProductPage({ data }: PageProps<Product>) {\n return (\n <div class=\"max-w-4xl mx-auto\">\n {/* Esto se renderiza en el servidor. Cero JS al cliente. */}\n <ProductDetails product={data} />\n\n {/* Esto es un island. Solo el JS de este componente se envía. */}\n <AddToCartIsland productId={data.id} price={data.price} />\n\n {/* Otro island. Se carga independientemente. */}\n <ReviewsIsland productId={data.id} />\n </div>\n );\n}\n```\n\n```tsx\n// islands/AddToCart.tsx, Esto envía JS al cliente\nimport { useSignal } from \"@preact/signals\";\n\nexport default function AddToCart({ productId, price }: Props) {\n const quantity = useSignal(1);\n const loading = useSignal(false);\n\n async function handleAddToCart() {\n loading.value = true;\n await fetch(\"/api/cart\", {\n method: \"POST\",\n body: JSON.stringify({ productId, quantity: quantity.value }),\n });\n loading.value = false;\n }\n\n return (\n <div class=\"flex items-center gap-4\">\n <select\n value={quantity.value}\n onChange={(e) => quantity.value = Number(e.currentTarget.value)}\n >\n {[1, 2, 3, 4, 5].map((n) => <option value={n}>{n}</option>)}\n </select>\n <button\n onClick={handleAddToCart}\n disabled={loading.value}\n class=\"bg-blue-600 text-white px-4 py-2 rounded\"\n >\n Agregar al Carrito, ${(price * quantity.value).toFixed(2)}\n </button>\n </div>\n );\n}\n```\n\nEl componente ` ProductDetails ` envía cero JavaScript. El island ` AddToCart ` envía quizás 3KB de JS (runtime de señales Preact + componente). Para una página de producto que podría ser 50KB de HTML, se está enviando 3KB de JS en vez de los 150KB+ que una SPA React requeriría."},{"heading":"Partials: Navegación SPA Sin SPAs","level":3,"url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#partials-navegacin-spa-sin-spas","content":"Fresh 2.0 introdujo Partials, una forma de actualizar secciones de la página sin navegación completa:\n\n```tsx\nimport { Partial } from \"$fresh/runtime.ts\";\n\nexport function ProductList({ products, page }) {\n return (\n <Partial name=\"product-list\">\n <div class=\"grid grid-cols-3 gap-4\">\n {products.map((p) => (\n <a href={`/products?page=${page}`} f-partial=\"/partials/products\">\n <ProductCard product={p} />\n </a>\n ))}\n </div>\n <Pagination page={page} />\n </Partial>\n );\n}\n```\n\nClickear un link con atributo ` f-partial ` fetches el nuevo fragmento HTML del servidor y intercambia solo esa sección del DOM. Sin librería de routing client-side. Sin state management de React. Solo HTML-over-the-wire con un runtime minúsculo (~2KB)."},{"heading":"Benchmarks: Análisis Contextual","level":2,"url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#benchmarks-anlisis-contextual","content":"Este es el punto donde una perspectiva contraria es justificada. Todos benchmarkean runtimes con servidores HTTP de \"hello world\". Ese es benchmarks son inútiles para aplicaciones del mundo real. Esto es lo que realmente importa."},{"heading":"Setup de Tests","level":3,"url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#setup-de-tests","content":"Todos los tests en la misma máquina (AMD Ryzen 9 7950X, 32GB RAM, Ubuntu 24.04):\n\n- **Node 22.11.0** con Fastify 5.0\n- **Deno 2.1.4** con Deno.serve()\n- **Bun 1.1.38** con Bun.serve()"},{"heading":"Hello World HTTP (requests/sec)","level":3,"url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#hello-world-http-requestssec","content":"```\nNode 22 (Fastify): 128,000 req/s\nDeno 2.1 (Deno.serve): 152,000 req/s\nBun 1.1 (Bun.serve): 167,000 req/s\n```\n\nDeno supera a Node por ~19%. Bun supera a Deno por ~10%. Estos números coinciden con el marketing. Pero la cosa es esta, este benchmark está midiendo la implementación del servidor HTTP, no el runtime."},{"heading":"API JSON con Acceso a Base de Datos","level":3,"url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#api-json-con-acceso-a-base-de-datos","content":"Más realista, fetch de SQLite, serializar JSON, retornar:\n\n```\nNode 22 (Fastify + better-sqlite3): 24,500 req/s\nDeno 2.1 (Deno.serve + deno-sqlite): 22,800 req/s\nBun 1.1 (Bun.serve + bun:sqlite): 26,100 req/s\n```\n\nLa brecha se achica dramáticamente. Ahora estamos pasando la mayor parte del tiempo en V8 (los tres lo usan) y SQLite. La capa HTTP del runtime apenas importa."},{"heading":"Server-Side Rendering (Preact/JSX)","level":3,"url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#server-side-rendering-preactjsx","content":"Renderizar una página de producto con 50 items:\n\n```\nNode 22 (Fastify + preact-render-to-string): 3,800 req/s\nDeno 2.1 (Fresh, SSR): 3,650 req/s\nBun 1.1 (Elysia + preact-render-to-string): 3,900 req/s\n```\n\nEsencialmente idénticos. SSR es enteramente CPU-bound en V8. El runtime no importa."},{"heading":"Tiempo de Startup","level":3,"url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#tiempo-de-startup","content":"Este es el punto donde las diferencias son marcadas:\n\n```\nNode 22 (script vacío): 35ms\nNode 22 (servidor Fastify): 120ms\nDeno 2.1 (script vacío): 45ms\nDeno 2.1 (con type-check): 280ms\nDeno 2.1 (--no-check): 50ms\nBun 1.1 (script vacío): 8ms\nBun 1.1 (servidor Elysia): 22ms\n```\n\nBun destruye absolutamente a Node y Deno en startup. Para funciones serverless donde el cold start importa, esta es la característica más destacada de Bun."},{"heading":"Uso de Memoria","level":3,"url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#uso-de-memoria","content":"Servidor HTTP idle:\n\n```\nNode 22 (Fastify): 45MB RSS\nDeno 2.1 (Deno.serve): 38MB RSS\nBun 1.1 (Bun.serve): 32MB RSS\n```"},{"heading":"Cuándo Elegir Qué","level":2,"url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#cundo-elegir-qu","content":"Basándose en el uso productivo de los tres runtimes en diferentes proyectos, esta es una evaluación honesta:\n\n**Elegir Deno 2 cuando:**\n\n- Se desea TypeScript built-in sin paso de build\n- La seguridad importa (el modelo de permise es es único y valioso)\n- Se está desplegando a Deno Deploy (su runtime edge es excelente)\n- Se está arrancando un proyecto nuevo y se desea la arquitectura de islands de Fresh\n- Se desea una sola herramienta para formateo, linting, testing, y ejecución\n\n**Elegir Node 22 cuando:**\n\n- Teners un codebase Node.js existente\n- Se depende de addons nativos (node-gyp)\n- Tu equipo conoce Node y el ecosistema profundamente\n- Se necesita la compatibilidad de paquetes más amplia posible\n\n**Elegir Bun cuando:**\n\n- El tiempo de cold start es crítico (serverless, herramientas CLI)\n- Se desea la experiencia de desarrollo más rápida posible\n- Se necesita SQLite built-in\n- Se está dispuesto a aceptar un runtime menos maduro por ganancias de velocidad\n\nLa verdad incómoda que ni el equipo de marketing de Deno ni el de Bun te van a decir es esta: **para la mayoría de las aplicaciones web, el runtime no importa.** Tu cuello de botella son las queries a la base de datos, el I/O de red, y la lógica de negocio. La diferencia entre 128K y 167K requests por segundo en un benchmark de hello world nunca va a ser relevante en producción.\n\nLo que sí importa es la experiencia de desarrollo, la compatibilidad del ecosistema, y la historia de deployment. En esas dimensiones, Deno 2 dio un salto masivo hacia adelante. Ya no te está pidiendo que abandones el ecosistema npm, te está dejando usarlo mientras ofrece algo mejor para código nuevo.\n\nEse es un pitch mucho más fuerte que \"somos X% más rápidos sirviendo respuestas HTTP vacías.\""},{"heading":"Preguntas frecuentes","level":2,"url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#questions","content":""},{"heading":"¿Puedo usar cualquier paquete npm con Deno 2?","level":3,"url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#question-1","content":"La mayoría de los paquetes funcionan out of the box con specifiers npm:. Paquetes que dependen de addons nativos (node-gyp), scripts postinstall, o APIs internas específicas de Node.js pueden necesitar el flag --node-modules-dir o directamente no funcionar. En la práctica, alrededor del 95% de los top 1000 paquetes npm funcionan sin problemas."},{"heading":"¿Deno es más rápido que Node.js?","level":3,"url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#question-2","content":"Depende del workload. Para servir HTTP, el servidor basado en Hyper de Deno es 15-20% más rápido. Para trabajo CPU-bound, son esencialmente idénticos (ambos usan V8). Para tiempo de startup, Deno es más lento por el type-checking de TypeScript (a menos que uses --no-check). Bun es más rápido que ambos para startup."},{"heading":"¿Debería migrar mi proyecto Node.js a Deno 2?","level":3,"url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#question-3","content":"Solo si valorás el TypeScript built-in de Deno, el modelo de permisos, o el runtime edge de Deno Deploy. Para proyectos Node.js existentes con pipelines de build complejos, el costo de migración raramente justifica los beneficios. Para proyectos nuevos, Deno 2 es un contendiente serio."},{"heading":"Tips","level":2,"url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#article-tips","content":"1. **Resolución de npm specifiers:** Deno 2 resuelve specifiers ` npm: ` a través de un cache global en ` $DENO_DIR/npm/ `, no en ` node_modules/ `. Si un paquete usa scripts postinstall (addons nativos), se necesita el flag ` --node-modules-dir ` para crear node\\_modules local. Verificar con ` deno info npm:nombre-paquete ` para ver el grafo de resolución antes de depurar problemas de imports.\n2. **Hidratación parcial de Fresh 2.0:** Fresh usa señales de Preact para reactividad en islands. Envolver componentes interactivos con ` island() `, todo fuera de los islands envía cero JS al cliente. La clave: utilizar componentes ` <Partial> ` para actualizar secciones del DOM sin navegación completa de página. Esto te da transiciones estilo SPA con arquitectura MPA.\n3. **Reutilización de V8 isolates en Deno Deploy:** Deno Deploy mantiene V8 isolates calientes entre requests. El primer request a un isolate frío tarda ~50ms, los siguientes reutilizan el isolate con ~1ms de overhead. Estructurar el código a nivel de módulo para que corra una vez (conexiones a DB, parsing de config) ya que persiste entre requests dentro del mismo ciclo de vida del isolate.\n4. **Deno.serve() vs node:http en performance:** ` Deno.serve() ` usa Hyper (Rust) por debajo, bypaseando V8 para el manejo TCP. Para la capa HTTP específicamente, benchmarkea 15-20% más rápido que ` node:http ` de Node. Pero si tu handler es CPU-bound (rendering de templates, serialización JSON), la diferencia de runtime desaparece porque ambos pasan 95%+ del tiempo en V8.\n\n- deno\n- node-js\n- bun\n- fresh-framework\n- javascript-runtimes\n- typescript\n- performance\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)"}],"links":[{"text":"English","url":"https://lucioduran.com/blog/deno-2-node-compat-fresh-framework"},{"text":"Deno","url":"https://deno.com/"},{"text":"Fresh","url":"https://fresh.deno.dev/"},{"text":"Volver al Blog","url":"https://lucioduran.com/es/blog"}],"structuredData":[{"@context":"https://schema.org","@graph":[{"@type":"BlogPosting","@id":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework#article","mainEntityOfPage":{"@type":"WebPage","@id":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework"},"headline":"Deno 2: Compatibilidad Total con Node, Fresh 2.0 y Benchmarks Comparativos Contra Node 22 y Bun","description":"Una evaluación técnica honesta de la capa de compatibilidad Node.js de Deno 2, npm specifiers, la arquitectura de islands de Fresh 2.0, y benchmarks de performance que cuentan una historia más matizada de lo que sugiere el marketing.","datePublished":"2025-11-09","author":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"publisher":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"image":"https://lucioduran.com/favicons/og-default.png","url":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework","inLanguage":"es","isPartOf":{"@id":"https://lucioduran.com/blog#blog"},"articleSection":"JavaScript Runtimes","keywords":"deno, node-js, bun, fresh-framework, javascript-runtimes, typescript, performance","wordCount":1425,"timeRequired":"PT8M","workTranslation":[{"@type":"BlogPosting","inLanguage":"en","url":"https://lucioduran.com/blog/deno-2-node-compat-fresh-framework"}]},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Inicio","item":"https://lucioduran.com/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://lucioduran.com/es/blog"},{"@type":"ListItem","position":3,"name":"Deno 2: Compatibilidad Total con Node, Fresh 2.0 y Benchmarks Comparativos Contra Node 22 y Bun","item":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework"}]},{"@type":"FAQPage","mainEntity":[{"@type":"Question","name":"¿Puedo usar cualquier paquete npm con Deno 2?","acceptedAnswer":{"@type":"Answer","text":"La mayoría de los paquetes funcionan out of the box con specifiers npm:. Paquetes que dependen de addons nativos (node-gyp), scripts postinstall, o APIs internas específicas de Node.js pueden necesitar el flag --node-modules-dir o directamente no funcionar. En la práctica, alrededor del 95% de los top 1000 paquetes npm funcionan sin problemas."}},{"@type":"Question","name":"¿Deno es más rápido que Node.js?","acceptedAnswer":{"@type":"Answer","text":"Depende del workload. Para servir HTTP, el servidor basado en Hyper de Deno es 15-20% más rápido. Para trabajo CPU-bound, son esencialmente idénticos (ambos usan V8). Para tiempo de startup, Deno es más lento por el type-checking de TypeScript (a menos que uses --no-check). Bun es más rápido que ambos para startup."}},{"@type":"Question","name":"¿Debería migrar mi proyecto Node.js a Deno 2?","acceptedAnswer":{"@type":"Answer","text":"Solo si valorás el TypeScript built-in de Deno, el modelo de permisos, o el runtime edge de Deno Deploy. Para proyectos Node.js existentes con pipelines de build complejos, el costo de migración raramente justifica los beneficios. Para proyectos nuevos, Deno 2 es un contendiente serio."}}]}]}],"contentHash":"22db2eb50ca91c05170198efe83cf8ea1d35afa4eb9104f500029306820c0185","provenance":{"method":"Deterministic serialization of the server-rendered main element","source":"https://lucioduran.com/es/blog/deno-2-node-compat-fresh-framework","hashAlgorithm":"sha256","hashScope":"JSON document fields before contentHash and provenance","sourceHTMLBytes":68006,"contentBytes":13476}}
