Kapitel 5 von 9

Laravel-13-Tutorial #5: Startseite mit Vite und Tailwind

geprüft am 7 September 2026 · 6 Min.

Schnelle Antwort

Laravel 13 bringt Vite und Tailwind CSS von Haus aus mit. Führe npm install und dann npm run dev aus und setz @vite(['resources/css/app.css', 'resources/js/app.js']) in dein Layout. Ohne einen dieser beiden Befehle scheitert die Seite mit „Vite manifest not found“.

Am Ende dieses Kapitels, hast du ein vollständiges Site-Layout, eine Sidebar, eine wiederverwendbare Artikelliste und eine Startseite, die sauber angezeigt wird.

Dieses Kapitel baut das visuelle Gerüst der Site: ein gemeinsames Layout, eine Startseite und die Build-Kette für die Stylesheets. Artikel gibt es noch keine, die Seite zeigt also eine leere Liste, Kapitel 6 füllt sie.

Vite und Tailwind sind bei Laravel dabei

Laravel 13 kommt mit Vite für die Assets und Tailwind CSS für die Styles. Auf PHP-Seite ist nichts zu installieren: package.json deklariert beides bereits.

Warum kein Bootstrap mehr per CDN

Die früheren Fassungen dieses Tutorials luden Bootstrap 4.5 und jQuery von einem CDN. Das hat zwei Nachteile: es kostet auf jeder Seite drei Requests an fremde Server, und es ignoriert das Tooling, das Laravel mitbringt. Tailwind, von Vite kompiliert, erzeugt eine einzige CSS-Datei, die nur die tatsächlich benutzten Klassen enthält.

Installier die JavaScript-Abhängigkeiten, einmalig:

bash
npm install

Starte dann den Vite-Entwicklungsserver in einem zweiten Terminal, neben php artisan serve:

bash
npm run dev

Vite überwacht deine Dateien und lädt den Browser bei jeder Änderung neu. Für die endgültigen Dateien, etwa vor einem Deployment:

bash
npm run build
code
public/build/assets/instrument-sans-500-normal-Z6ESRlEs.woff   21.65 kB
public/build/assets/instrument-sans-600-normal-B9e8oLYv.woff   21.67 kB
public/build/assets/fonts-C9MNnjVw.css                          2.35 kB │ gzip:  0.38 kB
public/build/assets/app-D8N0vhr5.css                           61.08 kB │ gzip: 12.65 kB
public/build/assets/app-BvRk9kiK.js                             0.00 kB │ gzip:  0.02 kB
✓ built in 1.43s
Vite Manifest not found

Wenn die Seite diesen Fehler mit dem absoluten Pfad deines Projekts zurückgibt:

code
Vite manifest not found at: /chemin/vers/blog/public/build/manifest.json

dann wurde weder npm run dev noch npm run build ausgeführt. Die Direktive @vite braucht eines von beiden. Das ist an dieser Stelle der häufigste Fehler.

Das Layout der Site

Leg resources/views/layouts/app.blade.php an. Das ist das Gerüst, das alle öffentlichen Seiten erweitern, nach dem Vererbungsprinzip aus Kapitel 3.

resources/views/layouts/app.blade.php
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>@yield('title', config('app.name'))</title>
    <meta name="description" content="@yield('description', 'Un blog construit avec Laravel 13.')">
    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-gray-50 text-gray-900 antialiased">
    <header class="border-b bg-white">
        <div class="mx-auto flex max-w-5xl items-center justify-between gap-4 px-4 py-4">
            <a href="http://%20route('home')%20" class="text-lg font-semibold">{{ config('app.name') }}</a>
            <form action="{{ route('search') }}" method="GET" role="search" class="flex gap-2">
                <label for="q" class="sr-only">Rechercher</label>
                <input id="q" type="search" name="q" value="{{ request('q') }}"
                       placeholder="Rechercher…"
                       class="rounded border border-gray-300 px-3 py-1.5 text-sm">
                <button type="submit" class="rounded bg-gray-900 px-3 py-1.5 text-sm text-white">Go</button>
            </form>
        </div>
    </header>

    <main class="mx-auto grid max-w-5xl gap-8 px-4 py-8 md:grid-cols-3">
        <div class="md:col-span-2">
            @yield('content')
        </div>
        @include('partials.sidebar')
    </main>

    <footer class="border-t bg-white">
        <div class="mx-auto max-w-5xl px-4 py-6 text-sm text-gray-500">
            {{ config('app.name') }} — tutoriel Laravel 13.
        </div>
    </footer>
</body>
</html>

Drei Punkte verdienen Aufmerksamkeit. @vite([...]) fügt die Tags zu den kompilierten Dateien ein und zeigt je nach Kontext auf den Entwicklungsserver oder auf den Build. Das Suchformular nutzt route('search'), eine Route, die erst Kapitel 8 deklariert, bis dahin lässt du es weg oder kommentierst die Zeile aus. Und <label class="sr-only"> bleibt für Screenreader lesbar, ohne sichtbar zu sein.

Die Sidebar

Leg resources/views/partials/sidebar.blade.php an. Sie zeigt die Kategorien, die Tags und die neuesten Artikel, alle vom Controller geliefert.

