Chapitre 5 sur 9

Tutoriel Laravel 13 #5 : Créer une page d’accueil

vérifié le 7 septembre 2026 · 7 min

Réponse rapide

Laravel 13 fournit Vite et Tailwind CSS par défaut. Lancez npm install puis npm run dev, et insérez @vite(['resources/css/app.css', 'resources/js/app.js']) dans votre gabarit. Sans l’une de ces deux commandes, la page échoue sur « Vite manifest not found ».

À la fin de ce chapitre, vous aurez un gabarit de site complet, une barre latérale, une liste d’articles réutilisable et une page d’accueil qui s’affiche correctement.

Ce chapitre construit l’ossature visuelle du site : un gabarit commun, une page d’accueil, et la chaîne de compilation des feuilles de style. Les articles n’existent pas encore, la page affichera donc une liste vide, le chapitre 6 la remplira.

Vite et Tailwind, fournis avec Laravel

Laravel 13 arrive avec Vite pour compiler les assets et Tailwind CSS pour les styles. Rien à installer côté PHP : package.json les déclare déjà.

Pourquoi plus de Bootstrap en CDN

Les versions précédentes de ce tutoriel chargeaient Bootstrap 4.5 et jQuery depuis un CDN. Cette approche a deux défauts : elle ajoute trois requêtes vers des serveurs tiers sur chaque page, et elle ignore complètement l’outillage que Laravel fournit. Tailwind, compilé par Vite, produit un seul fichier CSS qui ne contient que les classes réellement utilisées.

Installez les dépendances JavaScript, une seule fois :

bash
npm install

Puis lancez le serveur de développement Vite, dans un second terminal, à côté de php artisan serve :

bash
npm run dev

Vite surveille vos fichiers et rafraîchit le navigateur à chaque modification. Pour produire les fichiers définitifs, par exemple avant une mise en ligne :

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

Si la page renvoie cette erreur, avec le chemin absolu de votre projet :

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

c’est que ni npm run dev ni npm run build n’a été exécuté. La directive @vite a besoin de l’un des deux. C’est l’erreur la plus fréquente à ce stade.

Le gabarit du site

Créez resources/views/layouts/app.blade.php. C’est l’ossature que toutes les pages publiques vont étendre, selon le mécanisme d’héritage décrit au chapitre 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="{{ route('home') }}" 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>

Trois points méritent l’attention. @vite([...]) insère les balises vers les fichiers compilés, en pointant sur le serveur de développement ou sur les fichiers construits selon le contexte. Le formulaire de recherche utilise route('search'), une route que le chapitre 8 déclarera, jusque-là, laissez-le de côté ou commentez la ligne. Enfin le <label class="sr-only"> reste lisible par les lecteurs d’écran tout en étant invisible.

La barre latérale

Créez resources/views/partials/sidebar.blade.php. Elle affichera les catégories, les étiquettes et les articles récents, tous fournis par le contrôleur.

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="{{ route('category', $category) }}" 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="{{ route('tag', $tag) }}" 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="{{ route('post', $recent) }}" class="text-blue-700 hover:underline">{{ $recent->title }}</a>
                </li>
            @endforeach
        </ul>
    </section>
</aside>

La liste d’articles

Cette vue sera incluse par la page d’accueil, les pages de catégorie et d’étiquette, et la page de résultats. L’écrire une fois évite de la corriger cinq fois.

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="{{ route('post', $post) }}" class="hover:underline">{{ $post->title }}</a>
        </h2>
        <p class="mt-1 text-sm text-gray-500">
            {{ $post->user?->name }} —
            <a href="{{ route('category', $post->category) }}" 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="{{ route('post', $post) }}" 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

L’opérateur ?-> évite une erreur si la relation est absente : $post->user?->name renvoie null au lieu de planter quand l’auteur a été supprimé. translatedFormat écrit la date dans la langue de l’application, à la différence de format qui reste en anglais.

La page d’accueil

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="{{ route('post', $featured) }}" 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

Le contrôleur

Le contrôleur de l’accueil ne peut pas encore interroger de modèles, ils n’existent pas. Pour vérifier le gabarit dès maintenant, passez-lui des collections vides :

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

Rechargez la page : l’en-tête, la barre latérale et le pied de page s’affichent, avec « Aucun article pour le moment » au centre. La structure tient, il ne manque que les données.

Le chapitre suivant crée les tables, les modèles et les écrans du back-office qui rempliront cette page.

Erreurs fréquentes

Vite manifest not found at: … Ni npm run dev ni npm run build n’a été exécuté. La directive @vite a besoin de l’un des deux. C’est l’erreur la plus fréquente du chapitre.
route('search') lève une exception La route de recherche n’est déclarée qu’au chapitre 8. Commentez le formulaire ou déclarez la route à vide en attendant.
La date s’affiche en anglais format() ne traduit pas. Utilisez translatedFormat(), qui suit la locale de l’application.
Newsletter

Les nouveaux tests, tutoriels et projets, par e-mail.

Tests reproductibles, code versionné, résultats datés. Jamais de spam.