Communitygithub.com

agentmemoir/skills

Construye, migra o revisa aplicaciones Svelte 5 renderizadas en servidor usando runes ($state, $derived, $effect, $props, $bindable) con SvelteKit SSR (adapter-auto/node/cloudflare/vercel/netlify) o Vite SSR plano usando svelte/server render() y hydrate().

¿Qué es skills?

Esta habilidad te guía en la construcción, migración y auditoría de aplicaciones Svelte 5 con renderizado del lado del servidor (SSR). Cubre tanto SvelteKit con sus adaptadores (Node, Cloudflare, Vercel, Netlify) como Vite SSR plano para incrustar componentes en backends existentes (Express, Hono, Fastify). Se enfoca en los desafíos específicos que introducen las runes en SSR: `$state` compartido a nivel de módulo entre peticiones, `$effect` que nunca se ejecuta en el servidor, y valores no deterministas que causan errores de hidratación. Proporciona patrones correctos para cada uno, como usar `$derived` para valores visibles en SSR o devolver datos desde `load`. Además, incluye reglas de seguridad: secreto en variables de entorno, CSP con nonces, protección CSRF y prevención de SSRF. La habilidad referencia archivos complementarios que detallan el comportamiento de cada rune, la configuración del proyecto y checklist de auditoría.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/agentmemoir/skills/tree/HEAD/skills/frontend/svelte5-ssr-runes

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Renderizado del Lado del Servidor (SSR) en Svelte 5 con Runes

Qué cubre esta habilidad

"SSR" significa que cada petición se renderiza a HTML en un servidor (Node, Bun, serverless o edge) antes de que el navegador lo reciba; los mismos componentes luego se hidratan convirtiéndose en una aplicación interactiva. A diferencia de una compilación solo estática, existe un servidor real en el momento de la petición, por lo que +page.server.js, +layout.server.js, +server.js, acciones de formulario y hooks.server.js están permitidos — y son donde deben estar los datos y secretos.

La parte de las runes es lo que hace complicado el SSR: cada componente se ejecuta dos veces, una en el servidor y otra en el navegador. El código que funciona bien en una aplicación solo-cliente aquí se rompe:

  • $effect nunca se ejecuta en el servidor, por lo que cualquier estado que inicialice falta en el HTML del servidor.
  • El $state a nivel de módulo en archivos .svelte.ts se crea una vez por proceso del servidor y se comparte entre todas las peticiones de usuarios.
  • Los valores calculados a partir de Date.now(), Math.random() o localStorage durante el renderizado difieren entre servidor y cliente, causando errores de hidratación.

Esos tres fallos explican la mayoría de los bugs de SSR en código con runes, y cada uno tiene un patrón correcto y pequeño más abajo.

Paso 1 — Elige la forma del proyecto

NecesidadUsa
Aplicación completa: rutas, carga de datos en servidor, formularios, autenticación, streamingSvelteKit con adapter-auto (detección automática de Vercel/Netlify/Cloudflare), adapter-node (auto-alojado) o un adaptador de plataforma. Esta es la opción por defecto; úsala a menos que haya una razón para no hacerlo.
Un árbol de componentes Svelte dentro de un backend existente (Express/Hono/Fastify/Bun)Vite + Svelte 5 plano: render() de svelte/server en el servidor, hydrate() en el navegador. Tú controlas enrutamiento, cabeceras y manejo de peticiones.
Contenido mayormente estático con algunas rutas dinámicasSvelteKit con export const prerender = true por ruta (o 'auto' para prerenderizar páginas populares y renderizar en servidor la larga cola). Las rutas dinámicas aún siguen todas las reglas aquí.

No establezcas export const ssr = false para "arreglar" un bug de SSR — eso convierte la ruta en un shell renderizado en cliente (una SPA), perdiendo SEO y la primera pintura. Es legítimo solo para páginas que genuinamente no pueden renderizarse sin globales del navegador. csr = false es el interruptor opuesto: HTML de servidor sin hidratación, para páginas que no necesitan interactividad.

Los pasos de configuración para ambos caminos, más los adaptadores y opciones de página, están en references/ssr-build.md. Léelo antes de generar svelte.config.js / vite.config.js, porque los nombres de las opciones y el comportamiento de los adaptadores difieren entre versiones y es fácil equivocarse sutilmente.

Paso 2 — Conoce qué runes se ejecutan en el servidor

Esta tabla es el núcleo de la habilidad:

