DevTools

Cheatsheet Svelte

Framework frontend compilado, leve e ultra performático

Volver a los lenguajes
Svelte
75 tarjetas encontradas
Categorías:
Versiones:

Setup e Estrutura


8 cards
Crear Proyecto (SvelteKit)
npm create svelte@latest mi-app
cd mi-app
npm install
npm run dev

create svelte genera un proyecto SvelteKit completo con routing, SSR y adaptadores. npm run dev inicia el servidor de desarrollo con hot reload en localhost:5173.

Estructura SvelteKit
src/
  routes/        # páginas y API
  lib/           # código compartido
  app.html       # template HTML
static/          # archivos estáticos
svelte.config.js # configuración

src/routes/ define páginas por archivos. src/lib/ guarda componentes y utils reutilizables. svelte.config.js configura adaptadores, preprocesadores y aliases.

Solo Svelte (Vite)
npm create vite@latest mi-app \
  -- --template svelte
cd mi-app
npm install
npm run dev

Template Vite sin SvelteKit — solo Svelte puro. Ideal para SPAs simples, widgets o bibliotecas. Sin routing ni SSR incluidos.

svelte.config.js
import adapter from "@sveltejs/adapter-auto";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";

export default {
  preprocess: vitePreprocess(),
  kit: {
    adapter: adapter(),
    alias: { "@": "./src" }
  }
};

preprocess permite usar TypeScript, SCSS, etc. adapter define el target de deploy (Node, Vercel, static). alias crea atajos de import como @/lib.

Estructura SFC
<script>
  // lógica JavaScript
  let name = "Mundo";
</script>

<!-- template HTML -->
<h1>Hola, {name}!</h1>

<style>
  /* estilos con scope automático */
  h1 { color: tomato; }
</style>

Un SFC (Single File Component) tiene 3 bloques: <script> (lógica), HTML (template) y <style> (CSS con scope). El CSS solo afecta al propio componente.

TypeScript
<script lang="ts">
  let counter: number = 0;
  let name: string = "Ana";

  interface User {
    name: string;
    age: number;
  }
  let user: User = { name: "Ana", age: 30 };
</script>

lang="ts" activa TypeScript en el script. Funciona con vitePreprocess() en el config. Tipos, interfaces y generics se soportan directamente en el SFC.

main.js (Montaje)
import App from "./App.svelte";

const app = new App({
  target: document.body,
});

export default app;

El archivo de entrada instancia el componente raíz con new App(). target define el elemento DOM donde se monta la app. En SvelteKit, esto se genera automáticamente.

Estilos Globales
<style>
  /* solo este componente */
  p { color: blue; }

  /* global (afecta a los hijos) */
  :global(.card) { padding: 1rem; }

  /* scope parcial */
  div :global(span) { font-weight: bold; }
</style>

Por defecto, el CSS tiene scope al componente. :global() elimina el scope para selectores específicos. Útil para estilizar contenido de slots o bibliotecas externas.

Reatividade


9 cards
Variables Reactivas
<script>
  let counter = 0;

  function increment() {
    counter += 1;
  }
</script>

<button on:click={increment}>
  {counter}
</button>

En Svelte, cualquier let en el <script> es reactivo. Basta reasignar (counter += 1) para que el DOM se actualice automáticamente. No necesita setState ni setters.

$state (Svelte 5)
<script>
  let counter = $state(0);
  let user = $state({ name: "Ana", age: 30 });

  // la mutación directa funciona:
  user.age = 31;
  counter++;
</script>

$state() es la nueva rune de reactividad de Svelte 5. Crea estado profundamente reactivo — las mutaciones en propiedades anidadas disparan actualizaciones sin reasignación.

$bindable (Svelte 5)
<!-- Child.svelte -->
<script>
  let { value = $bindable() } = $props();
</script>
<input bind:value={value} />

<!-- Parent.svelte -->
<Child bind:value={name} />

$bindable() marca una prop como vinculable (two-way). El padre usa bind:value para sincronizar. Sustituye el patrón antiguo de dispatch + export let.

