Passer au contenu principal

Qu’est-ce que Svelte ?

Svelte occupe une place à part parmi les frameworks JavaScript. Alors que React et Vue fonctionnent comme des bibliothèques d’exécution, Svelte est un compilateur : au moment du build, les composants sont transformés en pur JavaScript, ce qui évite d’envoyer un runtime de framework au navigateur. Le point le plus caractéristique est l’absence de Virtual DOM. Quand l’état change, le code généré à la compilation met à jour directement le DOM. Il en résulte une UI très légère et rapide.
Cette page présente la combinaison Svelte 5 + Inertia v3, qui est la configuration par défaut du starter kit Laravel 13.

Les runes de Svelte 5

Svelte 5 (sorti en 2024) introduit un nouveau système de réactivité appelé runes. On déclare l’état réactif via des fonctions spéciales ($state, $derived, $effect, etc.). Contrairement à la réactivité implicite des versions antérieures, cette approche est explicite et plus prévisible.
<script lang="ts">
    let count = $state(0)

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

<button onclick={increment}>{count}</button>
Le starter kit Svelte de Laravel utilise Svelte 5 + TypeScript par défaut. Tous les exemples de cette page sont en TypeScript (lang="ts").

Place de Svelte dans Laravel

Historique

La relation entre Svelte et Laravel est très récente comparée à Vue et React : l’intégration officielle passe par les starter kits. En 2026, avec Laravel 13, Svelte devient officiellement l’une des options frontend de l’écosystème Laravel. Il figure au même titre que Vue et React dans le prompt interactif de laravel new. Ce framework reste peu connu des utilisateurs Laravel, mais il se distingue par une taille de bundle réduite grâce au compilateur et une syntaxe simple : en venant de Vue ou React, la quantité de code à écrire surprend souvent par sa concision.

Style dominant actuel : Inertia × Svelte

L’usage principal de Svelte avec Laravel est Inertia × Svelte. Inertia offre une architecture « monolithe moderne » où les données du contrôleur Laravel sont directement passées au composant Svelte, sans avoir à concevoir une API.

Mise en place

Via un starter kit (recommandé)

Le plus simple pour un nouveau projet.
laravel new my-app
En choisissant Svelte dans le prompt, tout est configuré automatiquement :
  • inertiajs/inertia-laravel (adaptateur serveur)
  • @inertiajs/svelte (adaptateur client)
  • svelte + @sveltejs/vite-plugin-svelte (Svelte 5 et plugin Vite)
  • TypeScript + svelte-check
  • Tailwind CSS + shadcn-svelte
  • Middleware HandleInertiaRequests
  • Écrans d’authentification (déjà implémentés en Inertia + Svelte + TypeScript)

Installation manuelle

Pour un projet existant, installez séparément côté serveur et côté client.
# Côté serveur (PHP)
composer require inertiajs/inertia-laravel

# Côté client (JavaScript)
npm install @inertiajs/svelte @inertiajs/vite svelte
npm install --save-dev @sveltejs/vite-plugin-svelte svelte-check typescript
Ajoutez le plugin Svelte et le plugin Vite d’Inertia à vite.config.ts.
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(),
    ],
})
Démarrez l’application Inertia dans resources/js/app.ts. Le plugin @inertiajs/vite prenant en charge la résolution automatique des pages et le montage, l’entrée peut se limiter au strict minimum.
import { createInertiaApp } from '@inertiajs/svelte'

createInertiaApp()
Pour les détails (template racine, enregistrement du middleware, etc.), consultez la documentation officielle Inertia.

Structure des répertoires

Dans le starter kit, les composants de page Svelte sont placés dans resources/js/pages/.
resources/js/
├── app.ts             # Point d'entrée de l'application Inertia
├── components/        # Composants UI réutilisables
│   └── ui/            # Composants shadcn-svelte
├── layouts/           # Composants de layout
│   ├── AppLayout.svelte
│   └── AuthLayout.svelte
├── lib/               # Utilitaires et modules runes Svelte
├── pages/             # Composants de page Inertia (correspondent au contrôleur)
│   ├── auth/
│   │   ├── Login.svelte
│   │   └── Register.svelte
│   ├── Dashboard.svelte
│   └── posts/
│       ├── Index.svelte
│       ├── Create.svelte
│       └── Show.svelte
└── types/             # Définitions de types TypeScript
Inertia::render('posts/Index', [...]) correspond au composant resources/js/pages/posts/Index.svelte.

Structure d’un fichier Svelte

Un fichier .svelte se compose de trois blocs : <script>, template et <style>.
<script lang="ts">
    // Logique (TypeScript)
    let name = $state('Laravel')
</script>

<!-- Template (syntaxe HTML) -->
<h1>Bonjour, {name} !</h1>