RuneDurante SSRNotas
$state(v)Se ejecutaEl valor inicial se renderiza. Mutarlo durante el renderizado no vuelve a renderizar el HTML ya emitido — SSR es un solo pase.
$state.raw(v)Se ejecutaSin proxy. Bueno para cargas de datos grandes que reemplazas completas en lugar de mutar.
$derived(expr)Se ejecutaSe evalúa como parte del renderizado, por lo que su salida termina en el HTML. Mantenlo puro y libre de APIs solo del navegador.
$effect / $effect.preNuncaSolo navegador. El estado que establece está ausente del HTML del SSR (aparece después de la hidratación). Esto no es un error de hidratación, pero es HTML vacío y cambio de diseño visible.
$effect.root(fn)NuncaIgual. No construyas estado visible en el servidor dentro de él.
$props() / $bindable()Se ejecutaLos datos cargados llegan como props; los valores iniciales se renderizan.
$props.id()Se ejecutaID único seguro para SSR, consistente entre servidor y cliente (Svelte ≥ 5.20). Úsalo para enlaces for/aria-labelledby.
$inspect(v)Solo desarrolloSin salida en SSR.
$state.snapshot(v)Se ejecutaÚsalo antes de pasar el estado a una API no-Svelte o librería del servidor.
$effect.tracking()falseEl compilador del servidor lo reemplaza con el literal false; no hay contexto de seguimiento durante SSR.

El bug clásico de SSR que esta tabla previene:

<!-- ❌ vacío en el HTML del servidor, luego aparece después de la hidratación -->
<script>
  let user = $state();
  $effect(() => { user = getUser(); });
</script>
<div>{user?.name}</div>

La solución es casi siempre la misma: calcula los valores visibles en SSR con $derived, u obténgelos en load y pásalos como props. Mantén $effect para cosas que solo existen en el navegador (canvas, observadores, analítica, sincronización de almacenamiento).

Lee references/runes-ssr.md para el comportamiento completo rune por rune en servidor, patrones de valores seguros para hidratación y el estado experimental del SSR asíncrono.

Paso 3 — Mantén el estado por petición, nunca global de módulo

// ❌ un objeto compartido para todo el proceso del servidor — el usuario A puede ver los datos del usuario B
// src/lib/user.svelte.ts
export const user = $state({ name: '' });

La solución canónica es devolver datos por petición desde load y pasarlos como props; cuando muchos componentes anidados lo necesitan, ponlo en contexto con un getter:

<!-- +layout.svelte -->
<script>
  import { setContext } from 'svelte';
  let { data } = $props();
  setContext('user', () => data.user);
</script>
<!-- cualquier hijo -->
<script>
  import { getContext } from 'svelte';
  const user = getContext('user');
</script>
<p>Hola {user().name}</p>

El contexto está vinculado al árbol de componentes, y cada petición SSR tiene su propio árbol, por lo que no puede filtrarse entre usuarios. Pasa una función (u objeto con getters) a setContext, no el valor en sí, o se pierde la reactividad. Los datos por petición que muchas funciones del servidor necesitan pueden vivir en event.locals, poblados en hooks.server.ts.

El estado a nivel de módulo sigue siendo válido para estado de aplicación solo-cliente que ningún dato de usuario toca (tema, características), siempre que nada renderizado en servidor muestre valores personalizados desde él.

Paso 4 — Trata la hidratación como un contrato

La hidratación reproduce el HTML del servidor; el primer renderizado del cliente debe coincidir. Cualquier valor que difiera causa un hydration_mismatch, que deliberadamente no se puede suprimir — arregla el código, no la advertencia.

  • Renderiza de manera determinista: nada de Date.now(), nada de formato new Date(), nada de Math.random(), nada de crypto.randomUUID(), nada de lecturas de localStorage/window/navigator en la ruta de renderizado.
  • Genera IDs con $props.id(), no con aleatoriedad o contadores de módulo.
  • Formatea las fechas en el servidor dentro de load y pasa cadenas, o pasa siempre una timeZone explícita — las zonas horarias del servidor y del cliente difieren.
  • Valores solo de cliente (almacenamiento, media queries, geolocalización): empieza con un valor predeterminado seguro para servidor y completa dentro de $effect/onMount, o protege el marcado con un flag mounted con un fallback estable de servidor.
<script>
  import { browser } from '$app/environment';

  let theme = $state('light');
  $effect(() => {
    if (!browser) return;
    theme = localStorage.getItem('theme') ?? 'light'; // después del montaje, para que la hidratación coincida
  });
</script>

En SSR plano de Svelte (sin SvelteKit), $app/environment no está disponible — usa typeof window !== 'undefined' dentro de un efecto, nunca en la ruta de renderizado.

