Vai al contenuto principale

Cos’è Svelte

Svelte occupa una posizione originale tra i framework JavaScript. Mentre React e Vue funzionano come librerie a runtime, Svelte funziona come compilatore. In fase di build converte i componenti in JavaScript puro, quindi non serve inviare al browser codice framework aggiuntivo. La caratteristica principale di Svelte è che non usa un Virtual DOM. Quando lo stato cambia, il codice generato in compilazione da Svelte aggiorna direttamente il DOM. Il risultato è una UI molto leggera e veloce.
In questa pagina spieghiamo la combinazione di Svelte 5 e Inertia v3. Lo starter kit di Laravel 13 usa questa configurazione per impostazione predefinita.

Le rune di Svelte 5

Con Svelte 5 (rilasciato nel 2024) è stato introdotto un nuovo sistema di reattività chiamato Runes. Si dichiarano stati reattivi con funzioni speciali (rune) come $state, $derived ed $effect. A differenza della reattività implicita di Svelte 4 e precedenti, il design è esplicito e più prevedibile.
<script lang="ts">
    let count = $state(0)

    function increment() {
        count++
    }
</script>

<button onclick={increment}>{count}</button>
Lo starter kit Svelte di Laravel usa Svelte 5 + TypeScript come standard. Anche gli esempi di questa pagina sono tutti in TypeScript (lang="ts").

Posizione in Laravel

Storia

Il rapporto tra Svelte e Laravel è molto più recente rispetto a Vue e React: la sua prima adozione ufficiale è l’inclusione negli starter kit ufficiali. Con Laravel 13 (2026) Svelte è stato aggiunto ufficialmente agli starter kit e, di fatto, è diventato una delle scelte frontend ufficiali nell’ecosistema Laravel. È trattato allo stesso livello di Vue e React ed è selezionabile dal prompt interattivo di laravel new. È ancora un framework poco conosciuto tra gli utenti Laravel, ma è caratterizzato da bundle leggeri grazie al compilatore e sintassi semplice: chi arriva da Vue o React potrebbe sorprendersi della minore quantità di codice necessaria.

Stile principale attuale: Inertia × Svelte

Il modo principale di usare Svelte in Laravel è Inertia × Svelte. Inertia realizza l’architettura “monolite moderno” che permette di passare dati dai controller Laravel direttamente ai componenti Svelte senza progettare un’API.

Setup

Tramite starter kit (consigliato)

Per iniziare un nuovo progetto il modo più semplice è usare lo starter kit.
laravel new my-app
Se scegli Svelte nel prompt interattivo, viene tutto configurato automaticamente.
  • inertiajs/inertia-laravel (adapter lato server)
  • @inertiajs/svelte (adapter lato client)
  • svelte + @sveltejs/vite-plugin-svelte (Svelte 5 e plugin Vite)
  • TypeScript + svelte-check
  • Tailwind CSS + libreria di componenti shadcn-svelte
  • Middleware HandleInertiaRequests
  • Schermate di autenticazione (login, registrazione, ecc.) già implementate con Inertia + Svelte + TypeScript

Installazione manuale

Per aggiungerlo a un progetto esistente, installa separatamente lato server e lato client.
# Lato server (PHP)
composer require inertiajs/inertia-laravel

# Lato client (JavaScript)
npm install @inertiajs/svelte @inertiajs/vite svelte
npm install --save-dev @sveltejs/vite-plugin-svelte svelte-check typescript
Aggiungi poi in vite.config.ts il plugin Svelte e il plugin Vite di Inertia.
import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import inertia from '@inertiajs/vite'

export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.ts'],
            refresh: true,
        }),
        svelte(),
        inertia(),
    ],
})
Avvia l’app Inertia in resources/js/app.ts. Il plugin @inertiajs/vite esegue la risoluzione automatica delle pagine e il mount, quindi bastano poche righe nell’entry point.
import { createInertiaApp } from '@inertiajs/svelte'

createInertiaApp()
Per i dettagli dell’installazione manuale (configurazione del template root, registrazione dei middleware, ecc.) consulta la documentazione ufficiale di Inertia.