Declaraciones Reactivas ($:)
let price = 10;
let qty = 2;

$: total = price * qty;
$: console.log("Cambió:", total);
$: if (total > 100) {
  alert("¡Caro!");
}

$: crea declaraciones reactivas que se re-ejecutan cuando las dependencias cambian. Funciona como valor derivado, efecto colateral o bloque condicional reactivo.

$derived (Svelte 5)
<script>
  let price = $state(10);
  let qty = $state(2);

  let total = $derived(price * qty);
  let withVat = $derived.by(() => {
    return total * 1.23;
  });
</script>

$derived() sustituye a $: para valores computados. $derived.by() acepta una función para lógica compleja. Recalcula automáticamente cuando las dependencias cambian.

Arrays e Inmutabilidad
let items = [1, 2, 3];

// la reactividad requiere reasignación:
items = [...items, 4];
items = items.filter(n => n > 1);

// objetos:
let user = { name: "Ana" };
user = { ...user, age: 30 };

Svelte detecta reasignación, no mutación profunda. push() solo no actualiza — usa spread ([...arr, x]) y reasigna. En Svelte 5 con $state, la mutación directa funciona.

$effect (Svelte 5)
<script>
  let counter = $state(0);

  $effect(() => {
    console.log("Contador:", counter);
    return () => {
      console.log("cleanup");
    };
  });
</script>

$effect() sustituye a $: para efectos colaterales. Corre después de que el DOM se actualice. El retorno (función de cleanup) se ejecuta antes de la siguiente ejecución o al destruir.

Bloque Reactivo
$: {
  console.log(a, b);
  result = a + b;
  if (result > 10) {
    message = "Grande";
  }
}

Un bloque $: { } agrupa múltiples instrucciones reactivas. Se re-ejecuta cuando cualquier dependencia (a, b) cambia. Útil para lógica derivada compleja.

$props (Svelte 5)
<script>
  let { title, color = "blue", ...rest } = $props();
</script>

<h2 style="color: {color}">{title}</h2>

$props() sustituye a export let para recibir props. Usa desestructuración con defaults. ...rest captura props extra (spread). Más explícito y type-safe.

Template e Binding


9 cards
Interpolación
<p>{name}</p>
<p>{1 + 1}</p>
<p>{active ? "Sí" : "No"}</p>
<p>{items.length} ítems</p>

Las llaves {} insertan expresiones JavaScript en el template. Aceptan cualquier expresión válida: variables, operaciones, ternarios, llamadas a funciones. El output se escapa automáticamente.

Two-way Binding
<input bind:value={name} />
<input type="checkbox" bind:checked={active} />
<select bind:value={country}>
  <option>PT</option>
  <option>BR</option>
</select>
<input type="range" bind:value={volume} />

bind:value sincroniza el input con una variable en ambos sentidos. Funciona con text, checkbox, select, range. Elimina handlers on:input manuales.

Clases y Estilos
<div class:active={isActive}>
<div class:visible>

<div style:color={color} style:font-size="14px">

<!-- Svelte 5: -->
<div class="base" class:active={flag}>

class:name={condición} añade/quita clases dinámicamente. style:prop={valor} define estilos inline reactivos. Más limpio que interpolar strings de class.

