SSR y SSG
EmberKit ofrece cuatro modos de renderizado controlados por emberkit.config.ts. El plugin de Vite gestiona SSR durante el desarrollo; el CLI genera bundles de cliente y servidor y puede pre-renderizar rutas estáticas en build time.
Modos de renderizado
| Modo | Salida del build | Runtime | Ideal para |
|---|---|---|---|
hybrid default | Bundle cliente + servidor + ssr-manifest.json; rutas estáticas pre-renderizadas a archivos HTML | HTML pre-renderizado para rutas estáticas; SSR para rutas dinámicas ([param]) | La mayoría de apps (docs, marketing + páginas dinámicas) |
static | Igual que hybrid, pero todas las rutas no dinámicas se pre-renderizan en build time | Solo archivos estáticos (no requiere servidor) | Blogs, sitios de marketing, documentación |
ssr | Bundle cliente + servidor + manifest; sin paso de pre-render | Cada petición HTML se renderiza en el servidor | Contenido personalizado o que cambia con frecuencia |
spa | Solo bundle de cliente | Routing en cliente; el dev server omite middleware SSR | Dashboards, paneles de admin, apps totalmente client-side |
Cómo se clasifican las rutas
Durante emberkit build, cada archivo bajo src/routes/ se convierte en una entrada del manifest:
- Ruta estática — la ruta no tiene segmentos dinámicos (p. ej.
/,/about,/docs/installation) - Ruta dinámica — la ruta contiene un parámetro de segmento (p. ej.
/blog/:slugdesde[slug].tsx)
En modo hybrid, las rutas estáticas se escriben en dist/.../index.html en build time. Las rutas dinámicas se renderizan en request time vía dist/server/entry-server.js.
Configuración
Crea emberkit.config.ts en la raíz del proyecto (o ejecuta emberkit generate config):
import { defineConfig } from '@emberkit/core';
export default defineConfig({
mode: 'hybrid', // 'static' | 'ssr' | 'spa' | 'hybrid'
server: {
port: 3000,
host: 'localhost',
},
build: {
outDir: 'dist',
target: 'esnext',
},
// Opcional: fusionar opciones extra de Vite
vite: {
plugins: [/* emberkitVitePlugin(), tailwindcss(), … */],
},
});
Registra el plugin de Vite en vite.config.ts (incluido en plantillas del CLI):
import { defineConfig } from 'vite';
import { emberkitVitePlugin } from '@emberkit/core/vite-plugin';
export default defineConfig({
plugins: [emberkitVitePlugin()],
esbuild: {
jsxImportSource: '@emberkit/core',
},
});
El plugin lee emberkit.config.ts para mode, routeDir, opciones de markdown y compresión. Si no existe archivo de config, hybrid es el valor por defecto.
Desarrollo
pnpm dev # emberkit dev — Vite + HMR
Para todos los modos excepto spa, el dev server ejecuta middleware SSR en navegaciones HTML:
- Omite assets, HMR y peticiones no HTML
- Carga la entrada SSR virtual y llama a
render(url, server) - Inyecta el markup renderizado en
index.htmlen#app(o<body id="app">)
Consulta el código fuente de la página en el navegador para confirmar HTML renderizado en servidor antes de la hidratación.
Build de producción
pnpm build # emberkit build
| Modo | Pasos |
|---|---|
spa | Bundle cliente → dist/ |
ssr | Bundle cliente → bundle SSR → ssr-manifest.json |
hybrid | Cliente → SSR → manifest → pre-render de rutas estáticas |
static | Cliente → SSR → manifest → pre-render de todas las rutas no dinámicas |
Artefactos del build:
dist/
├── index.html # Shell cliente (y / para home pre-renderizada)
├── assets/ # JS, CSS, chunks con hash
├── ssr-manifest.json # mode, routes[], flags isStatic
├── server/
│ └── entry-server.js # Renderer SSR (salvo que proporciones src/entry-server.tsx)
└── about/
└── index.html # Ruta estática pre-renderizada (hybrid/static)
Preview y servidores de producción
pnpm preview # emberkit preview — comprobación local rápida (puerto 4173 por defecto)
pnpm serve # emberkit serve — servidor de producción usando ssr-manifest.json
Preview sirve archivos estáticos y llama a entry-server.js para modos SSR.
Serve resuelve peticiones en orden: asset estático → path/index.html pre-renderizado → SSR (para modo ssr, o rutas dinámicas en hybrid) → fallback SPA a index.html raíz.
Entrada de servidor personalizada
Añade src/entry-server.ts o src/entry-server.tsx para reemplazar el shim SSR generado. Exporta render(url) devolviendo { html, status } o una cadena HTML.
Metadatos de ruta (head SSR integrado)
La entrada de servidor por defecto inyecta exports opcionales de ruta en <head>:
// src/routes/about.tsx
export const metadata = {
title: 'About — My App',
description: 'Learn more about us',
};
export default function AboutPage() {
return <main><h1>About</h1></main>;
}
Para tags declarativos de head desde componentes, usa el componente <Head> — actualiza el DOM en cliente y registra tags durante el render. Consulta Head para detalles.
Páginas de error personalizadas
Colócalas en src/routes/ (no anidadas bajo un segmento):
| Archivo | Cuándo se usa |
|---|---|
404.tsx | Ninguna ruta coincide |
500.tsx | Error no capturado al renderizar una ruta |
Si no existen esos archivos, EmberKit renderiza páginas de error integradas (las mismas que en la navegación del cliente). Las páginas personalizadas reciben pathname en 404 y error en 500.
// src/routes/404.tsx
export default function NotFound() {
return (
<main>
<h1>404</h1>
<p>Page not found</p>
</main>
);
}
Props de rutas dinámicas
SSR pasa params al componente de ruta coincidente:
// src/routes/blog/[slug].tsx
import type { RouteParams } from '@emberkit/core';
export default function Post({ params }: RouteParams<{ slug: string }>) {
return <article><h1>{params.slug}</h1></article>;
}
En el cliente, el runtime también proporciona params, query y request vía RouteParams. Consulta Routing.
APIs HTML de bajo nivel
Estas utilidades viven en @emberkit/core para servidores personalizados y tests:
renderToHTMLString
import { renderToHTMLString } from '@emberkit/core';
const fragment = renderToHTMLString(<Page />);
// Fragmento HTML (solo contenido del body)
renderToHTMLString escapa texto y atributos string para una salida SSR segura.
Registro de head
import { drainHeadContent } from '@emberkit/core';
// Tras renderizar componentes que usan <Head>:
const headTags = drainHeadContent();
Úsalo en un entry-server personalizado cuando necesites soporte completo de <Head> en SSR.
Helpers internos
renderSSR, createHtmlDocument y createStreamingRenderer existen en el paquete core para integraciones avanzadas pero no forman parte de la superficie pública de exportación de @emberkit/core hoy. Prefiere el pipeline del CLI o renderToHTMLString + drainHeadContent para servidores personalizados.
Hidratación tras SSR
El HTML del servidor incluye índices data-ek-bind e ids de handler data-ekclick para regiones interactivas. La entrada de cliente reconecta signals y eventos sin re-renderizar el árbol completo. Detalles: Hidratación.
Rendimiento (por qué la velocidad es el objetivo)
Los modos de renderizado de EmberKit existen para minimizar el tiempo hasta contenido y el trabajo en cliente:
| Técnica | Beneficio de velocidad |
|---|---|
| SSR / pre-render | Los usuarios ven HTML de inmediato; sin shell vacío esperando JS |
Pre-render static / hybrid | Rutas estáticas servidas desde disco o CDN — TTFB muy bajo |
| Hidratación selectiva | Menor parse/ejecución de JS; Time to Interactive más rápido en páginas con mucho contenido |
| Enlace DOM con signals | Las actualizaciones omiten diffing de virtual DOM — solo cambian nodos enlazados |
La velocidad es el primer principio del framework; el peso y los defaults de cero JS lo respaldan. Consulta Introducción.
Despliegue en edge
Despliega dist/ pre-renderizado en cualquier host estático, o ejecuta entry-server.js en Node, Bun o adaptadores edge. Consulta Despliegue en edge.
Próximos pasos
- Hidratación — Interactividad selectiva en cliente
- Routing — Rutas basadas en archivos, layouts, loaders
- Head — Gestión declarativa de
<head> - Despliegue en edge — Cloudflare Workers y Deno