Module 4 – Composants de page et layouts
Niveau 5.2 – Laravel Inertia (React & Vue)
Objectif
À la fin de ce module vous saurez :
- Créer des layouts réutilisables (structure commune : navbar, sidebar, footer) en React et en Vue.
- Envelopper le contenu de chaque page dans un layout pour éviter de dupliquer le HTML et la logique du menu.
- Utiliser le composant Link d’Inertia dans les layouts pour la navigation.
- Gérer titre de page et meta (SEO) avec les outils Inertia (Head) si besoin.
Nous donnons des exemples complets : un layout « app » (zone connectée) et un layout « guest » (page d’accueil publique), avec le code Laravel, React et Vue correspondant.
Pourquoi des layouts ?
Sans layout, chaque page devrait répéter la même structure : barre de navigation, conteneur principal, pied de page. Dès que vous changez un lien ou le style du menu, vous devez modifier toutes les pages. Un layout est un composant qui définit cette structure une fois et reçoit le contenu spécifique de chaque page (via children en React ou slot en Vue). En Inertia, les layouts sont de simples composants React ou Vue ; ils n’ont pas de lien direct avec Laravel (pas de route « layout »). Vous les importez dans chaque page et vous enveloppez le contenu.
Convention courante : placer les layouts dans resources/js/Layouts/ (ex. AppLayout.jsx, GuestLayout.vue).
Layout « App » – React
Structure : une barre de navigation en haut, un contenu principal (le contenu de la page) en dessous.
resources/js/Layouts/AppLayout.jsx :
import React from 'react';
import { Link } from '@inertiajs/react';
export default function AppLayout({ children }) {
return (
<div className="min-h-screen bg-gray-50">
<nav className="bg-white shadow-sm border-b border-gray-200">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
<div className="flex items-center">
<Link
href="/"
className="text-gray-800 font-semibold text-xl"
>
Mon App
</Link>
<Link
href="/dashboard"
className="ml-6 text-gray-600 hover:text-gray-900"
>
Dashboard
</Link>
<Link
href="/users"
className="ml-6 text-gray-600 hover:text-gray-900"
>
Utilisateurs
</Link>
</div>
</div>
</div>
</nav>
<main className="py-8">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{children}
</div>
</main>
</div>
);
}
children: prop spéciale en React qui contient tout ce que vous placez entre les balises ouvrantes et fermantes du composant quand vous l’utilisez (voir exemple de page ci-dessous).Link: toujours utiliser le composant Inertia pour les liens internes, y compris dans le layout, pour que la navigation reste en XHR.
Utilisation dans une page – resources/js/Pages/Dashboard.jsx :
import React from 'react';
import AppLayout from '@/Layouts/AppLayout';
export default function Dashboard() {
return (
<AppLayout>
<h1>Tableau de bord</h1>
<p>Bienvenue sur votre espace.</p>
</AppLayout>
);
}
Tout ce qui est entre <AppLayout> et </AppLayout> est passé au layout comme children et rendu dans la zone <main>.
Layout « App » – Vue
Même idée en Vue : un composant layout avec un slot pour le contenu.
resources/js/Layouts/AppLayout.vue :
<template>
<div class="min-h-screen bg-gray-50">
<nav class="bg-white shadow-sm border-b border-gray-200">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex items-center">
<Link
href="/"
class="text-gray-800 font-semibold text-xl"
>
Mon App
</Link>
<Link
href="/dashboard"
class="ml-6 text-gray-600 hover:text-gray-900"
>
Dashboard
</Link>
<Link
href="/users"
class="ml-6 text-gray-600 hover:text-gray-900"
>
Utilisateurs
</Link>
</div>
</div>
</div>
</nav>
<main class="py-8">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<slot />
</div>
</main>
</div>
</template>
<script setup>
import { Link } from '@inertiajs/vue3';
</script>
<slot />: emplacement où sera inséré le contenu passé par le parent. En Vue 3, le slot par défaut se fait avec<slot />(ou<slot></slot>).
Utilisation dans une page – resources/js/Pages/Dashboard.vue :
<template>
<AppLayout>
<h1>Tableau de bord</h1>
<p>Bienvenue sur votre espace.</p>
</AppLayout>
</template>
<script setup>
import AppLayout from '@/Layouts/AppLayout.vue';
</script>
Tout le contenu entre <AppLayout> et </AppLayout> est rendu dans le slot du layout.
Layout « Guest » (pages publiques)
Pour la page d’accueil ou la page de login, vous pouvez avoir un layout plus simple (sans la navbar complète, ou avec un menu différent). Exemple minimal en React :
resources/js/Layouts/GuestLayout.jsx :
import React from 'react';
import { Link } from '@inertiajs/react';
export default function GuestLayout({ children }) {
return (
<div className="min-h-screen flex flex-col items-center justify-center bg-gray-100">
<div className="w-full max-w-md p-8 bg-white rounded-lg shadow">
{children}
</div>
<Link href="/" className="mt-4 text-gray-600 hover:underline">
Retour à l'accueil
</Link>
</div>
);
}
En Vue, même principe avec <slot />. Les pages comme Welcome ou Login utilisent GuestLayout ; les pages Dashboard, Users/Index, etc. utilisent AppLayout.
Titre de page et balises meta (Head)
Chaque page peut vouloir définir son titre (onglet du navigateur) ou des meta (description pour le SEO). Inertia fournit un composant Head (React et Vue) que vous utilisez dans la page (ou dans le layout si le titre est commun).
React – Head
import { Head } from '@inertiajs/react';
export default function Dashboard() {
return (
<AppLayout>
<Head title="Tableau de bord" />
<h1>Tableau de bord</h1>
</AppLayout>
);
}
Pour une meta description :
<Head>
<title>Tableau de bord</title>
<meta name="description" content="Votre espace personnel" />
</Head>
Vue – Head
<template>
<AppLayout>
<Head title="Tableau de bord" />
<h1>Tableau de bord</h1>
</AppLayout>
</template>
<script setup>
import { Head } from '@inertiajs/vue3';
import AppLayout from '@/Layouts/AppLayout.vue';
</script>
Le composant Head injecte le contenu dans le <head> du document. Lors des navigations XHR, Inertia met à jour le titre (et les meta si vous les avez définis) pour refléter la nouvelle page.
Layout avec utilisateur connecté (shared data)
Souvent, la navbar affiche le nom de l’utilisateur connecté ou un lien « Déconnexion ». Ces données ne viennent pas des props de la page (spécifiques à une URL) mais des données partagées Inertia (disponibles sur toutes les pages). Nous verrons la configuration Laravel (middleware HandleInertiaRequests::share()) dans le module « Données partagées ». Ici, nous montrons comment lire ces données dans le layout pour afficher l’utilisateur.
En React, on utilise usePage() :
import { Link, usePage } from '@inertiajs/react';
export default function AppLayout({ children }) {
const { props } = usePage();
const user = props.auth?.user;
return (
<nav>
{/* ... liens ... */}
{user ? (
<span>Connecté : {user.name}</span>
) : (
<Link href="/login">Connexion</Link>
)}
</nav>
{/* ... */}
);
}
En Vue :
<script setup>
import { Link, usePage } from '@inertiajs/vue3';
import { computed } from 'vue';
const page = usePage();
const user = computed(() => page.props.auth?.user);
</script>
<template>
<nav>
<!-- ... -->
<span v-if="user">Connecté : {{ user.name }}</span>
<Link v-else href="/login">Connexion</Link>
</nav>
</template>
Pour que props.auth.user existe, il faut que le middleware Laravel HandleInertiaRequests partage cette clé (voir module 7). Sans cette config, auth peut être undefined ; dans ce cas, n’affichez pas le bloc « Connecté » ou gardez un fallback.
Résolution des chemins (@/Layouts/...)
Dans les exemples nous avons utilisé @/Layouts/AppLayout. Le @ est un alias qui pointe vers resources/js (configuré dans vite.config.js avec resolve.alias). Ainsi @/Layouts/AppLayout signifie resources/js/Layouts/AppLayout. Cela évite des chemins relatifs comme ../../Layouts/AppLayout qui deviennent illisibles quand les pages sont dans des sous-dossiers.
Vérifiez dans vite.config.js :
resolve: {
alias: {
'@': '/resources/js', // ou path.resolve(__dirname, 'resources/js')
},
},
En Vue, l’extension .vue est souvent requise pour les imports de composants : import AppLayout from '@/Layouts/AppLayout.vue'.
À retenir
- Layout = composant qui enveloppe le contenu commun (navbar, main, footer). Contenu spécifique de la page = children (React) ou slot (Vue).
- Toujours utiliser Link (Inertia) dans le layout pour les liens internes.
- Head (Inertia) pour le titre et les meta, utilisé dans la page (ou le layout).
- Données globales (ex. user) : usePage().props (React) ou usePage().props (Vue) ; à configurer côté Laravel avec share() (module 7).
- Alias @ vers resources/js pour des imports propres.
Dans le prochain module, nous verrons les formulaires : soumission vers Laravel, validation et affichage des erreurs avec useForm en React et Vue.