Zum Hauptinhalt springen

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.
Diese Seite behandelt Vue 3 mit Inertia v3. Die Starterkits von Laravel 13 verwenden diese Konstellation standardmäßig.

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.
<!-- 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.
<!-- 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>
In den Starterkits Inertia × Laravel ist der Composition-API-Stil mit <script setup> Standard. Alle Beispiele auf dieser Seite verwenden ebenfalls <script setup>.

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. 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.

Setup

Via Starterkit (empfohlen)

Für neue Projekte ist das Starterkit am komfortabelsten.
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.
# 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:
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.
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)
    },
})
Details zur manuellen Installation (Root-Template, Middleware-Registrierung usw.) finden Sie in der offiziellen Inertia-Dokumentation.

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.
<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

<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

<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.
<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)
<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

// 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

<!-- 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 von @inertiajs/vue3 bereitgestellte <Link>-Komponente sorgt für Seitenwechsel per XHR und vermeidet ein Full-Page-Reload.
<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

<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 als Objekte. store.form() liefert ein Objekt mit action und method, das per v-bind an <Form> per Spread übergeben wird.
<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.
Ohne Wayfinder verhalten sich action="/login" und Co. identisch, sobald Sie die URL direkt übergeben.

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

// 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

<!-- 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 / MethodeBeschreibung
form.dataDatenobjekt des Formulars
form.errorsValidierungsfehler (Zugriff per Feldname)
form.processingtrue, solange gesendet wird (für Button-Deaktivierung)
form.isDirtytrue, 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.
// 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.
<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>
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.

Grundlagen der Reaktivität in Vue 3

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

ref – primitive reaktive Werte

<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

<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

<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.
BausteinRolle
Laravel-ControllerRouting, Datenerfassung, Validierung
Inertia::render()Daten vom Controller an die Vue-Komponente reichen
Vue-Page-KomponenteProps empfangen und UI rendern
useFormFormular-State, Absenden und Fehleranzeige
Link-KomponenteSeitenwechsel ohne Full-Reload
usePage().propsZugriff 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.

Offizielle Inertia.js-Dokumentation

Alle Funktionen von Inertia v3 finden Sie in der offiziellen Dokumentation.
Zuletzt geändert am 13. Juli 2026