Struttura delle directory

Negli starter kit i componenti pagina Svelte vanno nella directory resources/js/pages/.
resources/js/
├── app.ts             # Entry point dell'app Inertia
├── components/        # Componenti UI riutilizzabili
│   └── ui/            # Componenti shadcn-svelte
├── layouts/           # Componenti di layout
│   ├── AppLayout.svelte
│   └── AuthLayout.svelte
├── lib/               # Funzioni utility e moduli rune Svelte
├── pages/             # Componenti pagina Inertia (corrispondono ai nomi dei controller)
│   ├── auth/
│   │   ├── Login.svelte
│   │   └── Register.svelte
│   ├── Dashboard.svelte
│   └── posts/
│       ├── Index.svelte
│       ├── Create.svelte
│       └── Show.svelte
└── types/             # Definizioni di tipo TypeScript
Scrivendo Inertia::render('posts/Index', [...]), il componente corrispondente è resources/js/pages/posts/Index.svelte.

Struttura di base di un file Svelte

Un file .svelte è composto da tre blocchi: <script>, template e <style>.
<script lang="ts">
    // Logica (TypeScript)
    let name = $state('Laravel')
</script>

<!-- Template (notazione simile all'HTML) -->
<h1>Hello, {name}!</h1>

<style>
    /* CSS con scope (applicato solo a questo componente) */
    h1 {
        color: #ff2d20;
    }
</style>
La struttura ricorda i Single File Component (SFC) di Vue, ma il codice del template e dello script è più conciso. <style> è per default con scope limitato, quindi non devi preoccuparti dei conflitti di nomi di classe.

Sintassi del template

Espansione di variabili ed espressioni

Nel template si usa {} per incorporare valori o espressioni JavaScript.
<script lang="ts">
    let name = $state('mondo')
    let count = $state(3)
</script>

<p>Ciao, {name}!</p>
<p>Il doppio è {count * 2}</p>

{#if} — condizionali

<script lang="ts">
    let isLoggedIn = $state(false)
    let role = $state('editor')
</script>

{#if isLoggedIn}
    <p>Benvenuto!</p>
{:else if role === 'admin'}
    <p>Loggato come amministratore</p>
{:else}
    <a href="/login">Login</a>
{/if}
Corrisponde a v-if / v-else di Vue e all’operatore ternario di React.

{#each} — rendering di liste

<script lang="ts">
    type Post = { id: number; title: string }
    let posts = $state<Post[]>([
        { id: 1, title: 'Primo post' },
        { id: 2, title: 'Secondo post' },
    ])
</script>

<ul>
    {#each posts as post (post.id)}
        <li>{post.title}</li>
    {/each}
</ul>
(post.id) è la specifica della chiave, usata per un aggiornamento differenziale efficiente. Corrisponde a v-for di Vue e a Array.map() di React.

bind: — binding bidirezionale

Con bind:value puoi sincronizzare bidirezionalmente il valore di un elemento form e una variabile reattiva.
<script lang="ts">
    let title = $state('')
    let agreed = $state(false)
    let role = $state('viewer')
</script>

<!-- Input di testo -->
<input bind:value={title} type="text" />
<p>In inserimento: {title}</p>

<!-- Checkbox -->
<input bind:checked={agreed} type="checkbox" />
<p>Accettato: {agreed}</p>

<!-- Select -->
<select bind:value={role}>
    <option value="viewer">Lettore</option>
    <option value="editor">Editor</option>
    <option value="admin">Amministratore</option>
</select>
Corrisponde a v-model di Vue. In React dovevi scrivere manualmente handler onChange; in Svelte, con bind:, si scrive in modo dichiarativo.

Le basi del componente pagina

I componenti pagina di Inertia sono normali componenti Svelte. I dati passati dal controller Laravel arrivano come props.

Controller

// app/Http/Controllers/PostController.php
use Inertia\Inertia;
use App\Models\Post;

class PostController extends Controller
{
    public function index()
    {
        return Inertia::render('posts/Index', [
            'posts' => Post::latest()->paginate(10),
        ]);
    }
}

Componente pagina Svelte

In Svelte 5 le props si ricevono con la runa $props().
<!-- resources/js/pages/posts/Index.svelte -->
<script lang="ts">
    import { Link } from '@inertiajs/svelte'

    type Post = {
        id: number
        title: string
        created_at: string
    }

    type Props = {
        posts: {
            data: Post[]
        }
    }

    let { posts }: Props = $props()
</script>

<div>
    <h1>Elenco dei post</h1>
    {#each posts.data as post (post.id)}
        <article>
            <h2>
                <Link href={`/posts/${post.id}`}>{post.title}</Link>
            </h2>
            <p>{post.created_at}</p>
        </article>
    {/each}
</div>
Basta ricevere le props con $props() per usare nel template i dati passati dal controller. Non è necessario definire un’API REST.
Usando il componente <Link> di @inertiajs/svelte, le transizioni avvengono via XHR ed eviti il full reload del browser.
<script lang="ts">
    import { Link } from '@inertiajs/svelte'
</script>

<!-- Link di base -->
<Link href="/posts">Elenco post</Link>

<!-- Link con metodo POST (ad es. per l'eliminazione) -->
<Link href="/posts/1" method="delete" as="button">
    Elimina
</Link>

<!-- Preload (fetch anticipato all'hover) -->
<Link href="/posts/1" preload>Vedi il post</Link>
Si scrive come un normale tag <a>, ma dietro le quinte Inertia sostituisce solo il componente di pagina, dando una sensazione da SPA.

Componente Form

Il componente <Form> fornito da @inertiajs/svelte è lo stile consigliato usato negli starter kit per l’invio dei form. Specifichi action e method come props e descrivi l’interno con {#snippet}.

Uso di base

<script lang="ts">
    import { Form } from '@inertiajs/svelte'
</script>

<Form action="/posts" method="post" class="flex flex-col gap-4">
    {#snippet children({ errors, processing })}
        <div>
            <label for="title">Titolo</label>
            <input id="title" name="title" type="text" required />
            {#if errors.title}
                <p class="error">{errors.title}</p>
            {/if}
        </div>

        <div>
            <label for="content">Contenuto</label>
            <textarea id="content" name="content"></textarea>
            {#if errors.content}
                <p class="error">{errors.content}</p>
            {/if}
        </div>

        <button type="submit" disabled={processing}>
            {processing ? 'Invio in corso...' : 'Pubblica'}
        </button>
    {/snippet}
</Form>
{#snippet children({ errors, processing })} è la sintassi degli snippet di Svelte, un blocco di contenuto da passare al componente (equivalente agli slot di Vue o alle render props di React). errors e processing sono calcolati e passati automaticamente dal componente Form. Ai campi si assegna l’attributo HTML nativo name invece di bind:value, così funziona la raccolta standard dei dati del browser.

Pattern degli starter kit

Gli starter kit gestiscono le rotte come oggetti tramite Wayfinder. store.form() restituisce un oggetto contenente action e method dell’oggetto rotta e lo passi a <Form> con lo spread.
<script lang="ts">
    import { Form } from '@inertiajs/svelte'
    import { store } from '@/routes/login'
</script>

<Form
    {...store.form()}
    resetOnSuccess={['password']}
    class="flex flex-col gap-6"
>
    {#snippet children({ errors, processing })}
        <!-- Contenuto del form -->
    {/snippet}
</Form>
I campi specificati in resetOnSuccess vengono ripristinati automaticamente al successo dell’invio. Utile per campi come la password che vuoi svuotare dopo l’invio.
Se non usi Wayfinder puoi passare direttamente l’URL come action="/login" e funziona allo stesso modo.

Hook useForm

Per la gestione dei form usa l’hook useForm di @inertiajs/svelte. Puoi implementare in modo semplice gestione stato, invio ed errori di validazione.

Lato controller

// app/Http/Controllers/PostController.php
class PostController extends Controller
{
    public function store(Request $request)
    {
        $validated = $request->validate([
            'title'   => ['required', 'string', 'max:255'],
            'content' => ['required', 'string'],
        ]);

        Post::create($validated + ['user_id' => auth()->id()]);

        return redirect()->route('posts.index')
            ->with('success', 'Post creato.');
    }
}

Componente form Svelte

<!-- resources/js/pages/posts/Create.svelte -->
<script lang="ts">
    import { useForm } from '@inertiajs/svelte'

    const form = useForm({
        title: '',
        content: '',
    })

    function submit(e: SubmitEvent) {
        e.preventDefault()
        form.post('/posts')
    }
</script>

<form onsubmit={submit}>
    <div>
        <label>Titolo</label>
        <input type="text" bind:value={form.data.title} />
        {#if form.errors.title}
            <p class="error">{form.errors.title}</p>
        {/if}
    </div>

    <div>
        <label>Contenuto</label>
        <textarea bind:value={form.data.content}></textarea>
        {#if form.errors.content}
            <p class="error">{form.errors.content}</p>
        {/if}
    </div>

    <button type="submit" disabled={form.processing}>
        {form.processing ? 'Invio in corso...' : 'Pubblica'}
    </button>
</form>
Ecco le principali proprietà restituite da useForm.
Proprietà / MetodoDescrizione
form.dataOggetto dati del form
form.errorsErrori di validazione (accesso per nome campo)
form.processingtrue durante l’invio (per disabilitare il pulsante)
form.isDirtytrue se ci sono modifiche rispetto ai valori iniziali
form.post(url)Invia con richiesta POST
form.put(url)Invia con richiesta PUT (update)
form.delete(url)Invia con richiesta DELETE
form.reset()Ripristina il form ai valori iniziali
Quando arrivano errori di validazione, useForm mantiene i contenuti inseriti mostrando gli errori. Combinato con bind:value offre un’esperienza di form senza interruzioni.

Dati condivisi (Shared Data)

I dati necessari a tutte le pagine (utente loggato, messaggi flash, ecc.) si definiscono nel metodo share() del middleware HandleInertiaRequests.
// app/Http/Middleware/HandleInertiaRequests.php
use Illuminate\Http\Request;
use Inertia\Middleware;

class HandleInertiaRequests extends Middleware
{
    public function share(Request $request): array
    {
        return array_merge(parent::share($request), [
            'auth' => [
                'user' => $request->user()
                    ? $request->user()->only('id', 'name', 'email')
                    : null,
            ],
            'flash' => [
                'success' => $request->session()->get('success'),
                'error'   => $request->session()->get('error'),
            ],
        ]);
    }
}
Per accedere ai dati condivisi da un componente Svelte usa usePage().
<script lang="ts">
    import { usePage } from '@inertiajs/svelte'

    type SharedProps = {
        auth: {
            user: { id: number; name: string; email: string } | null
        }
        flash: {
            success: string | null
            error: string | null
        }
    }

    const page = usePage<SharedProps>()
</script>

<header>
    {#if page.props.auth.user}
        <span>{page.props.auth.user.name}</span>
    {:else}
        <span>Ospite</span>
    {/if}
</header>

{#if page.props.flash.success}
    <div class="alert-success">{page.props.flash.success}</div>
{/if}
Poiché i dati condivisi sono inclusi in ogni richiesta, si consiglia di limitarli allo stretto necessario. Con valutazioni lazy tramite fn(), vengono valutati solo quando ci si accede davvero.

Reattività di Svelte 5 (Runes)

Ecco le rune di Svelte 5 essenziali per sviluppare con Inertia × Svelte.

$state — stato reattivo

<script lang="ts">
    let count = $state(0)
    let isOpen = $state(false)
    let items = $state<string[]>([])
</script>

<p>{count}</p>
<button onclick={() => count++}>+1</button>
<button onclick={() => isOpen = !isOpen}>Toggle</button>
Le variabili dichiarate con $state sono automaticamente reattive. Al cambio di valore il DOM si aggiorna in automatico. Corrisponde a ref di Vue o useState di React, ma non serve accedere alla proprietà .value: aggiornare lo stato equivale a fare l’assegnazione.

$derived — valori derivati

<script lang="ts">
    let posts = $state<{ title: string; published: boolean }[]>([])

    // Estrae solo i post con published = true
    let publishedPosts = $derived(posts.filter(post => post.published))

    // Quando ci sono più dipendenze
    let summary = $derived.by(() => {
        const total = posts.length
        const published = publishedPosts.length
        return `${published} di ${total} pubblicati`
    })
</script>

<p>{summary}</p>
$derived viene ricalcolato automaticamente quando cambia una delle dipendenze. Corrisponde a computed di Vue o useMemo di React.

$effect — effetti collaterali

<script lang="ts">
    let query = $state('')

    // Eseguito ogni volta che cambia query
    $effect(() => {
        console.log('la query di ricerca è cambiata:', query)

        // Puoi restituire una funzione di cleanup
        return () => {
            console.log('cleanup')
        }
    })
</script>

<input bind:value={query} placeholder="Cerca..." />
$effect si esegue ogni volta che cambia un valore $state da cui dipende. Corrisponde a useEffect di React, ma non devi indicare esplicitamente un array di dipendenze: le variabili $state usate vengono tracciate automaticamente.

Componenti shadcn-svelte

Gli starter kit includono shadcn-svelte. shadcn-svelte è una libreria di componenti con la stessa filosofia di design di shadcn/ui per React: il codice viene copiato nel progetto e puoi personalizzarlo liberamente.

Aggiungere componenti

npx shadcn-svelte@latest add button
npx shadcn-svelte@latest add input
npx shadcn-svelte@latest add card
Eseguendo il comando, il codice sorgente del componente viene posizionato in resources/js/components/ui/.

Utilizzo

<script lang="ts">
    import { Button } from '@/components/ui/button'
    import { Input } from '@/components/ui/input'
    import * as Card from '@/components/ui/card'
    import { useForm } from '@inertiajs/svelte'

    const form = useForm({ email: '', password: '' })

    function submit(e: SubmitEvent) {
        e.preventDefault()
        form.post('/login')
    }
</script>

<Card.Root class="w-96">
    <Card.Header>
        <Card.Title>Login</Card.Title>
    </Card.Header>
    <Card.Content>
        <form onsubmit={submit} class="space-y-4">
            <Input
                type="email"
                bind:value={form.data.email}
                placeholder="Email"
            />
            {#if form.errors.email}
                <p class="text-sm text-red-500">{form.errors.email}</p>
            {/if}

            <Input
                type="password"
                bind:value={form.data.password}
                placeholder="Password"
            />

            <Button type="submit" disabled={form.processing} class="w-full">
                {form.processing ? 'Login in corso...' : 'Accedi'}
            </Button>
        </form>
    </Card.Content>
</Card.Root>
Gli starter kit contengono già componenti d’uso comune come Button, Input, Card, Dialog e Dropdown. Quelli che vuoi aggiungere puoi introdurli in qualsiasi momento con npx shadcn-svelte@latest add.

Conclusioni

Svelte, in combinazione con Laravel, dà il meglio nell’architettura “monolite moderno” tramite Inertia. Grazie al design basato su compilatore, il bundle è piccolo e, con la sintassi delle rune, la reattività è esplicita e facile da comprendere.
ElementoRuolo
Controller LaravelRouting, recupero dati e validazione
Inertia::render()Passa i dati dal controller al componente Svelte
Componente pagina SvelteRiceve le props con $props() e renderizza la UI
useFormGestione stato, invio ed errori del form
Componente LinkTransizioni di pagina senza full reload
usePage().propsAccesso ai dati condivisi
shadcn-svelteLibreria di componenti standard
Con Inertia × Svelte ottieni un’esperienza di sviluppo che unisce la semplicità del backend Laravel a uno stile di scrittura compatto. Creando il progetto tramite starter kit, puoi iniziare subito con schermate di autenticazione incluse.

Documentazione ufficiale di Inertia.js

Per tutte le funzionalità di Inertia v3 consulta la documentazione ufficiale.
Ultima modifica il 13 luglio 2026