Hoofdstuk 5 van 9

Laravel 13-tutorial #5: een homepage bouwen met Vite en Tailwind

geverifieerd op 7 september 2026 · 6 min

Kort antwoord

Laravel 13 levert Vite en Tailwind CSS standaard mee. Draai npm install en daarna npm run dev, en zet @vite(['resources/css/app.css', 'resources/js/app.js']) in je layout. Ontbreekt een van die twee commando's, dan loopt de pagina stuk op “Vite manifest not found”.

Aan het einde van dit hoofdstuk, heb je een volledige site-layout, een sidebar, een herbruikbare artikellijst en een homepage die correct wordt weergegeven.

Dit hoofdstuk bouwt het visuele geraamte van de site: een gedeelde layout, een homepage en de compilatieketen voor de stylesheets. Artikelen bestaan nog niet, dus de pagina toont een lege lijst, hoofdstuk 6 vult haar.

Vite en Tailwind, meegeleverd met Laravel

Laravel 13 komt met Vite om de assets te compileren en met Tailwind CSS voor de styling. Aan PHP-kant valt er niets te installeren: package.json declareert ze al.

Waarom geen Bootstrap via CDN meer

Eerdere versies van deze tutorial laadden Bootstrap 4.5 en jQuery van een CDN. Die aanpak heeft twee nadelen: elke pagina doet er drie requests naar servers van derden bij, en de tooling die Laravel meelevert blijft volledig ongebruikt. Tailwind, gecompileerd door Vite, levert één CSS-bestand op dat alleen de klassen bevat die je werkelijk gebruikt.

Installeer de JavaScript-dependencies, eenmalig:

bash
npm install

Start daarna de Vite-ontwikkelserver in een tweede terminal, naast php artisan serve:

bash
npm run dev

Vite houdt je bestanden in de gaten en ververst de browser bij elke wijziging. Voor de definitieve bestanden, bijvoorbeeld vlak voor een 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

Geeft de pagina deze fout terug, met het absolute pad van je project:

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

dan is noch npm run dev noch npm run build gedraaid. De directive @vite heeft er een van de twee nodig. Dit is in dit stadium de meest voorkomende fout.

De layout van de site

Maak resources/views/layouts/app.blade.php aan. Dat is het geraamte dat alle publieke pagina’s uitbreiden, volgens het overervingsmechanisme uit hoofdstuk 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>

Drie punten verdienen aandacht. @vite([...]) zet de tags naar de gecompileerde bestanden in de pagina, en wijst afhankelijk van de context naar de ontwikkelserver of naar de gebouwde bestanden. Het zoekformulier gebruikt route('search'), een route die pas in hoofdstuk 8 gedeclareerd wordt, laat het tot dan weg of zet de regel in commentaar. En het <label class="sr-only"> blijft leesbaar voor schermlezers terwijl het onzichtbaar is.

De sidebar

Maak resources/views/partials/sidebar.blade.php aan. Die toont de categorieën, de tags en de recente artikelen, allemaal aangeleverd door de controller.

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>

De artikellijst

Deze view wordt straks opgenomen door de homepage, de categorie- en tagpagina’s en de resultatenpagina. Eén keer schrijven scheelt vijf keer corrigeren.

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

De operator ?-> voorkomt een fout als de relatie ontbreekt: $post->user?->name geeft null terug in plaats van te crashen wanneer de auteur verwijderd is. translatedFormat schrijft de datum in de taal van de applicatie, anders dan format, dat in het Engels blijft.

De homepage

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

De controller

De controller van de homepage kan nog geen modellen bevragen, want die bestaan niet. Geef hem lege collecties mee om de layout nu al te kunnen bekijken:

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');

Herlaad de pagina: de header, de sidebar en de footer verschijnen, met “Aucun article pour le moment” in het midden. De structuur staat, alleen de data ontbreekt.

Het volgende hoofdstuk maakt de tabellen, de modellen en de back-officeschermen die deze pagina gaan vullen.

Veelgemaakte fouten

Vite manifest not found at: … Noch npm run dev noch npm run build is gedraaid. De directive @vite heeft er een van de twee nodig. Dit is de meest voorkomende fout van dit hoofdstuk.
route('search') gooit een exception De zoekroute wordt pas in hoofdstuk 8 gedeclareerd. Zet het formulier zolang in commentaar of declareer een lege route.
De datum verschijnt in het Engels format() vertaalt niet. Gebruik translatedFormat(), dat de locale van de applicatie volgt.
Nieuwsbrief

Nieuwe tests, tutorials en projecten, per e-mail.

Reproduceerbare tests, geversioneerde code, gedateerde resultaten. Nooit spam.