<style>
    /* CSS scopé (limité au composant) */
    h1 {
        color: #ff2d20;
    }
</style>
C’est proche des SFC de Vue, mais avec encore moins de code à écrire. Le <style> étant scopé par défaut, on ne se soucie plus des collisions de noms de classes.

Syntaxe du template

Interpolation et expressions

Dans le template, {} intègre une valeur ou une expression JavaScript.
<script lang="ts">
    let name = $state('le monde')
    let count = $state(3)
</script>

<p>Bonjour, {name} !</p>
<p>Le double : {count * 2}</p>

{#if} — branchement

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

{#if isLoggedIn}
    <p>Bienvenue !</p>
{:else if role === 'admin'}
    <p>Connecté en tant qu'administrateur</p>
{:else}
    <a href="/login">Connexion</a>
{/if}
Équivalent au v-if / v-else de Vue et au ternaire de React.

{#each} — rendu de liste

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

<ul>
    {#each posts as post (post.id)}
        <li>{post.title}</li>
    {/each}
</ul>
(post.id) sert de clé et permet un diff efficace. Équivalent au v-for de Vue et au Array.map() de React.

bind: — binding bidirectionnel

bind:value synchronise la valeur d’un élément de formulaire avec une variable réactive dans les deux sens.
<script lang="ts">
    let title = $state('')
    let agreed = $state(false)
    let role = $state('viewer')
</script>

<!-- Champ texte -->
<input bind:value={title} type="text" />
<p>Saisie : {title}</p>

<!-- Case à cocher -->
<input bind:checked={agreed} type="checkbox" />
<p>Accord : {agreed}</p>

<!-- Liste déroulante -->
<select bind:value={role}>
    <option value="viewer">Lecteur</option>
    <option value="editor">Éditeur</option>
    <option value="admin">Administrateur</option>
</select>
Équivalent au v-model de Vue. En React, il fallait écrire un onChange ; Svelte l’exprime de manière déclarative avec bind:.

Composant de page — bases

Un composant de page Inertia est un composant Svelte classique. Les données passées par le contrôleur arrivent en props.

Contrôleur

// 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),
        ]);
    }
}

Composant de page Svelte

En Svelte 5, on reçoit les props avec la rune $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>Liste des articles</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>
Il suffit de récupérer les props via $props() pour utiliser les données transmises par le contrôleur. Aucune API REST à définir.
Le composant <Link> de @inertiajs/svelte déclenche la navigation en XHR sans rechargement complet.
<script lang="ts">
    import { Link } from '@inertiajs/svelte'
</script>

<!-- Lien de base -->
<Link href="/posts">Liste des articles</Link>

<!-- Lien POST (suppression, etc.) -->
<Link href="/posts/1" method="delete" as="button">
    Supprimer
</Link>

<!-- Préchargement au survol -->
<Link href="/posts/1" preload>Voir l'article</Link>
L’écriture est identique à celle d’une balise <a>, mais seul le composant de page est remplacé en coulisses, offrant une expérience SPA.

Composant Form

Le composant <Form> de @inertiajs/svelte est l’écriture recommandée dans les écrans d’authentification du starter kit. On passe action et method en props, et le contenu se décrit avec {#snippet}.

Utilisation de 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">Titre</label>
            <input id="title" name="title" type="text" required />
            {#if errors.title}
                <p class="error">{errors.title}</p>
            {/if}
        </div>

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

        <button type="submit" disabled={processing}>
            {processing ? 'Envoi...' : 'Publier'}
        </button>
    {/snippet}
</Form>
{#snippet children({ errors, processing })} est la syntaxe de snippet Svelte — un bloc de contenu passé au composant (équivalent des slots de Vue ou des render props de React). Les valeurs errors et processing sont calculées et fournies par le composant Form. Les champs utilisent l’attribut natif name plutôt que bind:value, ce qui active la collecte standard des données de formulaire par le navigateur.

Patron du starter kit

Le starter kit utilise Wayfinder pour gérer les routes en tant qu’objets. store.form() renvoie un objet contenant action et method que l’on spread dans <Form>.
<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 })}
        <!-- Contenu du formulaire -->
    {/snippet}
</Form>
Les champs listés dans resetOnSuccess sont réinitialisés automatiquement après un envoi réussi. Utile pour les champs de mot de passe à vider.
Sans Wayfinder, action="/login" fonctionne de la même façon.

Hook useForm

Pour la gestion de formulaires, utilisez le hook useForm de @inertiajs/svelte.

Côté contrôleur

// 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', 'Article créé.');
    }
}

Composant de formulaire 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>Titre</label>
        <input type="text" bind:value={form.data.title} />
        {#if form.errors.title}
            <p class="error">{form.errors.title}</p>
        {/if}
    </div>

    <div>
        <label>Contenu</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 ? 'Envoi...' : 'Publier'}
    </button>
