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

# Vue.js – Einstieg mit Inertia × Laravel

> Vorstellung von Vue.js – dem für Laravel-Nutzer vertrautesten JS-Framework. Praxisnah erläutert von Options API / Composition API bis zu Page-Komponenten, useForm und Shared Data mit Inertia v3 × Vue 3.

## Was ist Vue.js?

Vue.js (im Folgenden Vue) ist ein progressives JavaScript-Framework zum Aufbau von Benutzeroberflächen. „Progressiv" meint, dass Sie klein beginnen und bei Bedarf Funktionen ergänzen können – von der partiellen Integration in bestehende HTML-Seiten bis zum vollständigen SPA.

Der Kern von Vue ist **Reaktivität**. Bei Datenänderungen aktualisiert sich das DOM automatisch; Sie müssen nicht manuell verwalten, wann welches Element aktualisiert wird.

<Info>
  Diese Seite behandelt Vue 3 mit Inertia v3. Die Starterkits von Laravel 13 verwenden diese Konstellation standardmäßig.
</Info>

### Options API und Composition API

Vue 3 bietet zwei Stile: die **Options API** und die **Composition API**.

Die **Options API** ist der klassische Stil aus Vue 2. Komponenten werden über Options-Objekte wie `data`, `methods`, `computed` und `mounted` beschrieben.

```vue theme={null}
<!-- Beispiel Options API -->
<script>
export default {
    data() {
        return { count: 0 }
    },
    methods: {
        increment() {
            this.count++
        }
    }
}
</script>

<template>
    <button @click="increment">{{ count }}</button>
</template>
```

Die **Composition API** ist der neue Stil aus Vue 3. In Kombination mit der `<script setup>`-Syntax lässt sich Code besonders knapp schreiben. Auch Wiederverwendbarkeit von Logik und TypeScript-Integration sind sehr gut.

```vue theme={null}
<!-- Beispiel Composition API mit <script setup> -->
<script setup>
import { ref } from 'vue'

const count = ref(0)

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

<template>
    <button @click="increment">{{ count }}</button>
</template>
```

<Tip>
  In den Starterkits Inertia × Laravel ist der Composition-API-Stil mit `<script setup>` Standard. Alle Beispiele auf dieser Seite verwenden ebenfalls `<script setup>`.
</Tip>

***

## Rolle in Laravel

### Historie

Vue und Laravel haben eine lange gemeinsame Geschichte, die mit **Laravel 5.3 (2016)** und der Einführung von Vue als Standard-Frontend-Framework beginnt. Damals war Vue in `package.json` enthalten, ebenso eine Beispielkomponente `resources/js/components/ExampleComponent.vue`.

```mermaid theme={null}
timeline
    title Laravel und Vue im Zeitverlauf
    2016 : Laravel 5.3 – Vue als Standard
    2019 : Laravel 6 – Vue-Scaffold in Paket laravel/ui ausgelagert
    2021 : Laravel 8 – Starterkit Jetstream + Inertia (Vue)
    2022 : Laravel 9 – Umstellung auf Vite
    2025 : Laravel 12 – Starterkits erneuert (Vue / React)
    2026 : Laravel 13 – Starterkits mit Inertia v3 kompatibel
```

Mit **Laravel 6 (2019)** wurde das Auth-Scaffold in das Paket `laravel/ui` ausgelagert; auch das Vue-Scaffold zog in dieses Paket um. Aktueller Standard ist die Inertia-+-Vue-Konfiguration, die über das Starterkit von `laravel new` gewählt wird.

Für Laravel-Nutzer ist Vue das vertrauteste JS-Framework; auch die Lernressourcen sind reichhaltig.

### Aktueller Standard: Inertia × Vue

Der Standard-Ansatz für Vue in Laravel ist **Inertia × Vue**. Inertia realisiert die „moderne Monolith"-Architektur, bei der Daten aus dem Laravel-Controller direkt an Vue-Komponenten gehen, ohne API-Design.

