Titres, descriptions, Open Graph
Temps 1 · séance 5 · ~25 min
Les slides du chapitre (PDF)PDF
- Mettre votre nom dans le titre de chaque onglet avec
APP_NAME. - Donner une description à chaque page publique avec
<Head>. - Écrire les balises Open Graph d'une fiche dans le template Blade avec
withViewData. - Vérifier ce qu'un robot reçoit, avec la vue source du site compilé.
1. Votre nom dans l'onglet
Au chapitre 1.08, vous avez mis votre nom dans la variable APP_NAME de .env, et l'onglet de la liste s'intitule « Projets - Nora Benali ». Si le vôtre affiche encore « Projets - Laravel », le kit a gardé sa valeur : remplacez-la par votre nom :
APP_NAME="Nora Benali"
Écrivez votre nom à la place de celui de l'exemple. Les guillemets sont nécessaires dès que la valeur contient une espace.
La page Vue ne lit pas APP_NAME directement. Elle lit VITE_APP_NAME, une variable que le kit définit plus bas dans .env par VITE_APP_NAME="${APP_NAME}". Vite ne transmet au JavaScript que les variables dont le nom commence par VITE_, et il les lit à son démarrage. Arrêtez composer run dev avec Ctrl+C, puis relancez-le, comme au chapitre 1.18. Le titre de la fiche du quiz devient :
<title data-inertia="">Quiz des capitales - Nora Benali</title>
2. Une description par page
La balise <meta name="description"> résume une page en une phrase. Les moteurs de recherche peuvent l'afficher sous le titre de la page dans leurs résultats. Le composant <Head> du chapitre 1.08 l'accepte entre ses balises. Dans Home.vue, remplacez <Head title="Accueil" /> par :
<Head title="Accueil">
<meta
head-key="description"
name="description"
content="Portfolio de Nora Benali, développeuse web Laravel et Vue.js à Wavre."
/>
</Head>
L'attribut head-key donne une clé à la balise. Quand vous passez d'une page à l'autre avec <Link>, Inertia remplace la balise qui porte la même clé, au lieu d'en ajouter une à chaque page visitée.
Faites de même dans projects/Index.vue, About.vue et Contact.vue, chacune avec sa phrase. Dans la fiche, projects/Show.vue, le titre et la description viennent du projet :
<Head :title="project.title">
<meta
head-key="description"
name="description"
:content="project.summary"
/>
</Head>
3. L'aperçu d'un lien partagé : Open Graph
Collez l'adresse d'un site dans LinkedIn, WhatsApp, Slack ou Discord. Une carte apparaît sous le message, avec un titre, une phrase et une image. Le site les a déclarés dans des balises Open Graph, des balises <meta property="og:…"> placées dans le <head> de la page. Trois suffisent pour une fiche de projet :
| Balise | Contenu pour une fiche |
|---|---|
og:title |
le titre du projet |
og:description |
le résumé du projet |
og:image |
l'adresse complète de l'image de couverture |
Un premier essai dans <Head>
L'idée naturelle consiste à ajouter ces trois balises dans le <Head> de Show.vue, à côté de la description. Avec composer run dev, le HTML de la fiche les contient bien :
$ curl -s http://127.0.0.1:8000/projets/quiz-des-capitales | grep "og:"
<meta property="og:title" content="Quiz des capitales" data-inertia>
<meta property="og:description" content="Un quiz de géographie en Vue.js avec score, minuteur et classement local." data-inertia>
<meta property="og:image" content="http://127.0.0.1:8000/storage/images/quiz-capitales.jpg" data-inertia>
<title data-inertia="">Quiz des capitales - Nora Benali</title>
<meta name="description" content="Un quiz de géographie en Vue.js avec score, minuteur et classement local." data-inertia="description">
curl est un programme qui demande une adresse et affiche la réponse brute du serveur, sans exécuter de JavaScript. Il voit ce que voit un robot.
Le même projet, compilé avec npm run build et servi sans composer run dev, comme il le sera chez OVH, donne autre chose :
$ curl -s http://127.0.0.1:8000/projets/quiz-des-capitales | grep -oE "<title[^<]*</title>|<meta property=\"og[^>]*>|<meta name=\"description[^>]*>"
<title>Nora Benali</title>
Il ne reste ni description, ni Open Graph, et le titre est générique.
Pourquoi les balises disparaissent
En développement, le plugin d'Inertia pour Vite fait un rendu côté serveur : il construit le HTML de la page Vue avant de l'envoyer, balises de <Head> comprises. En production, ce rendu demande un programme Node qui tourne en permanence sur le serveur. Un hébergement mutualisé comme celui d'OVH n'exécute que PHP. Le HTML envoyé ne contient alors que la balise <script data-page> du chapitre 1.07, et Vue construit la page dans le navigateur.
Les balises de <Head> n'apparaissent donc qu'après l'exécution du JavaScript. Dans le labo, la description arrive dans le <head> une seconde et demie après le chargement. Google exécute le JavaScript et la trouve. Les robots d'aperçu de Facebook, LinkedIn, Slack, WhatsApp ou Discord ne l'exécutent pas : sans correction, votre lien partagé n'a ni image ni description.
Une balise qu'un robot doit lire s'écrit en PHP, dans le template Blade.
4. Les balises Open Graph dans le template Blade
Le kit a un seul template Blade, resources/views/app.blade.php, qui entoure toutes les pages. Laravel l'écrit en PHP à chaque requête, avant tout JavaScript. La méthode withViewData() lui passe des données, à côté des props de la page Vue. Dans ProjectController::show(), ajoutez-la après Inertia::render(...) :
return Inertia::render('projects/Show', [
'project' => [
...$project->only('title', 'slug', 'summary', 'content', 'demo_url', 'repo_url'),
'cover_url' => $project->cover_url,
'tags' => $project->tags->pluck('name'),
],
])->withViewData([
'meta' => [
'title' => $project->title,
'description' => $project->summary,
'image' => $project->cover_url,
],
]);
Les props partent vers la page Vue, et meta reste côté serveur, pour le template. Dans app.blade.php, ajoutez ces lignes juste avant @fonts :
@isset($meta)
<meta property="og:title" content="{{ $meta['title'] }}">
<meta property="og:description" content="{{ $meta['description'] }}">
@if ($meta['image'])
<meta property="og:image" content="{{ $meta['image'] }}">
@endif
@endisset
@isset($meta) n'écrit les balises que pour une page qui a passé meta, ici la fiche. @if ($meta['image']) saute l'image d'un projet qui n'en a pas. {{ }} échappe le texte, comme en 5XCOS : un guillemet dans un résumé ne casse pas la balise.
Le titre de l'onglet aussi doit exister sans JavaScript. Dans le même fichier, remplacez la balise <title> qui se trouve dans <x-inertia::head> :
<x-inertia::head>
<title>{{ isset($meta) ? $meta['title'].' - ' : '' }}{{ config('app.name', 'Laravel') }}</title>
</x-inertia::head>
Retirez enfin les trois balises og: du <Head> de Show.vue, si vous les y aviez mises. En développement, elles seraient écrites deux fois. Le site compilé et servi sans composer run dev répond maintenant, en ne gardant que ces quatre balises de la source (dans le fichier, les préchargements de polices s'intercalent entre les og: et le <title>) :
<meta property="og:title" content="Quiz des capitales">
<meta property="og:description" content="Un quiz de géographie en Vue.js avec score, minuteur et classement local.">
<meta property="og:image" content="http://127.0.0.1:8000/storage/images/quiz-capitales.jpg">
<title>Quiz des capitales - Nora Benali</title>
Regardez l'adresse de l'image. Elle commence par la valeur de APP_URL, quelle que soit l'adresse ouverte dans le navigateur. Un robot ne peut charger que la vraie adresse publique de l'image : en ligne, APP_URL doit être celle de votre site, ce que règle le chapitre 1.22.
5. Le favicon
Le favicon est la petite icône de l'onglet et des favoris. Le kit en livre trois, avec le logo de Laravel, liés dans app.blade.php :
<link rel="icon" href="/favicon.ico" sizes="any">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
Remplacez les trois fichiers du dossier public/ par les vôtres, en gardant leurs noms. Un générateur de favicon en ligne produit les trois à partir d'une image. Les initiales de votre nom dans la couleur de votre planche suffisent.
6. Vérifier
Vérifiez ce que reçoit un robot, pas ce qu'affiche votre navigateur.
- Compilez le site avec
npm run build, puis arrêtezcomposer run dev. Si le dossierpublic/hotexiste encore, supprimez-le : tant qu'il est là, Laravel attend Vite. - Ouvrez une fiche sur
portfolio.test, puis sa vue source avec Ctrl+U. Cherchezog:: les trois balises sont là, sansdata-inertia.

L'inspecteur des outils de développement ne convient pas. Il montre la page après l'exécution du JavaScript, et il afficherait les balises de <Head> même si un robot ne les voit pas. Le site en développement ne convient pas non plus, puisque son rendu côté serveur cache le problème.
Une fois le site en ligne, un outil d'aperçu montre la carte telle qu'elle apparaîtra : le débogueur de partage de Facebook, le Post Inspector de LinkedIn ou opengraph.xyz. Ces outils chargent la page depuis Internet. Ils ne voient ni portfolio.test ni votre machine.
7. Un test qui lit le HTML
Un test Pest reçoit le HTML que Laravel envoie, sans rendu côté serveur et sans JavaScript, exactement comme un robot. Ajoutez ce test dans tests/Feature/PublicPagesTest.php :
test('a project page has Open Graph tags in the HTML', function () {
Project::factory()->create([
'title' => 'Météo Wavre',
'slug' => 'meteo-wavre',
'summary' => 'La météo heure par heure.',
'cover_path' => 'images/meteo-wavre.jpg',
]);
$this->get('/projets/meteo-wavre')
->assertSee('<meta property="og:title" content="Météo Wavre">', false)
->assertSee('<meta property="og:description" content="La météo heure par heure.">', false)
->assertSee('/storage/images/meteo-wavre.jpg', false);
});
Le second argument de assertSee, false, demande de chercher le texte tel quel, sans échapper ses guillemets et ses chevrons. Dans le labo, le fichier contient aussi un test de l'accueil et le test des mentions légales du chapitre 1.21 :
php artisan test tests/Feature/PublicPagesTest.php
PASS Tests\Feature\PublicPagesTest
✓ the home page shows three published projects
✓ a project page has Open Graph tags in the HTML
✓ the legal notice page exists
Tests: 3 passed (21 assertions)
Le référencement ne s'arrête pas au titre, à la description et à Open Graph. Le plan du site pour les moteurs, la vitesse de chargement ou les données structurées viendront avec le projet en binôme.
8. Pratique : titres et aperçus (10 min)
Temps annoncé : 10 minutes.
-
APP_NAMEporte votre nom, et chaque onglet affiche « titre de la page - votre nom ». - Chaque page publique a une description avec
head-key="description". -
ProjectController::show()passemetaavecwithViewData(), etapp.blade.phpécrit les trois balisesog:. - Après
npm run build, sanscomposer run dev, la vue source d'une fiche contientog:title,og:descriptionetog:image. - Les trois favicons du kit sont remplacés par les vôtres.
- Le test Open Graph est vert.
Ce que votre portfolio contient maintenant
Un titre et une description pour chaque page publique, et des balises Open Graph écrites par le serveur sur chaque fiche de projet. Un lien partagé vers une fiche montrera son titre, son résumé et son image dès que le site sera en ligne. L'onglet porte votre nom et votre favicon.
À retenir
APP_NAMEdans.envdonne le suffixe des titres, et Vite le lit au démarrage parVITE_APP_NAME.<Head>donne à chaque page son titre et sa description, ethead-keyévite d'empiler les balises.- Sans rendu côté serveur, ce qu'écrit
<Head>n'existe qu'après le JavaScript, et un robot d'aperçu ne le voit pas. withViewData(['meta' => …])passe les données au templateapp.blade.php, qui écrit les balises Open Graph en PHP.- La vérification se fait dans la vue source du site compilé, sans
composer run dev, puis avec un outil d'aperçu une fois en ligne.