È uscito il Corso Java Completo
Torna al blog

Cos'è SvelteKit e come funziona

Cos'è SvelteKit e come funziona: routing sui file, la load per i dati, le form action con progressive enhancement e gli adapter per pubblicare ovunque.

Edoardo Midali

Edoardo Midali

Developer · Content Creator

10 min di lettura

Hai scritto qualche componente Svelte, ti sei trovato bene e adesso vorresti farci un'applicazione vera. Ti servono pagine, dati presi da un database, moduli che salvano qualcosa, e un posto dove pubblicare tutto. Svelte da solo non fa niente di tutto questo: si occupa dell'interfaccia e basta. In questo articolo trovi cosa aggiunge SvelteKit, perché la load è il concetto centrale da capire, come mai i suoi moduli funzionano anche senza JavaScript e quando conviene davvero.

Cos'è

SvelteKit è il meta-framework ufficiale di Svelte: aggiunge al compilatore di componenti il routing, il caricamento dati, la gestione dei moduli, gli endpoint HTTP e la pubblicazione, in un progetto solo e con le scelte già prese.

Sotto c'è Svelte intatto, con le rune e la sua sintassi. SvelteKit non tocca come scrivi un componente: si occupa di tutto quello che sta attorno. È lo stesso rapporto che c'è tra Next.js e React.

Chi sceglie Svelte per un progetto reale parte quasi sempre da qui, non da Svelte nudo.

Il routing sui file

Le rotte sono cartelle dentro src/routes/, e dentro ogni cartella i file hanno nomi fissi con ruoli precisi. All'inizio spiazza — ti ritrovi venti file chiamati +page.svelte — ma il vantaggio è che sai sempre cosa stai guardando.

src/routes/
  +layout.svelte          →  involucro comune
  +page.svelte            →  /
  chi-siamo/+page.svelte  →  /chi-siamo
  blog/+page.svelte       →  /blog
  blog/[slug]/
    +page.svelte          →  /blog/qualsiasi-cosa
    +page.server.ts       →  i dati di quella pagina, solo server
  api/iscritti/+server.ts →  endpoint HTTP

Le parentesi quadre sono il segmento dinamico. I suffissi contano:

  • +page.svelte — il componente della pagina
  • +page.ts — dati che possono essere caricati sia sul server sia nel browser
  • +page.server.ts — dati che girano solo sul server: qui puoi interrogare il database e usare chiavi segrete
  • +layout.svelte e +layout.server.ts — la stessa coppia, ma per tutto il sottoalbero
  • +server.ts — un endpoint HTTP, senza pagina

Quel .server nel nome è il confine tra server e client, ed è dichiarato dal nome del file invece che da una direttiva dentro il codice. È meno flessibile del modello di Next.js ma molto più difficile da sbagliare: non esiste il caso in cui una chiave segreta finisce nel pacchetto del browser perché hai spostato un import.

La load: il concetto centrale

Qui sta il cuore di SvelteKit, e se capisci solo una cosa di questo articolo, capisci questa.

In SvelteKit i dati si caricano prima del componente, non dentro. La funzione load di una rotta viene eseguita, il suo risultato viene passato alla pagina come data, e solo a quel punto il componente viene reso.

// src/routes/blog/[slug]/+page.server.ts
import { error } from "@sveltejs/kit";

export async function load({ params }) {
  const articolo = await db.articoli.findUnique({
    where: { slug: params.slug },
  });

  if (!articolo) throw error(404, "Articolo non trovato");

  return { articolo };
}
<!-- src/routes/blog/[slug]/+page.svelte -->
<script>
  let { data } = $props();
</script>

<article>
  <h1>{data.articolo.titolo}</h1>
  {@html data.articolo.html}
</article>

Perché non è solo una comodità

Il modo normale di caricare dati in un'applicazione lato client è dentro il componente: il componente si monta, parte la richiesta, si mostra uno spinner. Il problema arriva quando i componenti sono annidati. Il genitore carica, poi si rende, poi il figlio si monta, poi carica lui, poi si rende, poi il nipote. Ogni livello aspetta quello sopra: è il caricamento a cascata, e su una connessione mobile si traduce in secondi di attesa per dati che si sarebbero potuti chiedere tutti insieme.

