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.
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.sveltee+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
loadgira 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.ts — una 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() },
};
| Adapter | Cosa produce | Quando |
|---|---|---|
adapter-auto | Rileva la piattaforma | Predefinito, va bene per iniziare |
adapter-node | Un server Node.js | VPS, container, controllo pieno |
adapter-static | Solo file HTML e asset | Blog, documentazione, vetrine |
adapter-vercel / -netlify | Funzioni serverless | Piattaforme gestite |
adapter-cloudflare | Runtime edge | Latenza 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. Lì 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.