Pratique : Optimisation SEO et Open Graph
Bienvenue dans ce guide complet où nous allons transformer votre blog Inertia.js en une machine à référencer. L'objectif est simple : chaque article doit avoir des balises <title> et <meta> uniques et dynamiques, ce qui améliore drastiquement son visibilité sur Google et son attractivité lors du partage sur les réseaux sociaux.
Le Défi du SEO avec Inertia.js
Votre application est une SPA (Single Page Application). Quand vous naviguez d'un article à l'autre, seule une partie de la page se met à jour, sans rechargement complet. Le problème ? Les balises <head> du document HTML, elles, ne se mettent pas à jour automatiquement. Pour Google et les réseaux sociaux, c'est comme si toutes vos pages avaient le même titre et la même description.
Notre Solution : @vueuse/head
Pour résoudre ce problème de manière élégante et moderne, nous allons utiliser le package @vueuse/head.
Pourquoi est-ce le meilleur choix ?
Réactivité Totale : Il met à jour les balises
<head>automatiquement lorsque vos données changent, que ce soit au chargement initial (côté serveur) ou lors des navigations (côté client).Intégration Parfaite : Conçu pour Vue 3 et l'API Composition, il s'intègre sans effort à votre stack Inertia.
Syntaxe Claire : La configuration est intuitive et facile à maintenir.
Exhaustif : Il gère tout : le SEO classique pour Google, les balises Open Graph (Facebook, LinkedIn) et les Twitter Cards.
Étape 1 : Installation
Rien de plus simple. Ouvrez votre terminal à la racine de votre projet Laravel et lancez :
npm install @vueuse/headÉtape 2 : Configuration Globale du Plugin
Pour que @vueuse/head soit disponible dans toute votre application, nous allons l'enregistrer comme un plugin global.
2.1. Créer le fichier du plugin
Créez un nouveau fichier pour centraliser la configuration : resources/js/plugins/head.ts
// resources/js/plugins/head.ts
import { createHead } from '@vueuse/head'
// On exporte une instance "head" pré-configurée
export const head = createHead()2.2. Enregistrer le plugin dans l'application
Ouvrez votre fichier d'entrée principal : resources/js/app.ts. Comme vous utilisez Wayfinder, la configuration est plus simple car vous n'avez pas besoin du plugin ZiggyVue.
// resources/js/app.ts
import './bootstrap';
import '../css/app.css';
import { createInertiaApp } from '@inertiajs/vue3';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
// Importez le plugin head que nous venons de créer
import { head } from './plugins/head';
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) =>
resolvePageComponent(
`./Pages/${name}.vue`,
import.meta.glob('./Pages/**/*.vue')
),
setup({ el, App, props, plugin }) {
return createApp({ render: () => h(App, props) })
.use(plugin)
// ✅ On ajoute simplement notre plugin head
.use(head)
.mount(el);
},
progress: {
color: '#4B5563',
},
});Étape 3 : Implémentation dans le Composant BlogShow.vue
C'est ici que la magie opère. Nous allons utiliser les données envoyées par votre BlogController pour remplir dynamiquement les balises.
Voici le code final et corrigé pour le bloc <script setup> de votre fichier resources/js/Pages/BlogViews/BlogShow.vue. Copiez et collez ce code pour remplacer votre bloc existant.
<script setup lang="ts">
import { Link, router, useForm, usePage, Head } from '@inertiajs/vue3';
import { useHead } from '@vueuse/head';
import { computed, onMounted, ref } from 'vue';
import * as blogRoutes from '@/routes/blog';
import * as blogCommentRoutes from '@/routes/blog/comments';
import * as blogCommentPublicRoutes from '@/routes/blog/comments/public';
// --- ÉTAPE 1 : DÉFINITION DES PROPS (TRÈS IMPORTANT) ---
// Définissez toujours vos props en premier pour qu'elles soient accessibles partout.
interface Props {
post: {
id: number; title: string; slug: string; excerpt: string;
meta_title: string; meta_description: string; content: string;
cover_image: string | null; published_at: string; reading_time: number;
};
}
// ✅ Déclaration des props en premier. C'est la source de vérité pour votre composant.
const props = defineProps<Props>();
// --- ÉTAPE 2 : LOGIQUE SEO avec @vueuse/head ---
// On crée une computed dédiée pour le JSON-LD pour éviter les erreurs de sérialisation.
const jsonLd = computed(() => {
return JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Article',
headline: props.post.title,
description: props.post.meta_description || props.post.excerpt,
image: props.post.cover_image ? `https://VOTRE_DOMAINE.com/${props.post.cover_image}` : 'https://VOTRE_DOMAINE.com/images/default-og-image.jpg',
datePublished: props.post.published_at,
author: { '@type': 'Person', name: props.post.user.name },
});
});
useHead({
// Le titre de l'onglet du navigateur, avec un fallback
title: computed(() => props.post.meta_title || props.post.title),
// Les balises meta pour le SEO et les réseaux sociaux
meta: [
{ name: 'description', content: computed(() => props.post.meta_description || props.post.excerpt) },
{ property: 'og:title', content: computed(() => props.post.meta_title || props.post.title) },
{ property: 'og:description', content: computed(() => props.post.meta_description || props.post.excerpt) },
{ property: 'og:image', content: computed(() => props.post.cover_image ? `https://VOTRE_DOMAINE.com/${props.post.cover_image}` : 'https://VOTRE_DOMAINE.com/images/default-og-image.jpg') },
{ property: 'og:type', content: 'article' },
{ property: 'og:url', content: computed(() => blogRoutes.show(props.post.slug).url) }, // Utilisation de Wayfinder
{ name: 'twitter:card', content: 'summary_large_image' },
{ name: 'twitter:title', content: computed(() => props.post.meta_title || props.post.title) },
{ name: 'twitter:description', content: computed(() => props.post.meta_description || props.post.excerpt) },
{ name: 'twitter:image', content: computed(() => props.post.cover_image ? `https://VOTRE_DOMAINE.com/${props.post.cover_image}` : 'https://VOTRE_DOMAINE.com/images/default-og-image.jpg') },
],
script: [{ type: 'application/ld+json', children: jsonLd }], // On utilise la computed que nous venons de créer
});
// --- ÉTAPE 3 : RESTE DE LA LOGIQUE DU COMPOSANT ---
// ... (le reste de votre code pour les commentaires, etc., va ici)
</script>
<template>
<!--
Le composant <Head> d'Inertia est toujours utile pour le rendu
initial côté serveur. Il fonctionne en parallèle de @vueuse/head.
-->
<Head :title="props.post.meta_title || props.post.title" />
<!-- Le reste de votre template HTML pour l'article... -->
</template>⚠ Pièges Courants et Bonnes Pratiques
Le code ci-dessus est le fruit de notre débogage. Voici pourquoi il est structuré ainsi :
L'Erreur
Duplicate key 'post': Elle se produit si vous essayez de créer une variablepostaveccomputedalors quedefinePropsla rend déjà accessible. Solution : Utilisez toujoursprops.postdirectement.L'Erreur
Converting circular structure to JSON: Elle arrive quand vous essayez de mettre un objetcomputeddirectement dansJSON.stringifypour le JSON-LD. Solution : Créez unecomputeddédiée (notrejsonLd) qui retourne la chaîne JSON. C'est plus propre et évite les erreurs de sérialisation.L'Ordre de Déclaration :
definePropsdoit être appelé au tout début de votre<script setup>. Cela garantit que la variablepropsest définie et utilisable par toutes lescomputedqui suivent.
Étape 4 : Vérification
Comment savoir si tout fonctionne ?
Ouvrez votre article dans le navigateur.
Faites un clic droit -> "Inspecter".
Allez dans l'onglet "Éléments" et regardez la balise
<head>. Vous devriez voir toutes vos balises<title>,<meta name="description">,<meta property="og:title">, etc., avec les bonnes informations !Testez sur les réseaux sociaux :
Facebook : Utilisez le Facebook Sharing Debugger.
Twitter : Utilisez le Twitter Card Validator.
Conclusion
Félicitations ! Vous venez de mettre en place une solution de SEO robuste, moderne et parfaitement intégrée à votre stack technique. Vos articles sont maintenant optimisés pour les moteurs de recherche et prêts à être partagés sur les réseaux sociaux avec un rendu professionnel.
Lien util : formations seo