```mermaid theme={null}
graph LR
    Browser["Browser"]
    Inertia["Inertia.js<br>(Adapter-Schicht)"]
    Laravel["Laravel<br>(Controller)"]
    Vue["Vue<br>(Page-Komponente)"]

    Browser <-->|XHR / Full-Page-Load| Inertia
    Inertia <-->|Inertia-Response| Laravel
    Inertia -->|props| Vue
    Vue -->|Rendering| Browser
```

***

## Setup

### Via Starterkit (empfohlen)

Für neue Projekte ist das Starterkit am komfortabelsten.

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

Wenn Sie im interaktiven Prompt **Vue** wählen, wird automatisch alles Folgende eingerichtet:

* `inertiajs/inertia-laravel` (serverseitiger Adapter)
* `@inertiajs/vue3` (Client-Adapter)
* `vue` (Vue 3 selbst)
* `@vitejs/plugin-vue` (Vite-Plugin)
* Middleware `HandleInertiaRequests`
* Auth-Seiten für Login, Registrierung u. a. (bereits mit Inertia + Vue umgesetzt)

### Manuelle Installation

Für die Ergänzung in einem bestehenden Projekt installieren Sie Server- und Client-Seite separat.

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

# Clientseitig (JavaScript)
npm install @inertiajs/vue3 vue
npm install --save-dev @vitejs/plugin-vue
```

Ergänzen Sie das Vue-Plugin in `vite.config.js`:

```js theme={null}
import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.js'],
            refresh: true,
        }),
        vue({
            template: {
                transformAssetUrls: {
                    base: null,
                    includeAbsolute: false,
                },
            },
        }),
    ],
})
```

Starten Sie die Inertia-App in `resources/js/app.js`.

```js theme={null}
import { createApp, h } from 'vue'
import { createInertiaApp } from '@inertiajs/vue3'
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'

createInertiaApp({
    resolve: (name) =>
        resolvePageComponent(
            `./pages/${name}.vue`,
            import.meta.glob('./pages/**/*.vue'),
        ),
    setup({ el, App, props, plugin }) {
        createApp({ render: () => h(App, props) })
            .use(plugin)
            .mount(el)
    },
})
```

<Info>
  Details zur manuellen Installation (Root-Template, Middleware-Registrierung usw.) finden Sie in der [offiziellen Inertia-Dokumentation](https://inertiajs.com/installation).
</Info>

***

## Verzeichnisstruktur

Im Starterkit werden Vue-Page-Komponenten unter `resources/js/pages/` abgelegt.

```
resources/js/
├── app.js             # Einstiegspunkt der Inertia-App
├── bootstrap.js
├── components/        # Wiederverwendbare UI-Komponenten
│   ├── NavBar.vue
│   └── ...
├── layouts/           # Layout-Komponenten
│   ├── AppLayout.vue
│   └── AuthLayout.vue
└── pages/             # Inertia-Page-Komponenten (analog zu Controller-Namen)
    ├── Auth/
    │   ├── Login.vue
    │   └── Register.vue
    ├── Dashboard.vue
    └── Posts/
        ├── Index.vue
        ├── Create.vue
        └── Show.vue
```

`Inertia::render('Posts/Index', [...])` verweist auf `resources/js/pages/Posts/Index.vue`.

***

## Vue-Template-Syntax

Die grundlegenden Direktiven, die Sie zum Lesen und Schreiben von Starterkit-Code benötigen:

#### `{{ }}` – Variablen-Interpolation

Mit doppelten geschweiften Klammern binden Sie JavaScript-Werte und -Ausdrücke ins Template ein.

```vue theme={null}
<script setup>
const name = 'Welt'
const count = 3
</script>

<template>
    <p>Hallo, {{ name }}!</p>
    <p>Doppelter Wert: {{ count * 2 }}</p>
