100x Más Rápido: Caché de Plantillas HTML6 en Bun
La diferencia de velocidad que hace el caché
Las apps web que no cachean sus plantillas pagan el costo en cada petición. Cargan el archivo, parsean la sintaxis, lo convierten en código ejecutable, lo ejecutan, envían el resultado. Una y otra vez, miles de veces al día, haciendo el mismo trabajo desde cero cada vez.
uLearn compila una sola vez al iniciar. Cada petición después de eso simplemente ejecuta la función ya compilada. La diferencia no es del 10% o 50%. Es mil veces más rápido.
Los números: antes y después
Ejecuté las mismas rutas dos veces: una forzando al servidor a recompilar las plantillas en cada petición, otra con el caché de producción habilitado. Estos son los promedios en rutas protegidas (dashboard, páginas de aprendizaje, interfaz de repaso):
Sin caché:
- Respuesta del servidor (TTFB): 548ms
- Carga total de página: 763ms
Con caché:
- Respuesta del servidor (TTFB): 0.55ms
- Carga total de página: 130ms
Eso es 1000x más rápido en respuesta del servidor y 6x más rápido en tiempo total de carga. Mismo hardware, mismos datos, mismas rutas. El único cambio: compilar una vez en lugar de cada vez.
La arquitectura: Elysia en Bun
Bun es el runtime de JavaScript más rápido actualmente. Elysia es el framework más rápido construido sobre él. Juntos son la base: SSR sin hidratación del lado del cliente, tamaño de bundle mínimo, velocidad nativa.
Sobre eso se encuentra HTML6, un motor de plantillas liviano construido para velocidad. Compila plantillas a funciones JavaScript puras, encajando naturalmente en el stack Bun/Elysia.
HTML6 ya es rápido. Compilar y renderizar una página pesada toma alrededor de 500ms, no segundos. El caché evita recompilar en cada petición. Compilar una vez, servir 1000x más rápido.
Cómo funciona el caché de plantillas
La función compile envuelve el compilador de HTML6 con los componentes y pipes de la app:
// html6.service.ts
import html6 from "html6";
import { utilPipes } from "../utils/pipes";
// Carga los 84 componentes HTML una vez al iniciar
var components = await loadComponents();
async function compile(page: string = ""): Promise<Renderer> {
var renderer = html6.compile(page, { pipes: utilPipes, components });
return { render: (data) => renderer.render(data) };
}
Cada controlador guarda en caché el renderer compilado al iniciar:
import { compile } from "../services/html6.service";
class HomeController {
private isProd = process.env.NODE_ENV === "production";
private cache = {
template: null as string | null,
renderer: null as any,
};
private async getRenderer(template: string): Promise<any> {
if (!this.cache.renderer || !this.isProd) {
this.cache.renderer = await compile(template);
}
return this.cache.renderer;
}
public async warmupCache() {
if (this.isProd) {
await this.getRenderer(this.cache.template!);
}
}
// ...
}
// Called at server startup
await homeController.warmupCache();
Carga el archivo de plantilla. Llama a compile() para convertirlo en una función JavaScript. Guarda en memoria. Listo.
Cada petición después de eso salta directo al final:
class HomeController {
// private cache, getRenderer, etc.
async render(context: any) {
const renderer = await this.getRenderer(template);
const renderData = { lang, theme, title };
context.set.headers["Content-Type"] = "text/html";
return renderer.render(renderData);
}
}
Sin parsear. Sin compilar. Solo ejecución. La función ya está ahí, esperando en memoria.
El trade-off: memoria por velocidad
Este enfoque tiene un costo: memoria. El contenedor del servicio web tiene asignados 1024 MB en lugar de los 256 MB usados por otros servicios backend. Al iniciar, pre-compila alrededor de 40 renderers de HTML6 y 84 componentes reutilizables, manteniendo todos en memoria simultáneamente.
En producción, ese caché usa aproximadamente 400 MB. Cuatro veces el tamaño de un microservicio típico en Go. ¿Vale la pena? Absolutamente. Esos 400 MB compran una mejora de 1000x en cada petición. El servidor no trabaja menos, trabaja en un punto completamente diferente del pipeline.
Impacto real: top 5% globalmente
Estos no son benchmarks artificiales. Los números de producción de usuarios reales accediendo al sitio en vivo ponen a uLearn en el top 5% de todos los sitios web globalmente en rendimiento.
El umbral "bueno" de Google para Time to First Byte es menos de 200ms. Las rutas protegidas de uLearn promedian 0.55ms localmente y alrededor de 160ms en producción desde 10,000km de distancia (Buenos Aires a Virginia). Incluso considerando la distancia de red, eso es 26-300x mejor que lo que Google llama excelente.
El edge caching de Cloudflare agrega otra capa. Los assets estáticos se sirven desde la ubicación edge más cercana, reduciendo los round-trips de red y manteniendo tiempos de carga rápidos incluso para usuarios del otro lado del mundo.
El stack
Sin magia. Dos piezas:
Bun + Elysia: Runtime rápido, framework rápido, construido para SSR.
HTML6 + caché: Compilar una vez al iniciar, mantener en memoria, renderizar miles de veces.
Cada capa es rápida. Juntas, omiten trabajo que stacks más lentos repiten en cada petición.
¿Disfrutaste este artículo?
Seguime para más contenido sobre desarrollo web, diseño y tecnología.