If / else
{#if active}
  <p>Visible</p>
{:else if other}
  <p>Otro estado</p>
{:else}
  <p>Oculto</p>
{/if}

Los bloques {#if} / {:else if} / {:else} / {/if} hacen renderizado condicional. El contenido se elimina del DOM (no solo se oculta) cuando es falso.

Binding de Elementos
<div bind:clientWidth={w} bind:clientHeight={h}>
  {w}x{h}
</div>

<input bind:this={inputEl} />
<script>
  import { onMount } from "svelte";
  onMount(() => inputEl.focus());
</script>

bind:clientWidth / clientHeight vinculan las dimensiones del elemento a variables. bind:this guarda la referencia DOM directa para llamar métodos como focus().

Each (Listas)
{#each items as item, i}
  <li>{i}: {item.name}</li>
{:else}
  <p>Lista vacía</p>
{/each}

{#each items as item (item.id)}
  <li>{item.name}</li>
{/each}

{#each} itera arrays. El segundo parámetro es el índice. {:else} muestra un fallback si está vacío. La key entre paréntesis (item.id) optimiza las reordenaciones.

Binding de Grupos
let flavors = [];
{#each ["vainilla", "chocolate", "fresa"] as flavor}
  <label>
    <input type="checkbox" bind:group={flavors} value={flavor} />
    {flavor}
  </label>
{/each}
<p>Elegidos: {flavors.join(", ")}</p>

bind:group sincroniza un array (checkboxes) o un valor (radios) automáticamente. Cada input con el mismo group comparte la variable. Sin handlers manuales.

Await
{#await fetch("/api/data")}
  <p>Cargando...</p>
{:then response}
  <p>{response.ok}</p>
{:catch error}
  <p>Error: {error.message}</p>
{/await}

{#await} renderiza los estados de una Promise: loading, éxito (:then) y error (:catch). Elimina la necesidad de variables loading/error manuales.

HTML Raw y @const
{@html htmlContent}

{#each items as item}
  {@const upper = item.name.toUpperCase()}
  <p>{upper}</p>
{/each}

{@html} inserta HTML sin escape (riesgo de XSS — ¡sanitiza antes!). {@const} declara variables locales dentro de bloques {#each} o {#if}.

Eventos e Ações


8 cards
on:click y Handlers
<button on:click={handler}>Función</button>
<button on:click={() => n++}>Inline</button>
<button on:click={fn1} on:click={fn2}>
  Múltiples
</button>

on:click escucha eventos del DOM. Acepta una referencia a función o una arrow function inline. Se permiten múltiples handlers en el mismo evento y se ejecutan por orden.

Escuchar Eventos del Hijo
<Child on:saved={onSave} />

<script>
  function onSave(e) {
    console.log(e.detail.id);
  }
</script>

El padre usa on:nombreEvento para escuchar eventos dispatchados por el hijo. El payload está en e.detail. Comunicación unidireccional: el hijo emite, el padre reacciona.

Modificadores de Evento
<button on:click|once={fn}>Una vez</button>
<form on:submit|preventDefault={fn}>
<div on:click|stopPropagation={fn}>
<input on:keydown|self={fn}>

Modificadores con |: once (se quita tras el 1º), preventDefault, stopPropagation, self (solo si el target es él mismo), capture, passive.

Event Forwarding
<!-- Wrapper.svelte -->
<button on:click>
  <slot />
</button>

<!-- Uso -->
<Wrapper on:click={handler}>
  Haz clic aquí
</Wrapper>

on:click sin handler reenvía el evento al padre. El componente wrapper se vuelve transparente. Útil para componentes de UI que envuelven elementos nativos.

Objeto del Evento
<input on:input={(e) => {
  value = e.target.value;
}} />

<div on:keydown={(e) => {
  if (e.key === "Enter") send();
}}>

El parámetro e es el evento nativo del DOM. e.target.value accede al valor del input. e.key identifica la tecla. Funciona con cualquier evento (click, keydown, scroll).

Actions (use:)
<input use:autofocus />

<script>
  function autofocus(node) {
    node.focus();
    return {
      update(params) { },
      destroy() { }
    };
  }
</script>

use:acción ejecuta una función cuando el elemento se crea en el DOM. Recibe el node y puede retornar update (los params cambian) y destroy (cleanup). Ideal para tooltips, focus, drag.

createEventDispatcher
<script>
  import { createEventDispatcher } from "svelte";
  const dispatch = createEventDispatcher();

  function save() {
    dispatch("saved", { id: 1 });
  }
</script>

createEventDispatcher() crea un emisor de eventos personalizados. dispatch("nombre", datos) envía al padre. El padre escucha con on:saved={handler}. Los datos quedan en e.detail.

Actions con Parámetros
<div use:tooltip={{ text: "Ayuda", pos: "top" }}>
  Info
</div>

<script>
  function tooltip(node, { text, pos }) {
    // crear tooltip con text y pos
    return {
      update({ text, pos }) { },
      destroy() { /* eliminar */ }
    };
  }
</script>

use:acción={params} pasa un objeto como segundo argumento. update() se llama cuando los params cambian reactivamente. Permite acciones configurables y reutilizables.

Componentes


9 cards
Props (export let)
<!-- Card.svelte -->
<script>
  export let title;
  export let color = "blue";
</script>

<h2 style="color: {color}">{title}</h2>

export let declara una prop recibida del padre. El valor por defecto (= "blue") la hace opcional. Sin default, la prop es obligatoria (warning si no se pasa).

Named Slots
<!-- Card.svelte -->
<slot name="header" />
<slot />
<slot name="footer" />

<!-- uso -->
<Card>
  <svelte:fragment slot="header">
    Título
  </svelte:fragment>
  Cuerpo
</Card>

slot name="x" crea slots con nombre. El padre usa svelte:fragment slot="x" para rellenarlos. El slot sin nombre es el default. Permite layouts complejos con múltiples zonas.

svelte:self y svelte:element
<!-- recursión -->
{#if depth > 0}
  <svelte:self depth={depth - 1} />
{/if}

<!-- elemento dinámico -->
<svelte:element this={tag}>
  Contenido
</svelte:element>

svelte:self permite recursión (árboles, menús anidados). svelte:element renderiza un tag HTML dinámico (h1, p, div) definido por variable.

Usar Componente
<script>
  import Card from "./Card.svelte";
</script>

<Card title="Hola" color="red" />
<Card title="Mundo" />

Importa el componente y úsalo como tag HTML. Las props se pasan como atributos. El nombre del tag debe ser PascalCase (lo distingue de elementos HTML nativos). El import puede ser de ruta relativa o alias $lib.

Slot Props
<!-- List.svelte -->
{#each items as item}
  <slot {item} index={i} />
{/each}

<!-- uso -->
<List let:item let:index>
  <p>{index}: {item.name}</p>
</List>

Las props en el <slot> exponen datos del hijo al padre. El padre accede con let:prop. Patrón "render props" — el hijo controla los datos, el padre controla el renderizado.

Spread de Props
<script>
  const data = { title: "Hola", color: "red" };
</script>

<Card {...data} />
<Card {...data} color="blue" />

{...objeto} esparce todas las propiedades como props. Las props explícitas después del spread sobrescriben. Útil para pasar muchos datos o reenviar props desconocidas.

setContext / getContext
// padre
import { setContext } from "svelte";
setContext("theme", "dark");

// hijo (cualquier profundidad)
import { getContext } from "svelte";
const theme = getContext("theme");

setContext / getContext comparten datos sin prop drilling. El contexto se define en el padre y es accesible en cualquier descendiente. Ideal para temas, configs, stores de feature.

Slots
<!-- Card.svelte -->
<div class="card">
  <slot>Contenido por defecto</slot>
</div>

<!-- uso -->
<Card>Mi texto custom</Card>
<Card />

<slot> define dónde se proyecta el contenido del padre. El contenido entre las tags es el fallback si no se pasa nada. Permite composición flexible sin props.

svelte:component
<svelte:component this={currentComponent} />

<script>
  import A from "./A.svelte";
  import B from "./B.svelte";
  let currentComponent = A;
</script>

svelte:component renderiza dinámicamente el componente en this. Cuando la variable cambia, el componente antiguo se destruye y el nuevo se monta. Útil para tabs, wizards.

Ciclo de Vida e Transições


8 cards
onMount
import { onMount } from "svelte";

onMount(() => {
  console.log("¡Montado en el DOM!");
  const timer = setInterval(tick, 1000);

  return () => {
    clearInterval(timer);
  };
});

onMount corre después de que el componente se inserte en el DOM. El retorno (función) es el cleanup ejecutado al destruir. Ideal para timers, subscriptions, fetch de datos.

Transiciones Personalizadas
function typewriter(node, { speed = 50 }) {
  const text = node.textContent;
  return {
    duration: text.length * speed,
    tick: (t) => {
      node.textContent = text.slice(0,
        Math.floor(t * text.length));
    }
  };
}

Una transición custom retorna duration y tick(t) donde t va de 0 a 1. tick se llama en cada frame con el progreso. Permite animaciones totalmente personalizadas.

beforeUpdate / afterUpdate
import { beforeUpdate, afterUpdate } from "svelte";

beforeUpdate(() => {
  // antes de cualquier update del DOM
});

afterUpdate(() => {
  // después del DOM actualizado
});

beforeUpdate corre antes de cada re-render. afterUpdate corre después de que el DOM se actualice. Útiles para sincronizar bibliotecas externas o medir elementos.

Animate (flip)
import { flip } from "svelte/animate";

{#each items as item (item.id)}
  <div animate:flip={{ duration: 300 }}>
    {item.name}
  </div>
{/each}

animate:flip anima las reordenaciones de listas (First-Last-Invert-Play). Requiere key en el {#each}. Cuando los ítems cambian de posición, la transición es suave.

onDestroy y tick
import { onDestroy, tick } from "svelte";

onDestroy(() => {
  console.log("Componente eliminado");
});

// esperar el update del DOM:
value = "nuevo";
await tick();
// el DOM ya refleja "nuevo"

onDestroy corre cuando el componente se elimina. tick() retorna una Promise que se resuelve tras el siguiente update del DOM — útil para acceder a valores actualizados.

Key Block
{#key value}
  <div transition:fade>
    Contenido para {value}
  </div>
{/key}

{#key expresión} destruye y recrea el contenido cuando la expresión cambia. Útil para reiniciar animaciones o componentes sin svelte:component. Combina bien con transiciones.

Transiciones (fade, fly, slide)
import { fade, fly, slide } from "svelte/transition";

{#if visible}
  <div transition:fade={{ duration: 300 }}>
    Hola
  </div>
  <div in:fly={{ y: 200 }} out:slide>
    Animado
  </div>
{/if}

transition: anima entrada y salida. in: / out: separan las animaciones. Built-ins: fade, fly, slide, scale, blur. Aceptan duration, delay, easing.

Crossfade
import { crossfade } from "svelte/transition";
const [send, receive] = crossfade({
  duration: 300
});

<!-- elemento A -->
<div out:send={{ key: id }} />

<!-- elemento B -->
<div in:receive={{ key: id }} />

crossfade crea pares send/receive para animar elementos entre posiciones (ej: lista → detalle). La misma key une los dos elementos en la transición.

Stores


8 cards
Writable Store
// stores.js
import { writable } from "svelte/store";
export const counter = writable(0);

// componente
counter.set(5);
counter.update(n => n + 1);

writable(valor) crea un store mutable. set() sustituye el valor. update() recibe el valor actual y retorna el nuevo. Cualquier componente puede leer/escribir.

Store Personalizado
function createCounter(initial) {
  const { subscribe, set, update } = writable(initial);
  return {
    subscribe,
    increment: () => update(n => n + 1),
    decrement: () => update(n => n - 1),
    reset: () => set(initial),
  };
}

Expón solo subscribe + métodos personalizados. Encapsula la lógica e impide el set() directo. El consumidor usa $store normalmente. Patrón para stores con reglas de negocio.

Auto-suscripción ($)
<script>
  import { counter } from "./stores";
</script>

<p>{$counter}</p>
<button on:click={() => $counter++}>
  Incrementar
</button>

El prefijo $ se suscribe automáticamente al store. {$counter} en el template se actualiza en tiempo real. $counter++ funciona como atajo para update. La desuscripción es automática.

Subscribe Manual
import { counter } from "./stores";

const unsub = counter.subscribe(value => {
  console.log("Nuevo:", value);
});

// cuando ya no se necesite:
unsub();

subscribe(callback) registra un listener y retorna la función de desuscripción. Fuera de componentes Svelte (JS puro), no hay auto-suscripción $ — usa este método manualmente.

Readable Store
import { readable } from "svelte/store";

export const time = readable(new Date(), (set) => {
  const id = setInterval(
    () => set(new Date()), 1000
  );
  return () => clearInterval(id);
});

readable() crea un store de solo lectura. La función start recibe set para actualizar. El retorno es el cleanup (se ejecuta cuando no hay suscriptores). Ideal para relojes, sensores, WebSocket.

Store con localStorage
import { writable } from "svelte/store";

function persisted(key, initial) {
  const stored = localStorage.getItem(key);
  const store = writable(stored ? JSON.parse(stored) : initial);
  store.subscribe(v =>
    localStorage.setItem(key, JSON.stringify(v))
  );
  return store;
}

Combina writable con localStorage para persistencia. El store se hidrata del storage al crearse y graba en cada cambio. Patrón común para preferencias del usuario.

Derived Store
import { derived } from "svelte/store";
import { counter } from "./stores";

export const double = derived(
  counter,
  ($counter) => $counter * 2
);

// múltiples stores:
export const sum = derived(
  [a, b], ([$a, $b]) => $a + $b
);

derived() crea un store calculado a partir de otros. Recalcula automáticamente cuando la fuente cambia. Acepta uno o múltiples stores como dependencias.

get() (Fuera de Componentes)
import { get } from "svelte/store";
import { counter } from "./stores";

const value = get(counter);
console.log(value); // valor actual

get(store) lee el valor actual sin suscribirse. Útil en funciones utilitarias, tests o código fuera de componentes. No es reactivo — solo una lectura puntual.

SvelteKit


9 cards
Routing por Archivos
src/routes/
  +page.svelte        // /
  about/
    +page.svelte      // /about
  blog/[slug]/
    +page.svelte      // /blog/:slug

Cada carpeta en src/routes/ es una ruta. +page.svelte es la página. [slug] es un parámetro dinámico. La estructura de carpetas define las URLs automáticamente.

Layout
<!-- +layout.svelte -->
<nav>Menú global</nav>
<slot />
<footer>Pie de página</footer>

<!-- +layout.js -->
export const prerender = true;

+layout.svelte envuelve todas las páginas de la carpeta (y subcarpetas). El <slot /> es donde renderiza la página. Los layouts se anidan jerárquicamente. prerender genera HTML estático.

Adaptadores y Deploy
// svelte.config.js
import adapter from "@sveltejs/adapter-node";
// o: adapter-auto, adapter-static,
//    adapter-vercel, adapter-cloudflare

export default {
  kit: { adapter: adapter() }
};

// build:
npm run build

El adapter define el target de deploy. adapter-node (servidor Node), adapter-static (sitio estático), adapter-vercel/adapter-cloudflare (edge). npm run build genera el output.

Load Function
// +page.js
export async function load({ params, fetch }) {
  const res = await fetch(`/api/posts/${params.slug}`);
  return { post: await res.json() };
}

// +page.svelte
<script>
  export let data;
</script>
<h1>{data.post.title}</h1>

load() en +page.js carga datos antes de renderizar. Corre en cliente y servidor. El retorno queda disponible en data. params da acceso a los parámetros de la ruta.

API Endpoints
// +server.js
export async function GET({ params }) {
  return new Response(
    JSON.stringify({ ok: true }),
    { headers: { "Content-Type": "application/json" } }
  );
}

export async function POST({ request }) {
  const body = await request.json();
  return new Response(null, { status: 201 });
}

+server.js crea endpoints HTTP. Exporta funciones con nombres de verbos: GET, POST, PUT, DELETE. Retorna Response nativa. Ideal para APIs REST.

Server Load
// +page.server.js
export async function load({ params }) {
  const post = await db.posts.find(params.slug);
  return { post };
}

+page.server.js corre solo en el servidor. Ideal para acceder a la BD, secretos o APIs privadas. Los datos se serializan y se envían al cliente. Nunca expone código sensible.

Hooks
// hooks.server.js
export async function handle({ event, resolve }) {
  const token = event.cookies.get("token");
  event.locals.user = await getUser(token);
  return resolve(event);
}

// en +page.server.js:
export function load({ locals }) {
  return { user: locals.user };
}

hooks.server.js intercepta todos los requests. handle corre antes de cada ruta. event.locals comparte datos entre hooks y load. Ideal para auth global, logging, CORS.

Form Actions
// +page.server.js
export const actions = {
  default: async ({ request }) => {
    const data = await request.formData();
    const name = data.get("name");
    await db.save({ name });
    return { success: true };
  }
};

actions procesan envíos de formularios en el servidor. request.formData() lee los campos. El retorno queda en form en la página. Soporta progressive enhancement (funciona sin JS).

Navegación Programática
<script>
  import { goto, invalidupToll } from "$app/navigation";
  import { page } from "$app/stores";

  async function logout() {
    await fetch("/logout", { method: "POST" });
    invalidupToll();
    goto("/login");
  }
</script>
<p>Ruta actual: {$page.url.pathname}</p>

goto() navega programáticamente. invalidupToll() re-ejecuta todos los load(). $page es un store con URL, params y status. Sustituye a window.location.

Avançado


7 cards
svelte:window y svelte:body
<svelte:window on:scroll={onScroll}
  bind:scrollY={y} />
<svelte:body on:mouseenter={fn} />
<svelte:head>
  <title>{title} | App</title>
</svelte:head>

svelte:window escucha eventos globales sin addEventListener manual. bind:scrollY sincroniza el scroll. svelte:head inyecta tags en el <head> (SEO, meta tags).

Streaming y Defer
// +page.server.js
export function load() {
  return {
    fast: { title: "Hola" },
    slow: db.query("SELECT ...")  // Promise
  };
}

// +page.svelte
{#await data.slow}
  <p>Cargando datos lentos...</p>
{:then result}
  <p>{result.length} registros</p>
{/await}

Retornar Promises (sin await) en el load hace streaming — la página se renderiza inmediatamente con los datos rápidos. {#await} en el template muestra el loading hasta resolver.

Snippets (Svelte 5)
{#snippet card(item)}
  <div class="card">
    <h3>{item.title}</h3>
    <p>{item.text}</p>
  </div>
{/snippet}

{#each items as item}
  {@render card(item)}
{/each}

{#snippet} define bloques de template reutilizables (sustituye a los slot props). {@render} invoca el snippet con argumentos. Más explícito y componible que los slots con nombre.

Testing (Vitest)
import { render, screen } from "@testing-library/svelte";
import { test, expect } from "vitest";
import Card from "./Card.svelte";

test("muestra título", () => {
  render(Card, { props: { title: "Hola" } });
  expect(screen.getByText("Hola")).toBeTruthy();
});

@testing-library/svelte renderiza componentes en tests. render(Componente, { props }) lo monta en el DOM virtual. screen.getByText búsqueda elementos. Corre con vitest.

Accesibilidad (a11y)
<!-- Svelte avisa en compilación: -->
<img src="photo.jpg" alt="Descripción" />
<button aria-label="Cerrar">×</button>
<input aria-invalid={!!error} />

<!-- svelte-ignore para suprimir -->
<!-- svelte-ignore a11y-missing-content -->

Svelte emite warnings de accesibilidad en compilación: alt en imágenes, labels en inputs, roles. svelte-ignore suprime warnings específicos cuando es necesario.

Rendimiento y Buenas Prácticas
<!-- keyed each para listas grandes -->
{#each items as item (item.id)}
  <Item {item} />
{/each}

<!-- lazy load de componentes -->
<script>
  import { onMount } from "svelte";
  let Heavy;
  onMount(async () => {
    Heavy = (await import("./Heavy.svelte")).default;
  });
</script>

Usa key en {#each} para diffs eficientes. El import() dinámico hace code-splitting. Svelte compila a vanilla JS — sin virtual DOM, actualizaciones quirúrgicas en el DOM real.

Error Boundaries
<!-- +error.svelte -->
<script>
  import { page } from "$app/stores";
</script>

<h1>{$page.status}: {$page.error.message}</h1>
<a href="/">Volver al inicio</a>

+error.svelte se renderiza cuando una ruta lanza un error. $page.status y $page.error dan los detalles. Puede existir por ruta o global en src/routes/.