</template>
```

#### `v-if` – Bedingte Anzeige

```vue theme={null}
<template>
    <p v-if="isLoggedIn">Willkommen!</p>
    <p v-else-if="role === 'admin'">Als Administrator angemeldet</p>
    <a v-else href="/login">Anmelden</a>
</template>
```

Entspricht `{#if}` in Svelte und dem ternären Operator in React.

#### `v-for` – Listen darstellen

```vue theme={null}
<template>
    <ul>
        <li v-for="post in posts" :key="post.id">{{ post.title }}</li>
    </ul>
</template>
```

Für effizientes Diffing ist `:key` zwingend. Entspricht `Array.map()` in React.

#### `v-model` – Zwei-Wege-Binding

Mit `v-model` synchronisieren Sie den Wert eines Formularelements bidirektional mit einer reaktiven Variablen.

```vue theme={null}
<script setup>
import { ref } from 'vue'

const title = ref('')
const agreed = ref(false)
const role = ref('viewer')
</script>

<template>
    <!-- Text-Input -->
    <input v-model="title" type="text" />
    <p>Eingabe: {{ title }}</p>

    <!-- Checkbox -->
    <input v-model="agreed" type="checkbox" />
    <p>Zustimmung: {{ agreed }}</p>

    <!-- Select -->
    <select v-model="role">
        <option value="viewer">Betrachter</option>
        <option value="editor">Bearbeiter</option>
        <option value="admin">Administrator</option>
    </select>
</template>
```

#### `:` (v-bind) und `@` (v-on)

* `:attr="value"` – dynamischen Wert an ein HTML-Attribut binden (Kurzform von `v-bind:attr`)
* `@event="handler"` – Event-Listener registrieren (Kurzform von `v-on:event`)

```vue theme={null}
<template>
    <!-- dynamisches Attribut-Binding -->
    <img :src="imageUrl" :alt="imageAlt" />

    <!-- Event-Handler -->
    <button @click="handleClick">Klick</button>
    <form @submit.prevent="handleSubmit">...</form>
</template>
```

***

## Grundlagen der Page-Komponente

Eine Inertia-Page-Komponente ist eine gewöhnliche Vue-Komponente. Die aus dem Laravel-Controller übergebenen Daten stehen als Props zur Verfügung.

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

### Vue-Page-Komponente

```vue theme={null}
<!-- resources/js/pages/Posts/Index.vue -->
<script setup>
import { Link } from '@inertiajs/vue3'

defineProps({
    posts: Object,
})
</script>

<template>
    <div>
        <h1>Beitragsübersicht</h1>
        <article v-for="post in posts.data" :key="post.id">
            <h2>
                <Link :href="`/posts/${post.id}`">{{ post.title }}</Link>
            </h2>
            <p>{{ post.created_at }}</p>
        </article>
    </div>
</template>
```

Nach `defineProps()` verwenden Sie die vom Controller übergebenen Daten direkt im Template. Eine REST-API ist nicht nötig.

***

## Die `Link`-Komponente

Die von `@inertiajs/vue3` bereitgestellte `<Link>`-Komponente sorgt für Seitenwechsel per XHR und vermeidet ein Full-Page-Reload.

```vue theme={null}
<script setup>
import { Link } from '@inertiajs/vue3'
</script>

<template>
    <!-- Einfacher Link -->
    <Link href="/posts">Beitragsübersicht</Link>

    <!-- Link per POST (z. B. Löschen) -->
    <Link href="/posts/1" method="delete" as="button" type="button">
        Löschen
    </Link>

    <!-- Preload (bei Hover vorab laden) -->
    <Link href="/posts/1" preload>Beitrag ansehen</Link>
</template>
```

Die Schreibweise gleicht einem `<a>`-Tag; im Hintergrund tauscht Inertia nur die Page-Komponente aus.

***

## Die `Form`-Komponente

Die `<Form>`-Komponente von `@inertiajs/vue3` ist der empfohlene Stil aus den Starterkit-Auth-Screens. Sie geben `action` und `method` als Props an und greifen mit `v-slot` auf `errors` und `processing` zu.

### Grundlegende Verwendung

```vue theme={null}
<script setup>
import { Form } from '@inertiajs/vue3'
</script>

<template>
    <Form action="/posts" method="post" class="flex flex-col gap-4" v-slot="{ errors, processing }">
        <div>
            <label for="title">Titel</label>
            <input id="title" name="title" type="text" required />
            <p v-if="errors.title" class="error">{{ errors.title }}</p>
        </div>

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

        <button type="submit" :disabled="processing">
            {{ processing ? 'Wird gesendet...' : 'Veröffentlichen' }}
        </button>
    </Form>
</template>
```

`v-slot="{ errors, processing }"` ist die Scoped-Slot-Syntax von Vue; die `Form`-Komponente berechnet diese Werte automatisch und übergibt sie. Statt `v-model` verwenden Sie das native HTML-`name`-Attribut, sodass die Formulardatenerfassung des Browsers funktioniert.

### Muster aus dem Starterkit

Die Starterkits verwalten Routen mit [Wayfinder](/de/blog/wayfinder-introduction) als Objekte. `store.form()` liefert ein Objekt mit `action` und `method`, das per `v-bind` an `<Form>` per Spread übergeben wird.

```vue theme={null}
<script setup lang="ts">
import { Form } from '@inertiajs/vue3'
import { store } from '@/routes/login'
</script>

<template>
    <Form
        v-bind="store.form()"
        :reset-on-success="['password']"
        v-slot="{ errors, processing }"
        class="flex flex-col gap-6"
    >
        <!-- Formularinhalt -->
    </Form>
</template>
```

Die in `reset-on-success` angegebenen Felder werden bei erfolgreichem Absenden automatisch zurückgesetzt. Nutzen Sie das etwa für Passwortfelder, die nach dem Absenden geleert werden sollen.

<Info>
  Ohne Wayfinder verhalten sich `action="/login"` und Co. identisch, sobald Sie die URL direkt übergeben.
</Info>

***

## Der `useForm`-Helper

Für die Formularverarbeitung nutzen Sie den `useForm`-Helper aus `@inertiajs/vue3`. State-Handling, Absenden und Fehleranzeige lassen sich damit schlank umsetzen.

### Auf der Controller-Seite

```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', 'Der Beitrag wurde erstellt.');
    }
}
```

### Vue-Formular-Komponente

```vue theme={null}
<!-- resources/js/pages/Posts/Create.vue -->
<script setup>
import { useForm } from '@inertiajs/vue3'

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

function submit() {
    form.post('/posts')
}
</script>

<template>
    <form @submit.prevent="submit">
        <div>
            <label>Titel</label>
            <input v-model="form.title" type="text" />
            <p v-if="form.errors.title" class="error">{{ form.errors.title }}</p>
        </div>

        <div>
            <label>Inhalt</label>
            <textarea v-model="form.content"></textarea>
            <p v-if="form.errors.content" class="error">{{ form.errors.content }}</p>
        </div>

        <button type="submit" :disabled="form.processing">
            {{ form.processing ? 'Wird gesendet...' : 'Veröffentlichen' }}
        </button>
    </form>
</template>
```

Wichtige Eigenschaften des Rückgabewerts von `useForm`:

| Eigenschaft / Methode | Beschreibung                                             |
| --------------------- | -------------------------------------------------------- |
| `form.data`           | Datenobjekt des Formulars                                |
| `form.errors`         | Validierungsfehler (Zugriff per Feldname)                |
| `form.processing`     | `true`, solange gesendet wird (für Button-Deaktivierung) |
| `form.isDirty`        | `true`, wenn sich der Wert vom Ursprung unterscheidet    |
| `form.post(url)`      | Per POST senden                                          |
| `form.put(url)`       | Per PUT senden (Update)                                  |
| `form.delete(url)`    | Per DELETE senden                                        |
| `form.reset()`        | Formular auf Initialwerte zurücksetzen                   |

Bei Validierungsfehlern behält `useForm` die Eingaben und zeigt die Fehler an. In Verbindung mit `v-model` entsteht ein nahtloses Formularerlebnis.

***

## Shared Data

Daten, die auf allen Seiten gemeinsam gebraucht werden (angemeldeter Nutzer, Flash-Messages u. a.), definieren Sie in der Methode `share()` der 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'),
            ],
        ]);
    }
}
```

In Vue-Komponenten greifen Sie mit `usePage()` auf Shared Data zu.

```vue theme={null}
<script setup>
import { computed } from 'vue'
import { usePage } from '@inertiajs/vue3'