SvelteKit lo elimina per costruzione. Le load di layout e pagina appartengono alla rotta, non ai componenti, quindi il framework le conosce tutte prima di renderizzare qualsiasi cosa e le esegue in parallelo. Non c'è modo di incappare nella cascata per distrazione.

Due dettagli che rendono la cosa pratica:

  • Puoi restituire una promessa non attesa. Il dato lento non blocca la pagina: la parte veloce si vede subito e quella lenta si riempie dopo, dentro un blocco {#await}.
  • La load gira anche alle navigazioni successive. Alla prima visita sul server, poi nel browser quando clicchi un link — senza che tu scriva due versioni del codice.
export async function load({ fetch }) {
  const utente = await fetch("/api/utente").then((r) => r.json()); // veloce
  const statistiche = fetch("/api/statistiche").then((r) => r.json()); // lenta, non attesa
  return { utente, statistiche };
}

Il fetch che arriva come parametro non è quello globale: sul server evita una chiamata HTTP a sé stesso e porta con sé i cookie della richiesta. Usa sempre quello, è uno degli errori più comuni.

I modelli di rendering disponibili — statico, a richiesta, con rigenerazione — sono quelli descritti in SSG, SSR e ISR; in SvelteKit li imposti per rotta esportando prerender, ssr o csr dal file della pagina.

Le form action e il progressive enhancement

Questa è la parte di SvelteKit che vale davvero la pena conoscere, anche se poi userai altro, perché è rara.

Una form action è una funzione lato server associata a una rotta, che riceve l'invio di un modulo HTML.

// src/routes/contatti/+page.server.ts
import { fail } from "@sveltejs/kit";

export const actions = {
  default: async ({ request }) => {
    const dati = await request.formData();
    const email = dati.get("email");

    if (typeof email !== "string" || !email.includes("@")) {
      return fail(400, { email, errore: "Email non valida" });
    }

    await db.iscritti.create({ data: { email } });
    return { successo: true };
  },
};
<script>
  import { enhance } from "$app/forms";
  let { form } = $props();
</script>

<form method="POST" use:enhance>
  <input name="email" type="email" value={form?.email ?? ""} />
  <button>Iscriviti</button>

  {#if form?.errore}<p class="errore">{form.errore}</p>{/if}
  {#if form?.successo}<p>Fatto, grazie.</p>{/if}
</form>

Nota cosa non c'è. Nessun onsubmit, nessun fetch, nessun preventDefault, nessuno stato "sto inviando" scritto a mano, nessun JSON.stringify. È un modulo HTML normale che fa un POST.

E qui arriva la parte interessante: quel modulo funziona anche senza JavaScript. Se il visitatore ha JavaScript disattivato, se lo script non è ancora stato scaricato, se la rete è caduta a metà caricamento o se il browser è vecchio, il modulo si comporta come un modulo del 1998: il browser fa il POST, il server risponde, la pagina si ricarica con l'esito. Funziona.

use:enhance è l'aggiunta, non la base. Quando il JavaScript c'è, intercetta l'invio, lo fa in background e aggiorna la pagina senza ricaricarla. Questo è il progressive enhancement: parti da qualcosa che funziona sempre e ci aggiungi sopra i miglioramenti quando le condizioni lo permettono.

È il contrario dell'approccio abituale, dove il modulo è un gestore JavaScript e senza JavaScript non esiste nulla. Su un modulo di pagamento o di contatto, la differenza tra "l'utente riprova" e "l'utente se ne va" può essere esattamente quella finestra di due secondi in cui lo script non era ancora pronto.

Da tenere presente: le action sono legate a una rotta e restituiscono dati a quella pagina. Se ti serve una scrittura invocabile da più punti o da un'app mobile, la cosa giusta è un +server.tsuna REST API normale.

Gli adapter

SvelteKit non decide dove pubblichi. Costruisce un'applicazione astratta e un adapter la traduce nel formato dell'ambiente di destinazione.

// svelte.config.js
import adapter from "@sveltejs/adapter-node";

export default {
  kit: { adapter: adapter() },
};
AdapterCosa produceQuando
adapter-autoRileva la piattaformaPredefinito, va bene per iniziare
adapter-nodeUn server Node.jsVPS, container, controllo pieno
adapter-staticSolo file HTML e assetBlog, documentazione, vetrine
adapter-vercel / -netlifyFunzioni serverlessPiattaforme gestite
adapter-cloudflareRuntime edgeLatenza bassa, ovunque nel mondo

Il passaggio da uno all'altro è una riga, a patto di non aver usato API specifiche di un ambiente. Con adapter-static serve anche export const prerender = true nel layout radice, e da quel momento le form action e gli endpoint dinamici non esistono più: stai generando un sito statico. È una scelta legittima, ma va fatta sapendo cosa perdi. Il quadro sulle opzioni di pubblicazione è in come fare il deploy di un sito.

Errori comuni che fanno perdere ore

Usare il fetch globale nella load. Sul server il tuo codice chiama sé stesso via HTTP e perde i cookie. Usa quello che arriva dai parametri.

Mettere codice server in +page.ts invece di +page.server.ts. Il primo gira anche nel browser: una query al database lì dentro o non compila, o — peggio — ti fa finire una chiave nel pacchetto pubblico. Nel dubbio, .server.

Aspettarsi che la load si riesegua da sola. Dipende da cosa ha letto: se usa params si riesegue quando cambia l'URL, ma se dipende da uno stato che SvelteKit non vede resta ferma. Per forzarla c'è invalidate() o invalidateAll().

Mettere stato condiviso in una variabile a livello di modulo sul server. Su un server Node quella variabile è condivisa tra tutti i visitatori: due utenti e vedi i dati dell'uno nella pagina dell'altro. Lo stato per utente sta nei cookie o in locals, popolato in hooks.server.ts.

Dare per scontato che sia tutto lato server. Dopo l'idratazione l'applicazione naviga nel browser come una normale SPA. Il codice di +page.svelte gira in entrambi i posti: se tocchi window fuori da onMount, la prima resa si rompe.

Quando conviene e quando no

Ha senso quando ti servono insieme contenuti indicizzabili e interattività vera, e vuoi frontend e backend nello stesso progetto senza montare cinque librerie. Il modello dati è più semplice di quello di Next.js e si impara in una giornata.

Ha senso quando il peso del JavaScript conta davvero: siti pubblici con molto traffico mobile, mercati con connessioni lente, pagine dove i Core Web Vitals sono un obiettivo dichiarato.

Ha senso per un progetto tuo, un MVP o un team piccolo che sceglie da sé lo stack. Da solo scrivi meno codice e vai più veloce.

Non ha senso se stai imparando per trovare lavoro. Le offerte italiane che chiedono SvelteKit si contano sulle dita, e la qualità tecnica non cambia quel dato. È lo stesso discorso che vale per Svelte, spiegato per intero in Svelte o React.

Non ha senso quando il progetto dipende da librerie di terze parti complesse. Editor di testo ricco, calendari, griglie di dati, integrazioni con servizi esterni: per React esiste tutto in più versioni, per Svelte a volte esiste un pacchetto mantenuto da una persona sola.

Non ha senso per un sito di soli contenuti.Astro fa lo stesso lavoro spedendo ancora meno JavaScript, e ti lascia comunque usare componenti Svelte dove servono.

In sintesi

SvelteKit è quello che rende Svelte utilizzabile per un'applicazione vera: routing, dati, moduli, endpoint e pubblicazione, già montati.

La load è il concetto da capire. I dati si caricano prima del componente e in parallelo, quindi il caricamento a cascata non può verificarsi per distrazione.

Le form action con use:enhance funzionano anche senza JavaScript, e sono una delle poche implementazioni serie di progressive enhancement in circolazione.

Gli adapter separano il codice dalla destinazione: Node, serverless, edge o statico cambiando una riga.

Il .server nel nome del file è il confine tra server e browser, ed è molto più difficile da sbagliare di una direttiva dentro il codice.

Il limite non è tecnico ma di contorno: ecosistema più piccolo e mercato del lavoro italiano quasi inesistente rispetto a React.

Per le basi del compilatore, vedi cos'è Svelte; per il confronto con l'alternativa che quasi tutti scelgono, Svelte o React.