Renderização do Lado do Servidor (Runes) no Svelte 5
O que esta skill cobre
"SSR" significa que cada requisição é renderizada em HTML por um servidor (Node, Bun, serverless ou edge) antes do navegador recebê-la; os mesmos componentes então hidratam em um aplicativo interativo. Diferente de uma build apenas estática, existe um servidor real no momento da requisição, então +page.server.js, +layout.server.js, +server.js, form actions e hooks.server.js são permitidos — e é onde os dados e segredos pertencem.
A parte das runes é o que torna o SSR complicado: cada componente executa duas vezes, uma no servidor e uma no navegador. Código que funciona bem em um app apenas cliente quebra aqui:
$effectnunca executa no servidor, então qualquer estado que ele preenche está faltando no HTML do servidor.$stateno nível do módulo em arquivos.svelte.tsé criado uma vez por processo do servidor e compartilhado entre todas as requisições dos usuários.- Valores calculados a partir de
Date.now(),Math.random()oulocalStoragedurante a renderização diferem entre servidor e cliente, causando incompatibilidades de hidratação.
Essas três falhas são responsáveis pela maioria dos bugs de SSR em código com runes, e cada uma tem um pequeno padrão correto abaixo.
Passo 1 — Escolha a forma do projeto
| Necessidade | Use |
|---|---|
| App completo: rotas, carregamento de dados do servidor, formulários, autenticação, streaming | SvelteKit com adapter-auto (Vercel/Netlify/Cloudflare auto-detectam), adapter-node (auto-hospedado) ou um adaptador de plataforma. Este é o padrão; use a menos que haja um motivo para não usar. |
| Uma árvore de componentes Svelte dentro de um backend existente (Express/Hono/Fastify/Bun) | Vite + Svelte 5 puro: render() de svelte/server no servidor, hydrate() no navegador. Você é responsável por roteamento, cabeçalhos e manipulação de requisições. |
| Conteúdo majoritariamente estático com algumas rotas dinâmicas | SvelteKit com export const prerender = true por rota (ou 'auto' para pré-renderizar páginas populares e renderizar no servidor as páginas de cauda longa). As rotas dinâmicas ainda seguem todas as regras aqui. |
Não defina export const ssr = false para "corrigir" um bug de SSR — isso transforma a rota em uma casca renderizada no cliente (um SPA), perdendo SEO e a primeira pintura. É legítimo apenas para páginas que genuinamente não podem ser renderizadas sem globais do navegador. csr = false é a opção oposta: HTML do servidor sem hidratação, para páginas que não precisam de interatividade.
As etapas de configuração para ambos os caminhos, além de adaptadores e opções de página, estão em references/ssr-build.md. Leia-o antes de gerar svelte.config.js / vite.config.js, porque os nomes das opções e o comportamento dos adaptadores diferem entre versões e é fácil errar sutilmente.
Passo 2 — Saiba quais runnes executam no servidor
Esta tabela é o núcleo da skill:
| Rune | Durante SSR | Notas |
|---|---|---|
$state(v) | Executa | O valor inicial é renderizado. Mutá-lo durante a renderização não re-renderiza HTML já emitido — SSR é uma única passada. |
$state.raw(v) | Executa | Sem proxy. Bom para grandes cargas de data que você substitui por completo em vez de mutar. |
$derived(expr) | Executa | Avaliado como parte da renderização, então sua saída vai para o HTML. Mantenha puro e livre de APIs exclusivas do navegador. |
$effect / $effect.pre | Nunca | Apenas no navegador. Estado que ele define está ausente do HTML SSR (aparece após hidratação). Isso não é um erro de hidratação, mas é HTML vazio e mudança de layout visível. |
$effect.root(fn) | Nunca | O mesmo. Não construa estado visível no servidor dentro dele. |
$props() / $bindable() | Executa | Os dados carregados chegam como props; os valores iniciais renderizam. |
$props.id() | Executa | ID único seguro para SSR, consistente entre servidor e cliente (Svelte ≥ 5.20). Use para links for/aria-labelledby. |
$inspect(v) | Apenas dev | Nenhuma saída SSR. |
$state.snapshot(v) | Executa | Use antes de passar estado para uma API não-Svelte ou biblioteca do servidor. |
$effect.tracking() | false | O compilador do servidor o substitui pelo literal false; não há contexto de rastreamento durante SSR. |
O bug clássico de SSR que esta tabela previne:
<!-- ❌ vazio no HTML do servidor, depois aparece após hidratação -->
<script>
let user = $state();
$effect(() => { user = getUser(); });
</script>
<div>{user?.name}</div>
A correção é quase sempre a mesma: calcule valores visíveis no SSR em $derived, ou busque-os em load e passe como props. Mantenha $effect para coisas que só existem no navegador (canvas, observadores, analytics, sincronização de armazenamento).
Leia references/runes-ssr.md para o comportamento completo de cada rune no servidor, padrões de valor seguros para hidratação e o status experimental de SSR assíncrono.
Passo 3 — Mantenha o estado por requisição, nunca global do módulo
// ❌ um único objeto compartilhado para todo o processo do servidor — o usuário A pode ver dados do usuário B
// src/lib/user.svelte.ts
export const user = $state({ name: '' });
A correção canônica é retornar dados por requisição de load e passá-los como props; quando muitos componentes aninhados precisam deles, coloque-os em contexto com um getter:
<!-- +layout.svelte -->
<script>
import { setContext } from 'svelte';
let { data } = $props();
setContext('user', () => data.user);
</script>
<!-- qualquer filho -->
<script>
import { getContext } from 'svelte';
const user = getContext('user');
</script>
<p>Olá {user().name}</p>
O contexto é anexado à árvore de componentes, e cada requisição SSR tem sua própria árvore, então não pode vazar entre usuários. Passe uma função (ou objeto com getters) para setContext, não o valor em si, ou a reatividade é perdida. Dados por requisição que muitas funções do servidor precisam podem viver em event.locals, preenchidos em hooks.server.ts.
Estado no nível do módulo ainda é aceitável para estado de aplicativo apenas cliente que nenhum dado de usuário toca (tema, feature flags), desde que nada renderize valores personalizados a partir dele no servidor.
Passo 4 — Trate a hidratação como um contrato
A hidratação reproduz o HTML do servidor; a primeira renderização do cliente deve corresponder a ele. Qualquer valor diferente causa um hydration_mismatch, que é deliberadamente não suprimível — corrija o código, não o aviso.
- Renderize deterministicamente: sem
Date.now(), sem formatação comnew Date(), semMath.random(), semcrypto.randomUUID(), sem leituras delocalStorage/window/navigatorno caminho de renderização. - Gere IDs com
$props.id(), não com aleatoriedade ou contadores de módulo. - Formate datas no servidor em
loade passe strings, ou sempre passe umtimeZoneexplícito — os fusos do servidor e cliente diferem. - Valores apenas para cliente (armazenamento, media queries, geolocalização): comece com um padrão seguro para o servidor e preencha dentro de
$effect/onMount, ou feche a marcação com uma flagmountede um fallback estável no servidor.
<script>
import { browser } from '$app/environment';
let theme = $state('light');
$effect(() => {
if (!browser) return;
theme = localStorage.getItem('theme') ?? 'light'; // após montagem, então a hidratação corresponde
});
</script>
Em SSR puro do Svelte (sem SvelteKit), $app/environment não está disponível — use typeof window !== 'undefined' dentro de um efeito, nunca no caminho de renderização.
Passo 5 — Carregue e transmita dados no servidor
+page.server.ts/+layout.server.tspara bancos de dados, arquivos e env privado;+page.tsuniversal para APIs públicas que podem executar em qualquer lugar.loaddo servidor deve retornar dados que sobrevivam à serialização devalue: JSON maisDate,Map,Set,BigInt,RegExpe referências cíclicas. Sem funções ou instâncias de classe.- Dados lentos e não essenciais: retorne a promise e use
{#await}na página — promises aninhadas transmitem à medida que resolvem. Aguarde dados essenciais antes de retornar para que estejam na primeira pintura. - Use
event.fetchdentro deload: ele herda cookies da requisição e inclui na página as respostas para hidratação em vez de buscá-las novamente. - Após navegação,
dataé atualizado sem recriar o componente: derive dele ($derived) em vez de copiá-lo para locais não reativos.
SSR assíncrono (await em componentes) e funções remotas ainda são experimentais a partir de Svelte 5.57 / SvelteKit 2.7x (Setembro 2026). Não habilite compilerOptions.experimental.async em código de produção a menos que eu peça explicitamente; o status exato e a opção de ativação estão em references/runes-ssr.md.
Passo 6 — Aplique as regras de segurança do SSR
- Segredos permanecem privados.
$env/static/private/$env/dynamic/privatesão apenas do servidor; apenas variáveis prefixadas comPUBLIC_chegam ao navegador. Importações de$lib/server/e*.server.jsgeram erro se um componente as puxar. - Tudo o que é retornado de
loadestá embutido no payload HTML — todo ele, não apenas os campos que você renderiza. Nunca retorne hashes de senha, tokens ou campos internos que você não precisa no cliente. - CSP com nonces. Configure
kit.csp; o SvelteKit injeta um nonce em seus próprios scripts inline, e%sveltekit.nonce%cobre tags manuais emapp.html. Um servidor real pode enviar cabeçalhos CSP reais — use isso em vez de uma tag<meta>mais fraca. {@html}ainda é XSS. Renderizado no servidor não significa sanitizado. Sanitize HTML não confiável com DOMPurify imediatamente antes de renderizar.- Autenticação pertence a
hooks.server.ts(preenchalocals) mais verificações no nível da rota. Funçõesloadde layout não executam em toda navegação do lado do cliente. - Proteção CSRF está ativada por padrão para envios de formulário; deixe-a ativada, e verifique a origem você mesmo em qualquer endpoint de mutação
+server.jspersonalizado. - Cookies de sessão:
httpOnly,secureem produção,sameSite: 'lax'no mínimo. fetchdo lado do servidor de URLs fornecidas pelo usuário é SSRF. Faça allowlist de hosts; bloqueie IPs privados e endpoints de metadados da nuvem.
Lista de verificação completa e código: references/security.md.
Revisando um aplicativo existente
Quando a tarefa é auditar ou depurar em vez de construir, percorra references/ssr-review.md: verificações de modo de renderização, runes invisíveis no servidor, perigos de hidratação, estado com escopo de requisição, exposição de payload, buscas de segurança e exercícios de falha. Relate descobertas como arquivo:linha com a falha de produção que cada uma causa.
Arquivos de referência
references/runes-ssr.md— comportamento de cada rune no servidor, padrões de segurança para hidratação, estado com escopo de requisição,render+hydratedosvelte/serverpuro, e o status experimental de SSR assíncrono / funções remotas.references/ssr-build.md— configuração do SvelteKit e adaptadores, opções de página (ssr/csr/prerender), hooks, form actions, streaming, Vite SSR puro e notas de implantação.references/ssr-review.md— lista de verificação de auditoria para aplicativos SSR existentes: modos de renderização, runes invisíveis no servidor, perigos de hidratação, escopo de estado, exposição de payload, buscas de segurança e exercícios de falha.references/security.md— segredos e variáveis de ambiente, CSP nonces, XSS, CSRF, manipulação de autenticação/sessão, SSRF, vazamento de erro e uma lista de verificação pré-implantação.assets/— arquivos iniciais copiáveis:svelte.config.js(SvelteKit com CSP),hooks.server.ts(autenticação mais cabeçalhos de segurança) eindex.html/entry-server.js/entry-client.jspara Vite SSR puro (o trio é construído em CI portests/plain-ssr).