resources/views/partials/sidebar.blade.php
<aside class="space-y-8">
    <section>
        <h2 class="mb-2 font-semibold">Catégories</h2>
        <ul class="space-y-1 text-sm">
            @foreach ($categories as $category)
                <li>
                    <a href="http://%20route('category',%20$category)%20" class="text-blue-700 hover:underline">
                        {{ $category->name }}
                    </a>
                    <span class="text-gray-400">({{ $category->posts_count }})</span>
                </li>
            @endforeach
        </ul>
    </section>

    <section>
        <h2 class="mb-2 font-semibold">Étiquettes</h2>
        <ul class="flex flex-wrap gap-2 text-sm">
            @foreach ($tags as $tag)
                <li>
                    <a href="http://%20route('tag',%20$tag)%20" class="rounded bg-gray-200 px-2 py-0.5 hover:bg-gray-300">
                        {{ $tag->name }}
                    </a>
                </li>
            @endforeach
        </ul>
    </section>

    <section>
        <h2 class="mb-2 font-semibold">Articles récents</h2>
        <ul class="space-y-1 text-sm">
            @foreach ($recentPosts as $recent)
                <li>
                    <a href="http://%20route('post',%20$recent)%20" class="text-blue-700 hover:underline">{{ $recent->title }}</a>
                </li>
            @endforeach
        </ul>
    </section>
</aside>

Die Artikelliste

Diese View wird von der Startseite, den Kategorie- und Tag-Seiten und der Ergebnisseite eingebunden. Einmal geschrieben heißt: nicht fünfmal korrigiert.

resources/views/partials/posts-list.blade.php
@forelse ($posts as $post)
    <article class="mb-6 rounded border bg-white p-4">
        <h2 class="text-xl font-semibold">
            <a href="http://%20route('post',%20$post)%20" class="hover:underline">{{ $post->title }}</a>
        </h2>
        <p class="mt-1 text-sm text-gray-500">
            {{ $post->user?->name }} —
            <a href="http://%20route('category',%20$post-category)%20" class="hover:underline">{{ $post->category?->name }}</a>
            @if ($post->published_at) — {{ $post->published_at->translatedFormat('j F Y') }} @endif
        </p>
        <p class="mt-2 text-gray-700">{{ $post->excerpt ?: Str::limit(strip_tags($post->content), 160) }}</p>
        <a href="http://%20route('post',%20$post)%20" class="mt-3 inline-block text-sm text-blue-700 hover:underline">Lire la suite</a>
    </article>
@empty
    <p class="text-gray-600">Aucun article pour le moment.</p>
@endforelse

Der Operator ?-> verhindert einen Fehler, wenn die Relation fehlt: $post->user?->name liefert null, statt abzustürzen, wenn der Autor gelöscht wurde. translatedFormat schreibt das Datum in der Sprache der Anwendung, anders als format, das bei Englisch bleibt.

Die Startseite

resources/views/home.blade.php
@extends('layouts.app')

@section('title', config('app.name'))

@section('content')
    @if ($featuredPosts->isNotEmpty())
        <section class="mb-8">
            <h1 class="mb-3 text-2xl font-bold">À la une</h1>
            <ul class="space-y-1">
                @foreach ($featuredPosts as $featured)
                    <li>
                        <a href="http://%20route('post',%20$featured)%20" class="text-blue-700 hover:underline">{{ $featured->title }}</a>
                    </li>
                @endforeach
            </ul>
        </section>
    @endif

    <h2 class="mb-4 text-xl font-bold">Derniers articles</h2>
    @include('partials.posts-list')
@endsection

Der Controller

Der Controller der Startseite kann noch keine Modelle abfragen, es gibt sie nicht. Damit du das Layout schon jetzt prüfen kannst, gibst du ihm leere Collections mit:

app/Http/Controllers/IndexController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Support\Collection;
use Illuminate\View\View;

class IndexController extends Controller
{
    public function __invoke(): View
    {
        return view('home', [
            'posts' => new Collection(),
            'featuredPosts' => new Collection(),
            'categories' => new Collection(),
            'tags' => new Collection(),
            'recentPosts' => new Collection(),
        ]);
    }
}
routes/web.php
<?php

use App\Http\Controllers\IndexController;
use Illuminate\Support\Facades\Route;

Route::get('/', IndexController::class)->name('home');

Lad die Seite neu: Header, Sidebar und Footer erscheinen, in der Mitte steht „Aucun article pour le moment“. Die Struktur steht, es fehlen nur die Daten.

Das nächste Kapitel legt die Tabellen, die Modelle und die Backend-Screens an, die diese Seite füllen.

Häufige Fehler

Vite manifest not found at: … Weder npm run dev noch npm run build wurde ausgeführt. Die Direktive @vite braucht eines von beiden. Das ist der häufigste Fehler in diesem Kapitel.
route('search') wirft eine Exception Die Such-Route wird erst in Kapitel 8 deklariert. Kommentier das Formular aus oder deklarier die Route vorerst leer.
Das Datum erscheint auf Englisch format() übersetzt nicht. Nimm translatedFormat(), das der Locale der Anwendung folgt.
Newsletter

Neue Tests, Tutorials und Projekte, per E-Mail.

Reproduzierbare Tests, versionierter Code, datierte Ergebnisse. Niemals Spam.