Tutorial Laravel 13 #5: creare la homepage con Vite e Tailwind
verificato il 7 Settembre 2026 · 6 min
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».
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à.
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:
npm installPoi avvia il server di sviluppo di Vite, in un secondo terminale, accanto a php artisan serve:
npm run devVite 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:
npm run buildpublic/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.43sSe la pagina restituisce questo errore, con il percorso assoluto del tuo progetto:
Vite manifest not found at: /chemin/vers/blog/public/build/manifest.jsonvuol dire che non è stato eseguito né npm run dev né npm 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.
<!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.
<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.
@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>
@endforelseL’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
@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')
@endsectionIl controller
Il controller della homepage non può ancora interrogare i model: non esistono. Per verificare subito il layout, passagli delle collection vuote:
<?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(),
]);
}
}<?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.