Aller au contenu principal

[Lv2] Implémentation SSR: Data Fetching et gestion des Meta SEO

Dans un projet Nuxt 3: mettre en place le chargement de données SSR et la gestion dynamique des meta SEO pour que les routes dynamiques soient correctement indexées.


1. Axe de réponse en entretien​

  1. Stratégie de Data Fetching: utiliser useFetch/useAsyncData côté serveur pour prérendre un contenu SEO complet.
  2. Meta tags dynamiques: générer les meta avec useHead ou useSeoMeta selon les données de la page.
  3. Performance: déduplication des requêtes, cache serveur et séparation claire SSR/CSR.

2. Bien utiliser useFetch / useAsyncData​

2.1 Pourquoi SSR Data Fetching est nécessaire​

Situation typique:

  • Les routes dynamiques (ex: /products/[id]) dépendent d'une API.
  • Si le chargement est uniquement client, le crawler voit un contenu incomplet.
  • L'objectif est un HTML complet déjà rendu côté serveur.

Solution: utiliser useFetch ou useAsyncData dans Nuxt 3.

2.2 Exemple de base avec useFetch​

Fichier: pages/products/[id].vue

// usage de base
const { data: product } = await useFetch(`/api/products/${route.params.id}`);

Options importantes:

OptionRôleDéfaut
keyClé unique pour la déduplication des requêtesauto
lazyCharge en différé (sans bloquer SSR)false
serverExécuté côté serveurtrue
defaultValeur de fallbacknull
transformTransformation de réponse-

2.3 Exemple complet​

// pages/products/[id].vue
const { data: product } = await useFetch(`/api/products/${route.params.id}`, {
key: `product-${route.params.id}`, // évite les requêtes dupliquées
lazy: false, // SSR attend la fin du chargement
server: true, // force l'exécution serveur
default: () => ({
id: null,
name: '',
description: '',
image: '',
}),
transform: (data: any) => {
// normalisation de données
return {
...data,
formattedPrice: formatPrice(data.price),
};
},
});

Pourquoi ces options sont critiques:

  1. key
    • Active la déduplication de requêtes
    • Même key => une seule requête effective
  2. lazy: false
    • Le rendu SSR attend les données
    • Le crawler voit un contenu déjà complet
  3. server: true
    • Garantit l'exécution dans le pipeline SSR

2.4 useAsyncData vs useFetch​

CritèreuseFetchuseAsyncData
Usage principalAppels APIToute logique asynchrone
ErgonomieURL/headers simplifiésLogique manuelle plus libre
Cas courantChargement de données HTTPRequêtes DB, agrégation, fichiers
// useFetch: centré API
const { data } = await useFetch('/api/products/123');

// useAsyncData: logique asynchrone libre
const { data } = await useAsyncData('products', async () => {
const result = await someAsyncOperation();
return result;
});

2.5 $fetch vs useFetch​

Règle simple en entretien:

  • $fetch pour les actions utilisateur (click, submit, load more).
  • useFetch pour le chargement initial avec sync SSR/Hydration.

$fetch en bref:

  • Client HTTP bas niveau (ofetch)
  • Pas de transfert d'état SSR vers client
  • Utilisé directement dans setup() -> risque de double fetch

useFetch en bref:

  • Enveloppe useAsyncData + $fetch
  • Compatible Hydration
  • Retourne data, pending, error, refresh

Comparatif:

AspectuseFetch$fetch
Sync état SSROuiNon
Retourrefs réactifsPromise avec données brutes
Cas principalData Fetching au chargement pageOpérations déclenchées par UI
// Correct: chargement initial
const { data } = await useFetch('/api/user');

// Correct: action utilisateur
const submitForm = async () => {
await $fetch('/api/submit', { method: 'POST', body: form });
};

// À éviter: $fetch direct dans setup
const data = await $fetch('/api/user');

3. Gestion des meta SEO avec useHead​

3.1 Pourquoi des meta tags dynamiques​

Situation typique:

  • Pages produit/article générées dynamiquement.
  • Chaque URL doit avoir son title, description, og:image, canonical.
  • Les aperçus sociaux (Open Graph/Twitter) doivent être cohérents.

Solution: useHead ou useSeoMeta.

3.2 Exemple useHead​

useHead({
title: () => product.value?.name,
meta: [
{ name: 'description', content: () => product.value?.description },
{ property: 'og:title', content: () => product.value?.name },
{ property: 'og:image', content: () => product.value?.image },
],
link: [
{
rel: 'canonical',
href: () => `https://example.com/products/${product.value?.id}`,
},
],
});

