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

ModoSalida del buildRuntimeIdeal para
hybrid defaultBundle cliente + servidor + ssr-manifest.json; rutas estáticas pre-renderizadas a archivos HTMLHTML pre-renderizado para rutas estáticas; SSR para rutas dinámicas ([param])La mayoría de apps (docs, marketing + páginas dinámicas)
staticIgual que hybrid, pero todas las rutas no dinámicas se pre-renderizan en build timeSolo archivos estáticos (no requiere servidor)Blogs, sitios de marketing, documentación
ssrBundle cliente + servidor + manifest; sin paso de pre-renderCada petición HTML se renderiza en el servidorContenido personalizado o que cambia con frecuencia
spaSolo bundle de clienteRouting en cliente; el dev server omite middleware SSRDashboards, 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/:slug desde [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:

  1. Omite assets, HMR y peticiones no HTML
  2. Carga la entrada SSR virtual y llama a render(url, server)
  3. Inyecta el markup renderizado en index.html en #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
ModoPasos
spaBundle cliente → dist/
ssrBundle cliente → bundle SSR → ssr-manifest.json
hybridCliente → SSR → manifest → pre-render de rutas estáticas
staticCliente → 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.

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):

ArchivoCuándo se usa
404.tsxNinguna ruta coincide
500.tsxError 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.

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écnicaBeneficio de velocidad
SSR / pre-renderLos usuarios ven HTML de inmediato; sin shell vacío esperando JS
Pre-render static / hybridRutas estáticas servidas desde disco o CDN — TTFB muy bajo
Hidratación selectivaMenor parse/ejecución de JS; Time to Interactive más rápido en páginas con mucho contenido
Enlace DOM con signalsLas 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