const page = usePage()

// Zugriff auf Shared Data
const user = computed(() => page.props.auth.user)
const flash = computed(() => page.props.flash)
</script>

<template>
    <header>
        <span v-if="user">{{ user.name }}</span>
        <span v-else>Gast</span>
    </header>

    <div v-if="flash.success" class="alert-success">
        {{ flash.success }}
    </div>
</template>
```

<Info>
  Da Shared Data in jeder Anfrage enthalten sind, empfiehlt sich die Beschränkung auf das Nötigste. Eine Lazy-Evaluation per `fn()` wird nur ausgewertet, wenn tatsächlich zugegriffen wird.
</Info>

***

## Grundlagen der Reaktivität in Vue 3

Wichtige Reaktivitäts-APIs für Inertia × Vue:

### `ref` – primitive reaktive Werte

```vue theme={null}
<script setup>
import { ref } from 'vue'

const count = ref(0)
const isOpen = ref(false)

// Zugriff per .value (im Template nicht nötig)
count.value++
</script>

<template>
    <p>{{ count }}</p>
    <button @click="isOpen = !isOpen">Toggle</button>
</template>
```

### `computed` – berechnete Werte

```vue theme={null}
<script setup>
import { ref, computed } from 'vue'

const posts = ref([])

const publishedPosts = computed(() =>
    posts.value.filter(post => post.published)
)
</script>
```

### `onMounted` – Verarbeitung nach dem Mount

```vue theme={null}
<script setup>
import { onMounted } from 'vue'

