> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Introductie Svelte — de basis voor Inertia × Laravel

> Een beginnersgids voor het gebruik van Svelte met Laravel + Inertia.js. Van de compilergebaseerde aanpak en de runes-syntaxis van Svelte 5 tot Inertia Svelte-hooks zoals useForm en usePage, praktisch uitgelegd.

## Wat is Svelte

[Svelte](https://svelte.dev/) neemt een unieke positie in onder de JavaScript-frameworks. Waar React en Vue als **runtimelibrary's** werken, werkt Svelte als **compiler**. Componenten worden tijdens de build omgezet naar pure JavaScript, waardoor er geen overbodige frameworkcode naar de browser hoeft te worden gestuurd.

Het grootste kenmerk van Svelte is dat het **geen virtual DOM gebruikt**. Wanneer de state verandert, werkt de code die Svelte tijdens het compileren heeft gegenereerd de DOM direct bij. Dit maakt een extreem lichte en snelle UI mogelijk.

<Info>
  Deze pagina behandelt de combinatie van Svelte 5 en Inertia v3. De starter kits van Laravel 13 gebruiken deze opzet standaard.
</Info>

### De runes van Svelte 5

Svelte 5 (uitgebracht in 2024) introduceerde een nieuw reactiviteitssysteem genaamd **runes**. Je declareert reactieve state met speciale functies (runes) zoals `$state`, `$derived` en `$effect`. Anders dan de impliciete reactiviteit van Svelte 4 en eerder is dit ontwerp expliciet en voorspelbaar.

```svelte theme={null}
<script lang="ts">
    let count = $state(0)

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

<button onclick={increment}>{count}</button>
```

<Tip>
  De Svelte starter kit van Laravel gebruikt standaard Svelte 5 + TypeScript. Alle voorbeelden op deze pagina zijn daarom in TypeScript (`lang="ts"`) geschreven.
</Tip>

***

## De positie binnen Laravel

### Geschiedenis

De relatie tussen Svelte en Laravel is vergeleken met Vue en React nog erg jong: de opname in de **officiële starter kits** is de eerste officiële ondersteuning.

```mermaid theme={null}
timeline
    title Laravel en Svelte door de jaren heen
    2016 : Svelte 1 uitgebracht (gemaakt door Rich Harris)
    2019 : Svelte 3 — vernieuwd met een compilergebaseerd ontwerp
    2021 : Inertia.js — start van de Svelte-adapter door de community
    2024 : Svelte 5 — introductie van de runes-syntaxis
    2026 : Laravel 13 — Svelte starter kit officieel toegevoegd (met Inertia v3)
```

Met de officiële toevoeging van Svelte aan de starter kits in **Laravel 13 (2026)** werd Svelte een van de officiële frontendkeuzes binnen het Laravel-ecosysteem. Het wordt op gelijke voet met Vue en React behandeld en is te selecteren in de interactieve prompt van `laravel new`.

Het framework is bij Laravel-gebruikers nog niet zo bekend, maar het kenmerkt zich door een **lichte bundel dankzij de compiler** en een **eenvoudige syntaxis** — stap je over van Vue of React, dan zul je misschien verbaasd zijn hoe weinig je hoeft te schrijven.

### De huidige mainstream: Inertia × Svelte

De belangrijkste manier om Svelte in Laravel te gebruiken is tegenwoordig **Inertia × Svelte**. Inertia realiseert een "moderne monoliet"-architectuur waarbij je zonder een API te ontwerpen data rechtstreeks vanuit Laravel-controllers aan Svelte-componenten doorgeeft.

```mermaid theme={null}
graph LR
    Browser["Browser"]
    Inertia["Inertia.js<br>(adapterlaag)"]
    Laravel["Laravel<br>(controller)"]
    Svelte["Svelte<br>(paginacomponent)"]

    Browser <-->|XHR / volledige paginalading| Inertia
    Inertia <-->|Inertia-response| Laravel
    Inertia -->|props| Svelte
    Svelte -->|rendering| Browser
```

***

## Setup

### Via een starter kit (aanbevolen)

Voor een nieuw project is een starter kit de makkelijkste route.

```shell theme={null}
laravel new my-app
```

Kies je in de interactieve prompt voor **Svelte**, dan wordt dit allemaal automatisch opgezet:

* `inertiajs/inertia-laravel` (server-side adapter)
* `@inertiajs/svelte` (clientadapter)
* `svelte` + `@sveltejs/vite-plugin-svelte` (Svelte 5 zelf en de Vite-plugin)
* TypeScript + `svelte-check`
* Tailwind CSS + de shadcn-svelte-componentlibrary
* De middleware `HandleInertiaRequests`
* Authenticatieschermen zoals inloggen en registreren (al geïmplementeerd met Inertia + Svelte + TypeScript)

### Handmatige installatie

Voeg je Inertia toe aan een bestaand project, dan installeer je de server- en clientzijde afzonderlijk.

```shell theme={null}
# Serverzijde (PHP)
composer require inertiajs/inertia-laravel

# Clientzijde (JavaScript)
npm install @inertiajs/svelte @inertiajs/vite svelte
npm install --save-dev @sveltejs/vite-plugin-svelte svelte-check typescript
```

Voeg vervolgens de Svelte-plugin en de Inertia Vite-plugin toe aan `vite.config.ts`.

```ts theme={null}
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(),
    ],
})
```

Start de Inertia-app in `resources/js/app.ts`. Omdat de `@inertiajs/vite`-plugin het automatisch oplossen en mounten van pagina's verzorgt, volstaat een minimaal entrypoint.

```ts theme={null}
import { createInertiaApp } from '@inertiajs/svelte'

createInertiaApp()
```

<Info>
  Zie de [officiële Inertia-documentatie](https://inertiajs.com/installation) voor de details van een handmatige installatie (zoals het instellen van de roottemplate en het registreren van de middleware).
</Info>

***

## Directorystructuur

In de starter kits plaats je Svelte-paginacomponenten in de directory `resources/js/pages/`.

```
resources/js/
├── app.ts             # Startpunt van de Inertia-app
├── components/        # Herbruikbare UI-componenten
│   └── ui/            # shadcn-svelte-componenten
├── layouts/           # Layoutcomponenten
│   ├── AppLayout.svelte
│   └── AuthLayout.svelte
├── lib/               # Utilityfuncties en Svelte-runemodules
├── pages/             # Inertia-paginacomponenten (corresponderen met controllernamen)
│   ├── auth/
│   │   ├── Login.svelte
│   │   └── Register.svelte
│   ├── Dashboard.svelte
│   └── posts/
│       ├── Index.svelte
│       ├── Create.svelte
│       └── Show.svelte
└── types/             # TypeScript-typedefinities
```

Schrijf je `Inertia::render('posts/Index', [...])`, dan is `resources/js/pages/posts/Index.svelte` de bijbehorende component.

***

## De basisstructuur van een Svelte-bestand

Een `.svelte`-bestand bestaat uit drie blokken: **`<script>`, template en `<style>`**.

```svelte theme={null}
<script lang="ts">
    // Logica (TypeScript)
    let name = $state('Laravel')
</script>

<!-- Template (HTML-achtige notatie) -->
<h1>Hello, {name}!</h1>

<style>
    /* Scoped CSS (geldt alleen voor deze component) */
    h1 {
        color: #ff2d20;
    }
</style>
```

De structuur lijkt op een Single File Component (SFC) van Vue, maar kenmerkend is dat je in de template en het script minder hoeft te schrijven. Omdat `<style>` standaard scoped is, hoef je je geen zorgen te maken over botsende klassenamen.

### Templatesyntaxis

#### Variabelen en expressies invoegen

Binnen de template gebruik je `{}` om JavaScript-waarden of -expressies in te voegen.

```svelte theme={null}
<script lang="ts">
    let name = $state('wereld')
    let count = $state(3)
</script>

<p>Hallo, {name}!</p>
<p>Het dubbele is {count * 2}</p>
```

#### `{#if}` — voorwaarden

```svelte theme={null}
<script lang="ts">
    let isLoggedIn = $state(false)
    let role = $state('editor')
</script>

{#if isLoggedIn}
    <p>Welkom!</p>
{:else if role === 'admin'}
    <p>Ingelogd als beheerder</p>
{:else}
    <a href="/login">Inloggen</a>
{/if}
```

Dit komt overeen met `v-if` / `v-else` in Vue of de ternaire operator in React.

#### `{#each}` — lijsten renderen

```svelte theme={null}
<script lang="ts">
    type Post = { id: number; title: string }
    let posts = $state<Post[]>([
        { id: 1, title: 'Eerste post' },
        { id: 2, title: 'Tweede post' },
    ])
</script>

<ul>
    {#each posts as post (post.id)}
        <li>{post.title}</li>
    {/each}
</ul>
```

`(post.id)` is de key-aanduiding, die wordt gebruikt voor efficiënte diff-updates. Dit komt overeen met `v-for` in Vue of `Array.map()` in React.

#### `bind:` — two-way binding

Met `bind:value` synchroniseer je de waarde van een formulierelement in twee richtingen met een reactieve variabele.

```svelte theme={null}
<script lang="ts">
    let title = $state('')
    let agreed = $state(false)
    let role = $state('viewer')
</script>

<!-- Tekstinvoer -->
<input bind:value={title} type="text" />
<p>Invoer: {title}</p>

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

<!-- Selectbox -->
<select bind:value={role}>
    <option value="viewer">Lezer</option>
    <option value="editor">Redacteur</option>
    <option value="admin">Beheerder</option>
</select>
```

Dit komt overeen met `v-model` in Vue. In React moest je zelf een `onChange`-handler schrijven, maar in Svelte doe je dit declaratief met `bind:`.

***

## De basis van paginacomponenten

Een Inertia-paginacomponent is een gewone Svelte-component. De data die je vanuit de Laravel-controller doorgeeft, ontvang je als props.

### Controller

```php theme={null}
// 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),
        ]);
    }
}
```

### Svelte-paginacomponent

In Svelte 5 ontvang je props met de rune `$props()`.

```svelte theme={null}
<!-- 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>Postoverzicht</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>
```

Je ontvangt de props simpelweg met `$props()` en kunt de data uit de controller direct in de template gebruiken. Een REST API definiëren is niet nodig.

***

## De `Link`-component

Met de `<Link>`-component van `@inertiajs/svelte` verlopen paginanavigaties via XHR, zodat een volledige browserreload wordt vermeden.

```svelte theme={null}
<script lang="ts">
    import { Link } from '@inertiajs/svelte'
</script>

<!-- Basislink -->
<Link href="/posts">Postoverzicht</Link>

<!-- Link met de POST-methode (bijv. verwijderen) -->
<Link href="/posts/1" method="delete" as="button">
    Verwijderen
</Link>

<!-- Preload (vooraf ophalen bij hover) -->
<Link href="/posts/1" preload>Post bekijken</Link>
```

Je schrijft hem net als een gewone `<a>`-tag, maar op de achtergrond vervangt Inertia alleen de paginacomponent, waardoor het aanvoelt als een SPA.

***

## De `Form`-component

De `<Form>`-component van `@inertiajs/svelte` is de aanbevolen stijl voor formulierverzending die in de authenticatieschermen van de starter kits wordt gebruikt. Je geeft `action` en `method` op als props en schrijft de inhoud met **`{#snippet}`**.

### Basisgebruik

```svelte theme={null}
<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">Titel</label>
            <input id="title" name="title" type="text" required />
            {#if errors.title}
                <p class="error">{errors.title}</p>
            {/if}
        </div>

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

        <button type="submit" disabled={processing}>
            {processing ? 'Versturen...' : 'Posten'}
        </button>
    {/snippet}
</Form>
```

`{#snippet children({ errors, processing })}` is de snippet-syntaxis van Svelte: een contentblok dat je aan een component doorgeeft (vergelijkbaar met slots in Vue of render props in React). De `Form`-component berekent `errors` en `processing` automatisch en geeft ze door. Voor de formuliervelden gebruik je geen `bind:value` maar het native HTML-attribuut `name`, zodat de standaard formulierdataverzameling van de browser werkt.

### Het patroon van de starter kits

De starter kits gebruiken [Wayfinder](/nl/blog/wayfinder-introduction) om routes als objecten te beheren. `store.form()` geeft een object terug met de `action` en `method` van het routeobject, dat je in `<Form>` spreadt.

```svelte theme={null}
<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 })}
        <!-- Inhoud van het formulier -->
    {/snippet}
</Form>
```

Velden die je opgeeft in `resetOnSuccess` worden bij een geslaagde verzending automatisch gereset. Gebruik dit voor velden die je na het verzenden leeg wilt maken, zoals wachtwoordvelden.

<Info>
  Gebruik je Wayfinder niet, dan werkt het net zo goed als je direct een URL doorgeeft, zoals `action="/login"`.
</Info>

***

## De `useForm`-hook

Voor formulierverwerking gebruik je de `useForm`-hook van `@inertiajs/svelte`. Statebeheer van het formulier, verzenden en het tonen van validatiefouten implementeer je hiermee eenvoudig.

### Aan de controllerkant

```php theme={null}
// 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 aangemaakt.');
    }
}
```

### Svelte-formuliercomponent

```svelte theme={null}
<!-- 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>Titel</label>
        <input type="text" bind:value={form.data.title} />
        {#if form.errors.title}
            <p class="error">{form.errors.title}</p>
        {/if}
    </div>

    <div>
        <label>Inhoud</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 ? 'Versturen...' : 'Posten'}
    </button>
</form>
```

De belangrijkste properties van het object dat `useForm` teruggeeft:

| Property / methode | Beschrijving                                                           |
| ------------------ | ---------------------------------------------------------------------- |
| `form.data`        | Het dataobject van het formulier                                       |
| `form.errors`      | Validatiefouten (toegankelijk per veldnaam)                            |
| `form.processing`  | `true` tijdens het verzenden (gebruik dit om de knop uit te schakelen) |
| `form.isDirty`     | `true` als er iets is gewijzigd ten opzichte van de beginwaarden       |
| `form.post(url)`   | Verzenden met een POST-request                                         |
| `form.put(url)`    | Verzenden met een PUT-request (bijwerken)                              |
| `form.delete(url)` | Verzenden met een DELETE-request                                       |
| `form.reset()`     | Zet het formulier terug naar de beginwaarden                           |

Komt er een validatiefout terug, dan toont `useForm` de fouten terwijl de ingevoerde inhoud behouden blijft. In combinatie met `bind:value` krijg je een naadloze formulierervaring.

***

## Gedeelde data (shared data)

Data die je op elke pagina nodig hebt (zoals de ingelogde gebruiker en flashberichten) definieer je in de methode `share()` van de middleware `HandleInertiaRequests`.

```php theme={null}
// 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'),
            ],
        ]);
    }
}
```

Om vanuit een Svelte-component bij de gedeelde data te komen, gebruik je `usePage()`.

```svelte theme={null}
<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>Gast</span>
    {/if}
</header>

{#if page.props.flash.success}
    <div class="alert-success">{page.props.flash.success}</div>
{/if}
```

<Info>
  Gedeelde data wordt bij elke request meegestuurd; beperk deze daarom tot het strikt noodzakelijke. Gebruik je lazy evaluation met `fn()`, dan wordt de data alleen geëvalueerd wanneer er daadwerkelijk toegang toe is.
</Info>

***

## De reactiviteit van Svelte 5 (runes)

Hier zijn de runes van Svelte 5 die je moet kennen om met Inertia × Svelte te ontwikkelen.

### `$state` — reactieve state

```svelte theme={null}
<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>
```

Variabelen die je declareert met `$state` worden automatisch reactief. Verandert de waarde, dan wordt de DOM automatisch bijgewerkt. Dit komt overeen met `ref` in Vue of `useState` in React, maar zonder dat je een `.value`-property nodig hebt: een gewone toewijzing werkt de state al bij.

### `$derived` — afgeleide waarden

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

    // Alleen de posts filteren waarvan published true is
    let publishedPosts = $derived(posts.filter(post => post.published))

    // Bij meerdere afhankelijkheden
    let summary = $derived.by(() => {
        const total = posts.length
        const published = publishedPosts.length
        return `${published} van de ${total} gepubliceerd`
    })
</script>

<p>{summary}</p>
```

`$derived` wordt automatisch opnieuw berekend wanneer de waarden waarvan het afhangt veranderen. Dit komt overeen met `computed` in Vue of `useMemo` in React.

### `$effect` — side effects verwerken

```svelte theme={null}
<script lang="ts">
    let query = $state('')

    // Wordt uitgevoerd telkens wanneer query verandert
    $effect(() => {
        console.log('De zoekquery is veranderd:', query)

        // Je kunt een cleanup-functie teruggeven
        return () => {
            console.log('Cleanup')
        }
    })
</script>

<input bind:value={query} placeholder="Zoeken..." />
```

`$effect` wordt uitgevoerd telkens wanneer een `$state`-waarde waarvan het afhangt verandert. Het komt overeen met `useEffect` in React, maar je hoeft geen dependency-array op te geven: de gebruikte `$state`-variabelen worden automatisch gevolgd.

***

## shadcn-svelte-componenten

De starter kit bevat [shadcn-svelte](https://www.shadcn-svelte.com/). shadcn-svelte is een componentlibrary met dezelfde ontwerpfilosofie als shadcn/ui voor React: de code wordt naar je project gekopieerd en je kunt hem vrij aanpassen.

### Componenten toevoegen

```shell theme={null}
npx shadcn-svelte@latest add button
npx shadcn-svelte@latest add input
npx shadcn-svelte@latest add card
```

Na het uitvoeren van het commando wordt de broncode van de component geplaatst in `resources/js/components/ui/`.

### Gebruik

```svelte theme={null}
<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>Inloggen</Card.Title>
    </Card.Header>
    <Card.Content>
        <form onsubmit={submit} class="space-y-4">
            <Input
                type="email"
                bind:value={form.data.email}
                placeholder="E-mailadres"
            />
            {#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="Wachtwoord"
            />

            <Button type="submit" disabled={form.processing} class="w-full">
                {form.processing ? 'Inloggen...' : 'Inloggen'}
            </Button>
        </form>
    </Card.Content>
</Card.Root>
```

De starter kit bevat vooraf veelgebruikte componenten zoals Button, Input, Card, Dialog en Dropdown. Extra componenten voeg je op elk moment toe met het commando `npx shadcn-svelte@latest add`.

***

## Samenvatting

Svelte komt in combinatie met Laravel vooral tot zijn recht in de "moderne monoliet"-opzet via Inertia. Dankzij het compilergebaseerde ontwerp is de bundel klein, en dankzij de runes-syntaxis is de reactiviteit expliciet en makkelijk te begrijpen.

| Element                | Rol                                                       |
| ---------------------- | --------------------------------------------------------- |
| Laravel-controller     | Routing, data ophalen, validatie                          |
| `Inertia::render()`    | Geeft data door van de controller aan de Svelte-component |
| Svelte-paginacomponent | Ontvangt props via `$props()` en rendert de UI            |
| `useForm`              | Statebeheer, verzenden en fouten tonen bij formulieren    |
| `Link`-component       | Paginanavigatie zonder volledige reload                   |
| `usePage().props`      | Toegang tot gedeelde data                                 |
| shadcn-svelte          | Standaard componentlibrary                                |

Met Inertia × Svelte krijg je een ontwikkelervaring die de eenvoud van een Laravel-backend combineert met de compacte schrijfstijl van Svelte. Maak je een project aan met de starter kit, dan kun je direct beginnen met ontwikkelen, inclusief authenticatieschermen.

<Card title="Officiële Inertia.js-documentatie" icon="book-open" href="https://inertiajs.com">
  Raadpleeg de officiële documentatie voor alle functies van Inertia v3.
</Card>


## Related topics

- [Introductie React — de basis voor Inertia × Laravel](/nl/blog/react-introduction.md)
- [Introductie Vue.js — de basis voor Inertia × Laravel](/nl/blog/vue-introduction.md)
- [Frontend](/nl/frontend.md)
- [Kennis die je nodig hebt voordat je met Laravel begint](/nl/true-tutorial.md)
- [Introductie tot authenticatie](/nl/authentication.md)