Bonnes pratiques:

  1. Fournir des fonctions (() => ...) pour garder des meta réactifs.
  2. Couvrir title, description, OG et canonical.
  3. Sur les 404, ajouter noindex, nofollow.

3.3 Variante compacte avec useSeoMeta​

useSeoMeta({
title: () => product.value?.name,
description: () => product.value?.description,
ogTitle: () => product.value?.name,
ogDescription: () => product.value?.description,
ogImage: () => product.value?.image,
});

4. Cas pratique 1: SEO des routes dynamiques​

4.1 Contexte​

Scénario e-commerce avec beaucoup de pages SKU (/products/[id]).

Défis:

  • Beaucoup d'URLs dynamiques
  • Meta uniques par page
  • Gestion propre des 404
  • Éviter le duplicate content

4.2 Stratégie​

  1. Charger les données côté serveur (lazy: false, server: true)
  2. Lever une 404 propre (createError) si nécessaire
  3. Générer meta + canonical dynamiquement
const { data: product, error } = await useFetch(`/api/products/${id}`, {
key: `product-${id}`,
lazy: false,
server: true,
});

if (error.value || !product.value) {
throw createError({ statusCode: 404, statusMessage: 'Product not found' });
}

useSeoMeta({
title: () => `${product.value?.name} - Product`,
description: () => product.value?.description,
ogTitle: () => product.value?.name,
ogDescription: () => product.value?.description,
ogImage: () => product.value?.image,
});

4.3 Résultat​

Avant:

  • Contenu incomplet pour les crawlers
  • Meta identiques sur plusieurs pages
  • 404 mal gérées

Après:

  • HTML SSR complet pour les crawlers
  • Meta uniques par route
  • Erreurs traitées proprement

5. Cas pratique 2: optimisation performance​

5.1 Problème​

Le SSR augmente la charge serveur. Sans optimisation, les temps de réponse et les coûts montent.

5.2 Stratégies appliquées​

  1. Déduplication de requêtes
const { data } = await useFetch('/api/product/123', {
key: 'product-123',
});
  1. Cache serveur (Nitro)
export default defineCachedEventHandler(
async (event) => {
return await getProductsFromDB();
},
{
maxAge: 60 * 60,
swr: true,
},
);
  1. Séparations SSR/CSR
  • Pages SEO critiques: SSR
  • Pages internes non indexables: CSR
  1. Stratégie CSS/Assets
  • Prioriser le critical CSS
  • Charger le non critique plus tard

5.3 Impact​

Avant:

  • Charge serveur élevée
  • Requêtes dupliquées
  • Pas de cache

Après:

  • Temps de réponse réduits
  • Charge backend/DB mieux maîtrisée
  • Performance plus stable sous charge

6. Réponses courtes pour entretien​

6.1 useFetch / useAsyncData​

Pour le chargement initial, j'utilise useFetch avec key, lazy: false et server: true pour garantir un rendu SSR complet et SEO-friendly.

6.2 Meta dynamiques​

Je gère les meta avec useHead/useSeoMeta et des valeurs sous forme de fonctions pour rester réactif sur les pages dynamiques.

6.3 Performance​

Je combine déduplication, cache serveur et répartition SSR/CSR pour réduire la charge tout en conservant la qualité SEO.


7. Bonnes pratiques​

7.1 Data Fetching​

  1. Toujours définir un key.
  2. Choisir lazy selon l'objectif SEO.
  3. Traiter les erreurs (404/5xx) explicitement.

7.2 Meta SEO​

  1. Utiliser des fonctions pour des meta réactifs.
  2. Couvrir title/description/OG/canonical.
  3. Protéger les pages d'erreur avec noindex, nofollow.

7.3 Performance​

  1. Activer le cache serveur.
  2. Limiter SSR aux pages qui en ont besoin.
  3. Optimiser le rendu initial (critical CSS, chargement différé).

8. Synthèse d'entretien​

Dans Nuxt 3, j'ai mis en place un pipeline SSR complet: chargement serveur des données, meta SEO dynamiques par route et optimisations de performance (déduplication, cache, SSR/CSR split). Le résultat est à la fois SEO-friendly et scalable en production.

Points clefs:

  • ✅ Bon usage de useFetch/useAsyncData
  • ✅ Meta dynamiques avec useHead/useSeoMeta
  • ✅ SEO des routes dynamiques
  • ✅ Optimisation performance basée sur des cas réels