¿Qué hace Svelte 5 Solo-Estático (Runes)?
Qué cubre este skill
"Solo-estático" significa que el entregable es una carpeta con HTML/CSS/JS preconstruidos sin servidor Node/edge en el momento de la solicitud — se puede alojar en GitHub Pages, alojamiento estático de Netlify/Cloudflare/Vercel, S3+CloudFront, o abrir como archivos locales. Esta restricción descarta +page.server.js, +server.js, acciones de formulario, hooks.server.js y cualquier ruta de código que asuma un servidor en ejecución. "Runes" significa que la reactividad de los componentes se escribe con las primitivas $state/$derived/$effect/$props de Svelte 5, no con export let / $: / reactividad implícita de Svelte 4.
Estas dos restricciones interactúan: una compilación estática solo funciona limpiamente si ningún código — incluyendo dentro de $effect — asume que se ejecuta en un servidor por solicitud. Tenlo presente en todo momento; es la fuente de la mayoría de los errores cuando la gente migra una app SvelteKit a adapter-static.
Paso 1 — Elegir la forma correcta del proyecto
Pregunta (o infiere del contexto) si la aplicación necesita enrutamiento basado en archivos, múltiples páginas prerenderizadas o HTML por ruta optimizado para SEO. Esto determina toda la configuración, así que no lo omitas.
| Necesidad | Usar |
|---|---|
| Página única / dashboard / widget / embed, sin enrutamiento | Vite + Svelte simple (npm create vite@latest -- --template svelte-ts). La configuración estática más simple posible: no hay servidor que introducir accidentalmente. |
| Múltiples rutas, cada una prerenderizada a su propio archivo HTML, SEO importa | SvelteKit + @sveltejs/adapter-static con prerender = true. |
| Múltiples rutas, pero SEO no importa (herramienta interna, extensión de Chrome, UI de Electron/Tauri) | SvelteKit + adapter-static en modo SPA (un único shell fallback: 'index.html', enrutamiento del lado del cliente). |
Por defecto, usa Vite+Svelte simple a menos que necesites específicamente enrutamiento; tiene menos posibilidades de mala configuración. Los pasos completos de configuración, archivos de configuración y notas de despliegue para ambas rutas están en references/static-build.md; léelo antes de generar svelte.config.js / vite.config.js de memoria, porque las opciones de prerender/adapter cambian entre versiones de Svelte y es fácil equivocarse sutilmente (por ejemplo, olvidar trailingSlash, o poner ssr = false en una página que también quieres prerenderizar, lo que produce un shell vacío en lugar de HTML real).
Paso 2 — Escribir componentes con runes, no con sintaxis de Svelte 4
Nunca escribas export let, $:, on:click o un store writable() suelto como la forma principal de gestionar el estado del componente en código nuevo de Svelte 5 — estos aún compilan en la mayoría de las configuraciones, pero son patrones heredados; aquí se pide explícitamente runes. El mapeo principal:
| Svelte 4 | Svelte 5 |
|---|---|
let count = 0 (reactivo por estar a nivel superior) | let count = $state(0) |
$: doubled = count * 2 | let doubled = $derived(count * 2) (o $derived.by(() => ...) para lógica multi-sentencia) |
$: { sideEffect(count) } | $effect(() => { sideEffect(count) }) |
export let name | let { name } = $props() |
export let value; bind:value (el hijo muta) | let { value = $bindable() } = $props() |
onMount/onDestroy para setup/teardown solo del DOM | $effect(() => { ...; return () => cleanup(); }) |
<slot name="header"> | {#snippet header()}...{/snippet} + {@render header()} |
store writable()/readable(), $store en componentes | $state(...) en un módulo .svelte.js/.svelte.ts, importado directamente (la reactividad ahora funciona fuera de los componentes) |
Lee references/runes.md antes de escribir código rune no trivial — cubre las partes que la gente suele fallar: $derived debe mantenerse puro (sin efectos secundarios, sin mutar estado dentro), $effect nunca debe escribir en el mismo estado que lee (bucle infinito), $state.raw para datos grandes/congelados que reasignas en lugar de mutar, $effect.root/untrack para estado compartido fuera de los ciclos de vida del componente, y el patrón exacto basado en clases para estado cross-componente/módulo. Errar en estos produce bugs que solo se manifiestan como extraños bucles de re-renderizado o interfaz desactualizada, que son difíciles de diagnosticar después — mejor escribirlo correctamente desde el principio.
Al migrar código existente de Svelte 4, npx sv migrate svelte-5 maneja la reescritura mecánica (esta es la herramienta actual; la documentación antigua puede referenciar svelte-migrate), pero siempre revisa el diff — la migración automatizada no arregla de forma confiable patrones de stores como estado cross-módulo o efectos que deberían haber sido valores derivados.
Paso 3 — Aplicar el checklist de seguridad
El alojamiento estático no significa "sin superficie de ataque" — elimina los vectores de ataque del lado del servidor, pero los del lado del cliente (XSS, fuga de secretos, embeds inseguros de terceros) recaen completamente en ti, ya que no hay capa de servidor que los capture. Antes de finalizar cualquier tarea que toque contenido generado por el usuario, {@html}, variables de entorno o scripts de terceros, consulta references/security.md. Las reglas de mayor impacto, para que no se pasen por alto ni siquiera en una tarea rápida:
- Nunca pases contenido sin desinfectar a
{@html}. Svelte escapa automáticamente todo en las llaves{expression}, pero{@html ...}lo evita por completo — es la fuente más grande de XSS en aplicaciones Svelte. Desinfecta con DOMPurify (o una lista blanca estricta) inmediatamente antes de renderizar, no antes en el pipeline donde podría ser eludido por una edición posterior. - Nunca pongas un secreto real detrás de una variable de entorno con prefijo
VITE_(o con prefijoPUBLIC_de SvelteKit). Vite las incrusta en el bundle JS distribuido en tiempo de compilación — cualquiera puede leerlas desde los archivos compilados. Una aplicación solo-estática no tiene servidor para ocultar un secreto, así que si la aplicación necesita una clave API real, la clave pertenece al servidor del servicio de terceros o detrás de una función serverless/proxy separada que despliegue en otro lugar — indícamelo en lugar de cablear silenciosamente la clave en el código del cliente. - Establece una Content-Security-Policy aunque no haya servidor para enviar cabeceras. Los hosts estáticos aún lo admiten mediante una etiqueta
<meta http-equiv="Content-Security-Policy">enapp.html/index.html, o mediante un archivo de cabeceras específico del host (archivo_headersde Netlify, de Cloudflare Pages, cabeceras devercel.json) — consultareferences/security.mdpara ambos. - Trata cada
href/srcdinámico como controlado por un atacante si puede provenir de entrada del usuario o de una respuesta de API. Svelte no bloquea las URLjavascript:en los enlaceshref— valida/permite por lista blanca el esquema tú mismo. - Ejecuta
npm audit(o equivalente) y fija los archivos de bloqueo antes de publicar — los sitios estáticos siguen enviando todo el código de cada dependencia directamente al navegador, por lo que una dependencia comprometida es un compromiso directo del lado del cliente.
Archivos de referencia
references/runes.md— guía completa rune por rune con patrones correctos/incorrectos, estado compartido basado en clases, tipado TypeScript para props/snippets y la tabla detallada de migración de Svelte 4 a 5.references/static-build.md— configuración de Vite+Svelte simple, configuración de SvelteKit+adapter-static (modos prerender completo y fallback SPA), problemas con trailing-slash/base-path y notas de despliegue por host (GitHub Pages, Netlify, Cloudflare Pages, S3/CloudFront).references/security.md— checklist completo: XSS/{@html}/DOMPurify, CSP para hosts estáticos (etiqueta meta + plantillas_headers), secretos/variables de entorno, enlaces externos seguros, auditoría de dependencias, Trusted Types, sensibilidad de localStorage/sessionStorage y un checklist previo a la publicación.assets/— archivos de inicio copiables:vite.config.ts,svelte.config.js(adapter-static),_headers(plantilla CSP) ySecureHtml.svelte(componente envoltorio de DOMPurify).
Úsalos según sea necesario en lugar de reproducir su contenido de memoria — los nombres de las opciones de configuración y los detalles de las directivas CSP son exactamente el tipo de detalle que se vuelve obsoleto o se recuerda mal.