onMounted(() => {
    console.log('Komponente ist gemountet')
})
</script>
```

***

## Fazit

Vue.js harmoniert hervorragend mit Laravel und entfaltet insbesondere im „modernen Monolithen" mit Inertia seine Stärken.

| Baustein            | Rolle                                              |
| ------------------- | -------------------------------------------------- |
| Laravel-Controller  | Routing, Datenerfassung, Validierung               |
| `Inertia::render()` | Daten vom Controller an die Vue-Komponente reichen |
| Vue-Page-Komponente | Props empfangen und UI rendern                     |
| `useForm`           | Formular-State, Absenden und Fehleranzeige         |
| `Link`-Komponente   | Seitenwechsel ohne Full-Reload                     |
| `usePage().props`   | Zugriff auf Shared Data                            |

Mit Inertia × Vue erhalten Sie die Einfachheit des Laravel-Backends und die reaktive Vue-UI gleichermaßen. Legen Sie ein Projekt mit dem Starterkit an, sind auch die Auth-Screens sofort einsatzbereit.

<Card title="Offizielle Inertia.js-Dokumentation" icon="book-open" href="https://inertiajs.com">
  Alle Funktionen von Inertia v3 finden Sie in der offiziellen Dokumentation.
</Card>


## Related topics

- [React – Einstieg mit Inertia × Laravel](/de/blog/react-introduction.md)
- [Svelte – Einstieg mit Inertia × Laravel](/de/blog/svelte-introduction.md)
- [SPAs mit Inertia.js entwickeln](/de/blog/inertia-introduction.md)
- [Frontend](/de/frontend.md)
- [Wissen, das Sie vor dem Einstieg in Laravel benötigen](/de/true-tutorial.md)