Paso 5 — Carga y transmite datos en el servidor

  • +page.server.ts / +layout.server.ts para bases de datos, archivos y env privados; +page.ts universal para APIs públicas que pueden ejecutarse en cualquier lugar.
  • El load del servidor debe devolver datos que sobrevivan la serialización de devalue: JSON más Date, Map, Set, BigInt, RegExp y referencias cíclicas. Nada de funciones o instancias de clase.
  • Datos lentos y no esenciales: devuelve la promesa y usa {#await} en la página — las promesas anidadas se transmiten a medida que se resuelven. Espera los datos esenciales antes de devolverlos para que estén en la primera pintura.
  • Usa event.fetch dentro de load: hereda las cookies de la petición y en línea las respuestas para la hidratación en lugar de volver a buscarlas.
  • Después de la navegación, data se actualiza sin recrear el componente: deriva de él ($derived) en lugar de copiarlo en locales no reactivos.

El SSR asíncrono (await en componentes) y las funciones remotas siguen siendo experimentales a partir de Svelte 5.57 / SvelteKit 2.7x (septiembre de 2026). No habilites compilerOptions.experimental.async en código de producción a menos que yo lo pida explícitamente; el estado exacto y la activación están en references/runes-ssr.md.

Paso 6 — Aplica las reglas de seguridad de SSR

  1. Los secretos se mantienen privados. $env/static/private / $env/dynamic/private son solo de servidor; solo las variables con prefijo PUBLIC_ llegan al navegador. Las importaciones desde $lib/server/ y *.server.js lanzan un error si un componente las incluye.
  2. Todo lo que se devuelve desde load se incrusta en el payload HTML — todo, no solo los campos que renderizas. Nunca devuelvas hashes de contraseñas, tokens o campos internos que no necesites en el cliente.
  3. CSP con nonces. Configura kit.csp; SvelteKit inyecta un nonce en sus propios scripts en línea, y %sveltekit.nonce% cubre las etiquetas manuales en app.html. Un servidor real puede enviar cabeceras CSP reales — úsalo en lugar de una etiqueta <meta> más débil.
  4. {@html} sigue siendo XSS. Renderizado en servidor no significa sanitizado. Sanitiza HTML no confiable con DOMPurify justo antes del renderizado.
  5. La autenticación pertenece a hooks.server.ts (pobla locals) más comprobaciones a nivel de ruta. Las funciones load de layout no se ejecutan en cada navegación del lado del cliente.
  6. La protección CSRF está activada por defecto para envíos de formularios; déjalo activado y verifica el origen tú mismo en cualquier endpoint de mutación +server.js personalizado.
  7. Cookies de sesión: httpOnly, secure en producción, sameSite: 'lax' como mínimo.
  8. La fetch del lado del servidor de URLs proporcionadas por el usuario es SSRF. Lista blanca de hosts; bloquea IPs privadas y endpoints de metadatos de la nube.

Lista de verificación completa y código: references/security.md.

Revisando una aplicación existente

Cuando la tarea sea auditar o depurar en lugar de construir, trabaja a través de references/ssr-review.md: comprobaciones de modo de renderizado, runes invisibles en servidor, riesgos de hidratación, estado por petición, exposición de payload, búsquedas de seguridad y simulacros de fallos. Reporta hallazgos como archivo:línea con el fallo de producción que causa cada uno.

Archivos de referencia

  • references/runes-ssr.md — comportamiento rune por rune en servidor, patrones de seguridad de hidratación, estado por petición, svelte/server render + hydrate plano, y estado experimental del SSR asíncrono / funciones remotas.
  • references/ssr-build.md — configuración de SvelteKit y adaptadores, opciones de página (ssr/csr/prerender), hooks, acciones de formulario, streaming, Vite SSR plano y notas de despliegue.
  • references/ssr-review.md — lista de verificación de auditoría para aplicaciones SSR existentes: modos de renderizado, runes invisibles en servidor, riesgos de hidratación, ámbito de estado, exposición de payload, búsquedas de seguridad y simulacros de fallos.
  • references/security.md — secretos y variables de entorno, nonces CSP, XSS, CSRF, manejo de autenticación/sesión, SSRF, fuga de errores y lista de verificación previa al lanzamiento.
  • assets/ — archivos de inicio copiables: svelte.config.js (SvelteKit con CSP), hooks.server.ts (autenticación más cabeceras de seguridad), e index.html / entry-server.js / entry-client.js para Vite SSR plano (el trío se construye en CI mediante tests/plain-ssr).

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

Skills relacionados