</form>
Principales propriétés de l’objet retourné par useForm.
Propriété / MéthodeDescription
form.dataObjet des données du formulaire
form.errorsErreurs de validation (par nom de champ)
form.processingtrue pendant l’envoi
form.isDirtytrue si la valeur diffère de l’initiale
form.post(url)Envoi POST
form.put(url)Envoi PUT (mise à jour)
form.delete(url)Envoi DELETE
form.reset()Réinitialiser le formulaire aux valeurs initiales
En cas d’erreur de validation, useForm conserve les valeurs saisies. Combiné à bind:value, l’expérience de formulaire est fluide.

Données partagées (Shared Data)

Les données communes à toutes les pages (utilisateur connecté, messages flash) sont déclarées dans la méthode share() du 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'),
            ],
        ]);
    }
}
Depuis un composant Svelte, on accède à ces données partagées via 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>Invité</span>
    {/if}
</header>

{#if page.props.flash.success}
    <div class="alert-success">{page.props.flash.success}</div>
{/if}
Ces données étant présentes à chaque requête, limitez-les au strict nécessaire. Utilisez une évaluation paresseuse avec fn() pour qu’elles ne soient évaluées qu’en cas d’accès.

Réactivité de Svelte 5 (runes)

Les runes essentielles pour développer avec Inertia × Svelte.

$state — état réactif

<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}>Basculer</button>
Une variable déclarée avec $state est automatiquement réactive : lorsque sa valeur change, le DOM est mis à jour. C’est l’équivalent de ref en Vue ou useState en React, mais sans .value : il suffit d’une affectation pour mettre à jour.

$derived — valeur dérivée

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

    // On extrait uniquement les articles publiés
    let publishedPosts = $derived(posts.filter(post => post.published))

    // Plusieurs dépendances
    let summary = $derived.by(() => {
        const total = posts.length
        const published = publishedPosts.length
        return `${published} publié(s) sur ${total} au total`
    })
</script>

<p>{summary}</p>
$derived recalcule automatiquement lorsque ses dépendances changent. Équivalent à computed en Vue ou useMemo en React.

$effect — effets de bord

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

    // Exécuté à chaque changement de query
    $effect(() => {
        console.log('Nouvelle requête :', query)

        // Une fonction de nettoyage peut être retournée
        return () => {
            console.log('Nettoyage')
        }
    })
</script>

<input bind:value={query} placeholder="Recherche..." />
$effect se déclenche à chaque variation d’un $state utilisé. Équivalent à useEffect en React, mais sans tableau de dépendances : les variables $state utilisées sont détectées automatiquement.

Composants shadcn-svelte

Le starter kit intègre shadcn-svelte. Cette bibliothèque a le même esprit que shadcn/ui pour React : vous copiez le code dans le projet et le personnalisez librement.

Ajouter des composants

npx shadcn-svelte@latest add button
npx shadcn-svelte@latest add input
npx shadcn-svelte@latest add card
Ces commandes déposent le code du composant dans resources/js/components/ui/.

Utilisation

<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>Connexion</Card.Title>
    </Card.Header>
    <Card.Content>
        <form onsubmit={submit} class="space-y-4">
            <Input
                type="email"
                bind:value={form.data.email}
                placeholder="Adresse e-mail"
            />
            {#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="Mot de passe"
            />

            <Button type="submit" disabled={form.processing} class="w-full">
                {form.processing ? 'Connexion...' : 'Se connecter'}
            </Button>
        </form>
    </Card.Content>
</Card.Root>
Le starter kit inclut déjà Button, Input, Card, Dialog, Dropdown, etc. Ajoutez d’autres composants au besoin avec npx shadcn-svelte@latest add.

Conclusion

Svelte s’exprime particulièrement bien avec Laravel dans un « monolithe moderne » via Inertia. Son bundle réduit grâce au compilateur et la clarté des runes en font un choix pertinent.
ÉlémentRôle
Contrôleur LaravelRoutage, récupération de données, validation
Inertia::render()Transmet les données du contrôleur au composant Svelte
Composant de page SvelteReçoit les props via $props() et rend l’UI
useFormGestion d’état, envoi et erreurs de formulaire
Composant LinkNavigation sans rechargement
usePage().propsAccès aux données partagées
shadcn-svelteBibliothèque de composants standard
Inertia × Svelte combine la simplicité du backend Laravel à l’écriture compacte de Svelte. Le starter kit permet de démarrer immédiatement, écrans d’authentification inclus.

Documentation officielle Inertia.js

Consultez la documentation officielle pour toutes les fonctionnalités d’Inertia v3.
Dernière modification le 13 juillet 2026