Capitolo 5 di 9

Tutorial Laravel 13 #5: creare la homepage con Vite e Tailwind

verificato il 7 Settembre 2026 · 6 min

Risposta rapida

Laravel 13 include Vite e Tailwind CSS di serie. Esegui npm install poi npm run dev e inserisci @vite(['resources/css/app.css', 'resources/js/app.js']) nel tuo layout. Senza uno di questi due comandi la pagina fallisce con «Vite manifest not found».

Alla fine di questo capitolo, avrai un layout di sito completo, una barra laterale, una lista di articoli riutilizzabile e una homepage che si visualizza correttamente.

Questo capitolo costruisce l’ossatura visiva del sito: un layout comune, una homepage e la catena di compilazione dei fogli di stile. Gli articoli non esistono ancora, quindi la pagina mostrerà una lista vuota, la riempirà il capitolo 6.

Vite e Tailwind, inclusi in Laravel

Laravel 13 arriva con Vite per compilare gli asset e Tailwind CSS per gli stili. Lato PHP non c’è niente da installare: package.json li dichiara già.

Perché niente più Bootstrap da CDN

Le versioni precedenti di questo tutorial caricavano Bootstrap 4.5 e jQuery da un CDN. L’approccio ha due difetti: aggiunge tre richieste verso server di terze parti su ogni pagina e ignora del tutto gli strumenti che Laravel mette a disposizione. Tailwind, compilato da Vite, produce un unico file CSS che contiene solo le classi realmente usate.

Installa le dipendenze JavaScript, una volta sola:

bash
npm install

Poi avvia il server di sviluppo di Vite, in un secondo terminale, accanto a php artisan serve:

bash
npm run dev

Vite tiene d’occhio i tuoi file e aggiorna il browser a ogni modifica. Per produrre i file definitivi, per esempio prima di una messa online:

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

Se la pagina restituisce questo errore, con il percorso assoluto del tuo progetto:

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

vuol dire che non è stato eseguito né npm run devnpm run build. La direttiva @vite ha bisogno di uno dei due. È l’errore più frequente a questo punto.

Il layout del sito

Crea resources/views/layouts/app.blade.php. È l’ossatura che tutte le pagine pubbliche estenderanno, secondo il meccanismo di ereditarietà descritto nel capitolo 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>

Tre punti meritano attenzione. @vite([...]) inserisce i tag verso i file compilati, puntando al server di sviluppo o ai file già costruiti a seconda del contesto. Il form di ricerca usa route('search'), una rotta che dichiareremo nel capitolo 8: fino ad allora lascialo da parte o commenta la riga. Infine il <label class="sr-only"> resta leggibile dagli screen reader pur restando invisibile.

La barra laterale

Crea resources/views/partials/sidebar.blade.php. Mostrerà le categorie, i tag e gli articoli recenti, tutti forniti dal 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>

La lista degli articoli

Questa vista verrà inclusa dalla homepage, dalle pagine di categoria e di tag e dalla pagina dei risultati. Scriverla una volta sola evita di correggerla cinque volte.

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

L’operatore ?-> evita un errore quando la relazione manca: $post->user?->name restituisce null invece di far saltare tutto se l’autore è stato eliminato. translatedFormat scrive la data nella lingua dell’applicazione, a differenza di format che resta in inglese.

La 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

Il controller

Il controller della homepage non può ancora interrogare i model: non esistono. Per verificare subito il layout, passagli delle collection vuote:

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

Ricarica la pagina: intestazione, barra laterale e footer compaiono, con «Aucun article pour le moment» al centro. La struttura regge, mancano solo i dati.

Il capitolo successivo crea le tabelle, i model e le schermate del back-office che riempiranno questa pagina.

Errori frequenti

Vite manifest not found at: … Non è stato eseguito né npm run dev né npm run build. La direttiva @vite ha bisogno di uno dei due. È l'errore più frequente del capitolo.
route('search') solleva un'eccezione La rotta di ricerca viene dichiarata solo al capitolo 8. Nel frattempo commenta il form oppure dichiara una rotta vuota.
La data compare in inglese format() non traduce. Usa translatedFormat(), che segue la locale dell'applicazione.
Newsletter

I nuovi test, tutorial e progetti, via e-mail.

Test riproducibili, codice versionato, risultati datati. Mai spam.