[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fQHv08dX681fPKSNbi5ss00eVHIwQrUxYmfn5WT-XCNg":3,"$fr18UmhwDoKIu6_cTDsz8u9GKmEq1hBChu9ooPt_9pjs":25},{"_id":4,"slug":5,"__v":6,"author":7,"body":8,"canonical":9,"category":10,"createdAt":11,"date":12,"description":13,"htmlContent":14,"image":15,"imageAlt":15,"readingTime":16,"tags":17,"title":23,"updatedAt":24},"69d83cc6aa6b273b0cd8286f","changer-de-framework-next-js-vers-nuxt-ou-l-inverse-sans-perte-seo",0,"Equipe Seogard","Un e-commerce de 18 000 pages migre de Next.js vers Nuxt 3 en septembre. Résultat : -34% de trafic organique en six semaines. La cause n'est pas le framework cible — c'est la migration elle-même. Les URLs ont changé de structure, 2 400 redirections manquaient, et le SSR renvoyait du `200` sur des pages vides pendant trois jours sans que personne ne s'en aperçoive.\n\nChanger de framework JavaScript est un choix technique légitime. Le problème, c'est que Google ne voit pas un changement de framework : il voit des URLs qui disparaissent, du contenu qui change, et des signaux techniques qui se dégradent. Voici comment éviter la casse.\n\n## Cartographier l'existant avant de toucher une ligne de code\n\nLa première erreur est de commencer la migration par le code. Avant d'initialiser le moindre `npx nuxi init` ou `npx create-next-app`, vous devez avoir une photo complète de votre surface SEO actuelle.\n\n### Inventaire exhaustif des URLs indexées\n\nExportez la liste complète des URLs indexées depuis Google Search Console (rapport \"Pages\" > filtre \"Indexée\"). Croisez avec un crawl Screaming Frog complet de votre site actuel. Les deux listes ne matcheront pas parfaitement — c'est normal. Ce qui compte, c'est l'union des deux ensembles.\n\nPour un site de 18 000 pages, un crawl Screaming Frog prend entre 45 minutes et 3 heures selon votre config serveur. Configurez-le pour extraire :\n\n- URL, status code, title, meta description, canonical, hreflang\n- En-têtes H1, structured data (JSON-LD)\n- Temps de réponse serveur (TTFB)\n- Mode de rendu (vérifiez si la page est SSR ou CSR via l'extraction custom du HTML brut)\n\n```bash\n# Export Screaming Frog en CLI pour intégration CI\nscreamingfrog --crawl https://www.maboutique.fr \\\n  --headless \\\n  --output-folder ./crawl-baseline \\\n  --export-tabs \"Internal:All,Response Codes:All\" \\\n  --save-crawl ./crawl-baseline/baseline.seospider\n```\n\nCet export devient votre **baseline**. Chaque URL de cette baseline doit être gérée dans la migration : soit redirigée, soit recréée à l'identique, soit explicitement supprimée (avec un 410).\n\n### Documenter la structure d'URL existante\n\nNext.js et Nuxt utilisent des conventions de routing différentes. Next.js avec App Router utilise des dossiers dans `app/`, Nuxt 3 utilise `pages/`. Les patterns de routes dynamiques divergent :\n\n```\n# Next.js App Router\napp/produits/[slug]/page.tsx        → /produits/chaussure-running-x500\napp/categories/[...path]/page.tsx   → /categories/homme/chaussures/running\n\n# Nuxt 3\npages/produits/[slug].vue           → /produits/chaussure-running-x500\npages/categories/[...path].vue      → /categories/homme/chaussures/running\n```\n\nDans les deux cas, les URLs générées peuvent être identiques — et c'est exactement ce que vous visez. Si votre structure d'URL actuelle est propre (`/produits/slug`, `/categories/path`), conservez-la telle quelle dans le nouveau framework. Ne profitez pas de la migration pour \"nettoyer\" les URLs. Chaque URL modifiée est un risque de perte de link equity.\n\nSi votre structure actuelle utilise des patterns spécifiques à Next.js (comme des query strings `?slug=xxx` issues d'un ancien `getServerSideProps` mal configuré), c'est le moment de les corriger — mais avec des redirections 301 systématiques.\n\nPour un audit complet des vérifications à prévoir, appuyez-vous sur la [checklist des 20 vérifications SEO de refonte](/blog/refonte-de-site-les-20-verifications-seo-indispensables) qui couvre les fondamentaux au-delà du framework.\n\n## Gérer le SSR : le vrai risque invisible\n\nLe framework change, le mode de rendu ne doit pas régresser. Si votre site Next.js sert du HTML complet via `getServerSideProps` ou le App Router en mode server components, votre Nuxt 3 doit servir un HTML équivalent via `useAsyncData` ou `useFetch` en mode SSR. Et inversement.\n\n### Vérifier le rendu serveur page par page\n\nNe vous fiez pas à la config globale. Nuxt 3 permet de mixer SSR et CSR au niveau de chaque route via `routeRules`. Next.js permet le même mix via les directives `\"use client\"` et les segments de route. Un composant mal placé peut transformer une page SSR en coquille vide côté Googlebot.\n\nVoici comment vérifier que le HTML servi contient bien le contenu critique :\n\n```bash\n# Vérifier le HTML brut reçu par un crawler (sans exécution JS)\ncurl -s -A \"Googlebot\" https://staging.maboutique.fr/produits/chaussure-running-x500 \\\n  | grep -c \"\u003Ch1>\"\n\n# Doit retourner 1. Si 0, le H1 est rendu côté client uniquement.\n\n# Vérification plus complète avec extraction du title et de la meta description\ncurl -s https://staging.maboutique.fr/produits/chaussure-running-x500 \\\n  | grep -oP '(?\u003C=\u003Ctitle>).*?(?=\u003C/title>)'\n\ncurl -s https://staging.maboutique.fr/produits/chaussure-running-x500 \\\n  | grep -oP '(?\u003C=name=\"description\" content=\").*?(?=\")'\n```\n\nFaites ce test sur un échantillon de chaque type de page : fiches produit, catégories, pages CMS, blog. Sur un site de 18 000 pages, un échantillon de 50-100 URLs couvrant tous les templates suffit pour le staging. Mais en production, vous avez besoin d'un monitoring continu — un déploiement peut casser le SSR d'un template entier sans que vos tests manuels le détectent.\n\nLes [Chrome DevTools offrent des méthodes avancées](/blog/chrome-devtools-pour-le-seo-astuces-avancees) pour inspecter le rendu côté serveur vs côté client, notamment via la désactivation de JavaScript dans le panneau Network.\n\n### Configuration SSR dans Nuxt 3 avec routeRules\n\nSi vous migrez de Next.js vers Nuxt 3, voici un exemple de `nuxt.config.ts` qui reproduit un comportement SSR équivalent, avec gestion fine par type de route :\n\n```typescript\n// nuxt.config.ts\nexport default defineNuxtConfig({\n  ssr: true, // SSR activé globalement\n\n  routeRules: {\n    // Pages produit : SSR + cache CDN 1h (équivalent ISR Next.js)\n    '/produits/**': {\n      swr: 3600,\n      headers: { 'Cache-Control': 's-maxage=3600, stale-while-revalidate=86400' }\n    },\n\n    // Pages catégorie : SSR pur, pas de cache (contenu dynamique)\n    '/categories/**': {\n      swr: false\n    },\n\n    // Pages statiques (CGV, mentions légales) : pré-rendues au build\n    '/pages/**': {\n      prerender: true\n    },\n\n    // Dashboard utilisateur : pas de SSR, pas d'indexation\n    '/compte/**': {\n      ssr: false,\n      headers: { 'X-Robots-Tag': 'noindex' }\n    }\n  },\n\n  // Reproduire les headers SEO critiques\n  nitro: {\n    routeRules: {\n      '/**': {\n        headers: {\n          'X-Frame-Options': 'SAMEORIGIN',\n          'X-Content-Type-Options': 'nosniff'\n        }\n      }\n    }\n  }\n})\n```\n\nLe piège classique : oublier que `swr` (Stale-While-Revalidate) dans Nuxt 3 est l'équivalent d'ISR dans Next.js, mais avec des différences de comportement au premier hit. Si votre cache CDN n'est pas warm, le premier visiteur (potentiellement Googlebot) reçoit une réponse SSR complète mais plus lente. Mesurez votre TTFB sur le cold start.\n\n### Configuration équivalente dans Next.js (migration inverse)\n\nSi vous allez de Nuxt vers Next.js avec App Router :\n\n```typescript\n// app/produits/[slug]/page.tsx\nimport { Metadata } from 'next'\n\n// Équivalent du swr: 3600 de Nuxt\nexport const revalidate = 3600\n\nexport async function generateMetadata(\n  { params }: { params: { slug: string } }\n): Promise\u003CMetadata> {\n  const product = await getProduct(params.slug)\n\n  return {\n    title: product.seoTitle,\n    description: product.seoDescription,\n    alternates: {\n      canonical: `https://www.maboutique.fr/produits/${params.slug}`\n    },\n    openGraph: {\n      title: product.seoTitle,\n      description: product.seoDescription,\n      images: [product.ogImage]\n    }\n  }\n}\n\nexport default async function ProductPage(\n  { params }: { params: { slug: string } }\n) {\n  const product = await getProduct(params.slug)\n\n  // JSON-LD injecté dans le HTML serveur\n  const jsonLd = {\n    '@context': 'https://schema.org',\n    '@type': 'Product',\n    name: product.name,\n    description: product.description,\n    offers: {\n      '@type': 'Offer',\n      price: product.price,\n      priceCurrency: 'EUR',\n      availability: product.inStock\n        ? 'https://schema.org/InStock'\n        : 'https://schema.org/OutOfStock'\n    }\n  }\n\n  return (\n    \u003C>\n      \u003Cscript\n        type=\"application/ld+json\"\n        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}\n      />\n      \u003Ch1>{product.name}\u003C/h1>\n      {/* ... */}\n    \u003C/>\n  )\n}\n```\n\nPoint critique : dans Next.js App Router, `generateMetadata` s'exécute côté serveur et injecte les balises `\u003Chead>` dans le HTML initial. Si vous oubliez cette fonction et mettez vos meta dans un `useEffect` côté client, Googlebot ne les verra pas dans le HTML initial (même s'il exécute du JavaScript, le timing est aléatoire).\n\n## Le plan de redirections : l'étape qui fait ou défait la migration\n\nSi vos URLs ne changent pas entre les deux frameworks, vous n'avez pas besoin de redirections. C'est le scénario idéal. Mais en pratique, trois situations forcent un changement d'URL :\n\n1. **Le trailing slash** : Next.js par défaut ne met pas de trailing slash, Nuxt 3 si (`trailingSlash: true` dans la config). Si vous ne harmonisez pas, chaque URL est en doublon.\n2. **Les routes dynamiques catch-all** : le pattern `[...slug]` peut générer des paths différents selon le framework.\n3. **Les URLs générées par des plugins** : sitemap, pagination, filtres à facettes.\n\n### Construire la table de redirections\n\nPartez de votre baseline Screaming Frog. Pour chaque URL indexée, mappez l'URL cible dans le nouveau framework. Le format de travail le plus efficace :\n\n```csv\nsource,target,status\n/produits/chaussure-running-x500,/produits/chaussure-running-x500,200\n/produits/chaussure-running-x500/,/produits/chaussure-running-x500,301\n/categorie/homme/chaussures,/categories/homme/chaussures,301\n/blog/article-ancien?page=2,/blog/article-ancien?page=2,200\n```\n\nImplémentez les redirections au niveau le plus proche du serveur. Ne les mettez pas dans le code applicatif du framework — un bug de déploiement les ferait sauter.\n\n### Redirections au niveau Nginx\n\n```nginx\n# /etc/nginx/conf.d/redirections-migration.conf\n\n# Trailing slash → sans trailing slash (migration Nuxt → Next.js)\nrewrite ^(.+)/$ $1 permanent;\n\n# Changement de segment d'URL\nlocation ~ ^/categorie/(.*)$ {\n    return 301 /categories/$1;\n}\n\n# Redirections unitaires depuis un map (performant pour des milliers d'URLs)\nmap $request_uri $redirect_target {\n    include /etc/nginx/redirections-migration.map;\n}\n\nserver {\n    # ...\n    if ($redirect_target) {\n        return 301 $redirect_target;\n    }\n}\n```\n\nLe fichier `.map` contient une ligne par redirection — gérable même pour 10 000+ URLs. C'est plus performant qu'une cascade de `location` blocks, et ça ne touche pas au code applicatif.\n\nSi vous êtes sur Vercel ou Netlify, utilisez les fichiers de redirections natifs (`vercel.json` ou `_redirects`), mais soyez conscient des limites : Vercel supporte jusqu'à 1 024 redirections dans `vercel.json` en plan Pro. Au-delà, il faut passer par le middleware Edge — qui ajoute de la latence.\n\nPour les stratégies de redirection au niveau CDN, la technique d'[Edge SEO via modification des réponses HTTP](/blog/edge-seo-modifier-les-reponses-http-au-niveau-cdn) offre une flexibilité supplémentaire sans toucher au code applicatif.\n\n## Scénario concret : migration d'un e-commerce de 18 000 pages\n\nPrenons le cas réaliste d'un e-commerce mode avec cette surface :\n\n- **12 400** fiches produit (`/produits/[slug]`)\n- **380** pages catégorie (`/categories/[...path]`)\n- **1 200** pages de blog (`/blog/[slug]`)\n- **4 020** pages de filtre à facettes (indexées avec canonical vers la catégorie parente)\n- **Total indexé dans GSC** : ~14 200 pages (les facettes sont partiellement indexées)\n- **Trafic organique** : 185 000 sessions/mois\n- **Framework source** : Next.js 14 (App Router, déployé sur Vercel)\n- **Framework cible** : Nuxt 3 (déployé sur un VPS avec Nginx + PM2)\n\n### Chronologie de migration\n\n**Semaine -4 à -2 : Préparation**\n\nCrawl baseline avec Screaming Frog. Export GSC des 16 derniers mois de données (pour capturer la saisonnalité). Identification de 340 URLs avec des problèmes pré-existants (soft 404, canonicals incorrects). Décision de les corriger pendant la migration — une erreur classique est de traiter ces corrections comme un bonus gratuit, mais chaque changement simultané complique le diagnostic post-migration.\n\n**Semaine -2 à J-0 : Développement**\n\n- Environnement staging Nuxt 3 avec toutes les routes mappées\n- Crawl Screaming Frog du staging : comparaison automatisée des title, H1, canonical, status codes avec la baseline\n- Test de rendu SSR sur 200 URLs échantillonnées via `curl` + script bash\n- Validation des données structurées JSON-LD (Product, BreadcrumbList, Article) avec le [Rich Results Test](https://search.google.com/test/rich-results) de Google\n- Mise en place du fichier de redirections Nginx (287 redirections identifiées)\n\n**J-0 : Bascule**\n\nDéploiement en soirée (20h, heure de faible trafic). Vérification immédiate :\n\n1. Curl sur 50 URLs critiques (top trafic) : status 200, HTML contient H1 et meta\n2. Vérification des redirections : les 287 retournent bien 301\n3. Soumission du sitemap mis à jour dans Google Search Console\n4. Demande d'indexation manuelle sur les 20 pages les plus importantes\n\n**J+1 à J+7 : Monitoring intensif**\n\n- Crawl quotidien d'un échantillon de 500 URLs pour détecter les régressions SSR\n- Surveillance des logs serveur : pic de 404 = redirections manquantes\n- Vérification dans GSC du rapport \"Pages\" : les nouvelles URLs commencent à apparaître dans \"Découverte\"\n\n**J+7 à J+30 : Stabilisation**\n\n- Le trafic organique chute de 12% la première semaine — c'est dans la fourchette attendue pour une migration propre\n- Récupération à 95% du trafic initial à J+21\n- Retour au niveau pré-migration à J+28\n- Identification de 43 URLs orphelines (présentes dans la baseline mais absentes du nouveau sitemap) — corrigées à J+10\n\nUn outil de monitoring comme Seogard détecte automatiquement les régressions de type \"meta description disparue\" ou \"SSR cassé retournant un body vide\" dès le déploiement, sans attendre que le trafic chute pour s'en rendre compte.\n\n### Ce qui aurait mal tourné sans préparation\n\nSans la baseline Screaming Frog, les 43 URLs orphelines auraient été découvertes uniquement via la chute de trafic — entre 2 et 4 semaines plus tard. Sans le test SSR systématique, les pages catégorie servies en CSR pur (un bug dans le composant `AsyncData`) auraient perdu leur indexation pendant des semaines. Sans les redirections pour le trailing slash, les 12 400 fiches produit auraient eu un doublon indexable.\n\n## Checklist des éléments SEO à transférer entre frameworks\n\nAu-delà des URLs et du SSR, une migration de framework touche des dizaines d'éléments SEO rarement listés dans les guides génériques.\n\n### Balises meta et canonicals\n\nChaque template de page dans le nouveau framework doit reproduire exactement les mêmes balises que l'ancien. Pas \"à peu près les mêmes\" — exactement les mêmes, caractère par caractère pour les canonicals.\n\nVérifiez en particulier :\n- Les canonicals auto-référentiels (chaque page pointe vers elle-même)\n- Les canonicals des pages à facettes (pointent vers la catégorie parente)\n- Les meta robots sur les pages de compte, panier, checkout\n- Les balises hreflang si le site est multilingue\n\nSur le sujet du contenu dupliqué généré par les facettes, la gestion des [canonicals et du contenu dupliqué](/blog/contenu-duplique-causes-techniques-et-solutions) est un prérequis à maîtriser avant la migration.\n\n### Données structurées JSON-LD\n\nLes données structurées sont souvent générées dynamiquement dans le composant page. Lors du changement de framework, le format peut subtilement changer (ordre des propriétés, présence/absence de champs optionnels). Ce n'est pas un problème SEO en soi, mais c'est l'occasion de valider que tout est conforme.\n\nExportez les données structurées de chaque template avec un script :\n\n```javascript\n// scripts/validate-jsonld.mjs\n// Exécution : node scripts/validate-jsonld.mjs urls.txt\n\nimport { readFileSync } from 'fs'\n\nconst urls = readFileSync(process.argv[2], 'utf-8')\n  .split('\\n')\n  .filter(Boolean)\n\nfor (const url of urls) {\n  try {\n    const res = await fetch(url, {\n      headers: { 'User-Agent': 'SEO-Migration-Validator/1.0' }\n    })\n    const html = await res.text()\n\n    // Extraction de tous les blocs JSON-LD\n    const jsonLdBlocks = html.match(\n      /\u003Cscript type=\"application\\/ld\\+json\">([\\s\\S]*?)\u003C\\/script>/g\n    )\n\n    if (!jsonLdBlocks || jsonLdBlocks.length === 0) {\n      console.error(`[MISSING] ${url} — Aucun JSON-LD trouvé`)\n      continue\n    }\n\n    for (const block of jsonLdBlocks) {\n      const json = block\n        .replace('\u003Cscript type=\"application/ld+json\">', '')\n        .replace('\u003C/script>', '')\n\n      try {\n        const parsed = JSON.parse(json)\n        const type = parsed['@type'] || parsed?.['@graph']?.[0]?.['@type'] || 'unknown'\n        console.log(`[OK] ${url} — @type: ${type}`)\n      } catch (e) {\n        console.error(`[INVALID JSON] ${url} — ${e.message}`)\n      }\n    }\n  } catch (e) {\n    console.error(`[FETCH ERROR] ${url} — ${e.message}`)\n  }\n}\n```\n\nExécutez ce script sur votre baseline (ancien site) et sur le staging (nouveau site), puis diff les résultats.\n\n### Sitemap XML\n\nNext.js et Nuxt gèrent les sitemaps différemment. Next.js App Router permet de créer un `app/sitemap.ts` qui génère dynamiquement le sitemap. Nuxt 3 utilise le module `@nuxtjs/sitemap`.\n\nLe piège : le nouveau sitemap doit être soumis **immédiatement** après la bascule. Si Google crawle le site entre le déploiement et la soumission du sitemap, il peut découvrir des 404 avant de trouver les redirections.\n\n### Fichier robots.txt\n\nVérifiez que le `robots.txt` du nouveau framework n'est pas celui par défaut (qui peut contenir un `Disallow: /` en mode staging). C'est un classique qui bloque le crawl de l'intégralité du site pendant des heures ou des jours.\n\n### Performance et Core Web Vitals\n\nLe changement de framework impacte les métriques de performance. Nuxt 3 avec Nitro a un profil de performance différent de Next.js avec le runtime Edge de Vercel. Mesurez le TTFB, le LCP et le CLS avant et après migration sur vos templates principaux.\n\nUn changement de framework ne devrait pas dégrader les Core Web Vitals si le code est correctement implémenté. Mais le \"correctement\" est un piège : une hydratation client trop agressive dans Nuxt, ou un bundle trop lourd dans Next.js, peut ajouter 200-500ms de LCP.\n\n## Monitoring post-migration : les 30 jours critiques\n\nLes KPIs à surveiller quotidiennement pendant le premier mois :\n\n### Dans Google Search Console\n\n- **Couverture d'indexation** : le nombre de pages \"Valides\" ne doit pas chuter. Si des pages passent en \"Exclues\" avec la raison \"Page avec redirection\", c'est normal temporairement.\n- **Performance** : suivez les clics et impressions par type de page (regex dans le filtre de pages). Une chute de 10-15% la première semaine est normale. Au-delà de 20%, investiguez.\n- **Rapport d'exploration** : vérifiez que le crawl rate ne s'effondre pas. Un TTFB dégradé sur le nouveau framework peut réduire le crawl budget alloué par Google.\n\nPour tirer le maximum de Search Console pendant cette phase critique, les [rapports souvent ignorés de GSC](/blog/google-search-console-les-rapports-que-vous-ignorez) contiennent des signaux précieux pour détecter les problèmes tôt.\n\n### Dans vos logs serveur\n\nParsez les logs pour identifier :\n- Les URLs avec un volume anormal de 404 (redirections manquantes)\n- Les User-Agents Googlebot qui reçoivent des réponses différentes des visiteurs normaux (problème de caching ou de rendering conditionnel)\n- Les temps de réponse supérieurs à 800ms (seuil critique pour le crawl budget)\n\n### Avec Screaming Frog en mode monitoring\n\nConfigurez un crawl hebdomadaire pendant les 30 premiers jours, puis mensuel. Comparez systématiquement avec la baseline. Tout écart doit être investigué.\n\nPour suivre l'impact global de la migration avec les bons indicateurs, les [KPIs de suivi SEO technique](/blog/mesurer-l-impact-seo-technique-quels-kpis-suivre) fournissent un cadre structuré.\n\n## Les edge cases que personne ne mentionne\n\n### Les pages en cache de Google pendant la transition\n\nGoogle peut continuer à afficher l'ancienne version de vos pages dans son cache pendant 2 à 4 semaines après la migration. Ce n'est pas un problème SEO, mais ça peut perturber votre monitoring si vous comparez le cache Google avec votre nouveau site.\n\n### Les backlinks pointant vers des URLs avec query strings\n\nVos backlinks les plus précieux peuvent pointer vers des URLs avec des paramètres (`?utm_source=...`, `?ref=...`). Vérifiez que votre nouveau framework gère ces paramètres correctement et ne retourne pas de 404 dessus. Next.js et Nuxt ont des comportements différents sur les query strings non déclarées dans le routing.\n\n### L'impact sur les bots IA\n\nLes bots de type GPTBot ou ClaudeBot crawlent de plus en plus agressivement. Un changement de framework peut modifier votre `robots.txt` et accidentellement bloquer ou autoriser ces bots. L'[augmentation du crawl par les bots IA](/blog/chatgpt-now-crawls-3-6x-more-than-googlebot-what-24m-requests-reveal) rend ce point d'autant plus critique — un pic de crawl IA pendant la migration peut surcharger votre serveur et dégrader les temps de réponse pour Googlebot.\n\n### Les tests A/B SEO pendant une migration\n\nNe lancez pas de test A/B SEO pendant une migration de framework. Vous ne pourrez pas isoler les variables. Attendez au minimum 6 semaines post-migration avec un trafic stabilisé. L'article sur [l'A/B testing SEO sans pénaliser le référencement](/blog/a-b-testing-seo-tester-sans-penaliser-le-referencement) détaille les conditions nécessaires pour des tests fiables.\n\nSi votre pipeline CI/CD intègre des checks SEO automatisés, adaptez-les au nouveau framework dès le premier commit. Les [checks SEO dans le CI/CD](/blog/automatiser-les-checks-seo-dans-le-ci-cd) sont votre filet de sécurité : ils détectent les meta manquantes, les canonicals cassés ou le SSR désactivé avant que le code n'atteigne la production.\n\n---\n\nUne migration de framework n'est pas un projet SEO — c'est un projet d'infrastructure qui a des conséquences SEO massives si mal exécuté. La baseline, les redirections, le SSR et le monitoring post-migration sont les quatre piliers qui séparent une migration transparente d'une catastrophe organique. Outillez chaque étape : Screaming Frog pour la baseline, `curl` et des scripts custom pour le SSR, Nginx pour les redirections, et un monitoring continu avec Seogard pour détecter instantanément les régressions que vos tests manuels rateront forcément.\n```","https://seogard.io/blog/changer-de-framework-next-js-vers-nuxt-ou-l-inverse-sans-perte-seo","Migration","2026-04-09T23:56:54.564Z","2026-04-09","Guide technique complet pour migrer entre Next.js et Nuxt sans perdre de trafic organique : redirections, SSR, sitemap, monitoring et cas concret.","\u003Cp>Un e-commerce de 18 000 pages migre de Next.js vers Nuxt 3 en septembre. Résultat : -34% de trafic organique en six semaines. La cause n'est pas le framework cible — c'est la migration elle-même. Les URLs ont changé de structure, 2 400 redirections manquaient, et le SSR renvoyait du \u003Ccode>200\u003C/code> sur des pages vides pendant trois jours sans que personne ne s'en aperçoive.\u003C/p>\n\u003Cp>Changer de framework JavaScript est un choix technique légitime. Le problème, c'est que Google ne voit pas un changement de framework : il voit des URLs qui disparaissent, du contenu qui change, et des signaux techniques qui se dégradent. Voici comment éviter la casse.\u003C/p>\n\u003Ch2>Cartographier l'existant avant de toucher une ligne de code\u003C/h2>\n\u003Cp>La première erreur est de commencer la migration par le code. Avant d'initialiser le moindre \u003Ccode>npx nuxi init\u003C/code> ou \u003Ccode>npx create-next-app\u003C/code>, vous devez avoir une photo complète de votre surface SEO actuelle.\u003C/p>\n\u003Ch3>Inventaire exhaustif des URLs indexées\u003C/h3>\n\u003Cp>Exportez la liste complète des URLs indexées depuis Google Search Console (rapport \"Pages\" > filtre \"Indexée\"). Croisez avec un crawl Screaming Frog complet de votre site actuel. Les deux listes ne matcheront pas parfaitement — c'est normal. Ce qui compte, c'est l'union des deux ensembles.\u003C/p>\n\u003Cp>Pour un site de 18 000 pages, un crawl Screaming Frog prend entre 45 minutes et 3 heures selon votre config serveur. Configurez-le pour extraire :\u003C/p>\n\u003Cul>\n\u003Cli>URL, status code, title, meta description, canonical, hreflang\u003C/li>\n\u003Cli>En-têtes H1, structured data (JSON-LD)\u003C/li>\n\u003Cli>Temps de réponse serveur (TTFB)\u003C/li>\n\u003Cli>Mode de rendu (vérifiez si la page est SSR ou CSR via l'extraction custom du HTML brut)\u003C/li>\n\u003C/ul>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Export Screaming Frog en CLI pour intégration CI\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">screamingfrog\u003C/span>\u003Cspan style=\"color:#79B8FF\"> --crawl\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> https://www.maboutique.fr\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  --headless\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  --output-folder\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> ./crawl-baseline\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  --export-tabs\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"Internal:All,Response Codes:All\"\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  --save-crawl\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> ./crawl-baseline/baseline.seospider\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Cet export devient votre \u003Cstrong>baseline\u003C/strong>. Chaque URL de cette baseline doit être gérée dans la migration : soit redirigée, soit recréée à l'identique, soit explicitement supprimée (avec un 410).\u003C/p>\n\u003Ch3>Documenter la structure d'URL existante\u003C/h3>\n\u003Cp>Next.js et Nuxt utilisent des conventions de routing différentes. Next.js avec App Router utilise des dossiers dans \u003Ccode>app/\u003C/code>, Nuxt 3 utilise \u003Ccode>pages/\u003C/code>. Les patterns de routes dynamiques divergent :\u003C/p>\n\u003Cpre>\u003Ccode># Next.js App Router\napp/produits/[slug]/page.tsx        → /produits/chaussure-running-x500\napp/categories/[...path]/page.tsx   → /categories/homme/chaussures/running\n\n# Nuxt 3\npages/produits/[slug].vue           → /produits/chaussure-running-x500\npages/categories/[...path].vue      → /categories/homme/chaussures/running\n\u003C/code>\u003C/pre>\n\u003Cp>Dans les deux cas, les URLs générées peuvent être identiques — et c'est exactement ce que vous visez. Si votre structure d'URL actuelle est propre (\u003Ccode>/produits/slug\u003C/code>, \u003Ccode>/categories/path\u003C/code>), conservez-la telle quelle dans le nouveau framework. Ne profitez pas de la migration pour \"nettoyer\" les URLs. Chaque URL modifiée est un risque de perte de link equity.\u003C/p>\n\u003Cp>Si votre structure actuelle utilise des patterns spécifiques à Next.js (comme des query strings \u003Ccode>?slug=xxx\u003C/code> issues d'un ancien \u003Ccode>getServerSideProps\u003C/code> mal configuré), c'est le moment de les corriger — mais avec des redirections 301 systématiques.\u003C/p>\n\u003Cp>Pour un audit complet des vérifications à prévoir, appuyez-vous sur la \u003Ca href=\"/blog/refonte-de-site-les-20-verifications-seo-indispensables\">checklist des 20 vérifications SEO de refonte\u003C/a> qui couvre les fondamentaux au-delà du framework.\u003C/p>\n\u003Ch2>Gérer le SSR : le vrai risque invisible\u003C/h2>\n\u003Cp>Le framework change, le mode de rendu ne doit pas régresser. Si votre site Next.js sert du HTML complet via \u003Ccode>getServerSideProps\u003C/code> ou le App Router en mode server components, votre Nuxt 3 doit servir un HTML équivalent via \u003Ccode>useAsyncData\u003C/code> ou \u003Ccode>useFetch\u003C/code> en mode SSR. Et inversement.\u003C/p>\n\u003Ch3>Vérifier le rendu serveur page par page\u003C/h3>\n\u003Cp>Ne vous fiez pas à la config globale. Nuxt 3 permet de mixer SSR et CSR au niveau de chaque route via \u003Ccode>routeRules\u003C/code>. Next.js permet le même mix via les directives \u003Ccode>\"use client\"\u003C/code> et les segments de route. Un composant mal placé peut transformer une page SSR en coquille vide côté Googlebot.\u003C/p>\n\u003Cp>Voici comment vérifier que le HTML servi contient bien le contenu critique :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Vérifier le HTML brut reçu par un crawler (sans exécution JS)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">curl\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -s\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -A\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"Googlebot\"\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> https://staging.maboutique.fr/produits/chaussure-running-x500\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  |\u003C/span>\u003Cspan style=\"color:#B392F0\"> grep\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -c\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"&#x3C;h1>\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Doit retourner 1. Si 0, le H1 est rendu côté client uniquement.\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Vérification plus complète avec extraction du title et de la meta description\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">curl\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -s\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> https://staging.maboutique.fr/produits/chaussure-running-x500\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  |\u003C/span>\u003Cspan style=\"color:#B392F0\"> grep\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -oP\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '(?&#x3C;=&#x3C;title>).*?(?=&#x3C;/title>)'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">curl\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -s\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> https://staging.maboutique.fr/produits/chaussure-running-x500\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  |\u003C/span>\u003Cspan style=\"color:#B392F0\"> grep\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -oP\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '(?&#x3C;=name=\"description\" content=\").*?(?=\")'\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Faites ce test sur un échantillon de chaque type de page : fiches produit, catégories, pages CMS, blog. Sur un site de 18 000 pages, un échantillon de 50-100 URLs couvrant tous les templates suffit pour le staging. Mais en production, vous avez besoin d'un monitoring continu — un déploiement peut casser le SSR d'un template entier sans que vos tests manuels le détectent.\u003C/p>\n\u003Cp>Les \u003Ca href=\"/blog/chrome-devtools-pour-le-seo-astuces-avancees\">Chrome DevTools offrent des méthodes avancées\u003C/a> pour inspecter le rendu côté serveur vs côté client, notamment via la désactivation de JavaScript dans le panneau Network.\u003C/p>\n\u003Ch3>Configuration SSR dans Nuxt 3 avec routeRules\u003C/h3>\n\u003Cp>Si vous migrez de Next.js vers Nuxt 3, voici un exemple de \u003Ccode>nuxt.config.ts\u003C/code> qui reproduit un comportement SSR équivalent, avec gestion fine par type de route :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">// nuxt.config.ts\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">export\u003C/span>\u003Cspan style=\"color:#F97583\"> default\u003C/span>\u003Cspan style=\"color:#B392F0\"> defineNuxtConfig\u003C/span>\u003Cspan style=\"color:#E1E4E8\">({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  ssr: \u003C/span>\u003Cspan style=\"color:#79B8FF\">true\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#6A737D\">// SSR activé globalement\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  routeRules: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    // Pages produit : SSR + cache CDN 1h (équivalent ISR Next.js)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    '/produits/**'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      swr: \u003C/span>\u003Cspan style=\"color:#79B8FF\">3600\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      headers: { \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Cache-Control'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'s-maxage=3600, stale-while-revalidate=86400'\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    // Pages catégorie : SSR pur, pas de cache (contenu dynamique)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    '/categories/**'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      swr: \u003C/span>\u003Cspan style=\"color:#79B8FF\">false\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    // Pages statiques (CGV, mentions légales) : pré-rendues au build\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    '/pages/**'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      prerender: \u003C/span>\u003Cspan style=\"color:#79B8FF\">true\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    // Dashboard utilisateur : pas de SSR, pas d'indexation\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    '/compte/**'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      ssr: \u003C/span>\u003Cspan style=\"color:#79B8FF\">false\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      headers: { \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'X-Robots-Tag'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'noindex'\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">  // Reproduire les headers SEO critiques\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  nitro: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    routeRules: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">      '/**'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        headers: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          'X-Frame-Options'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'SAMEORIGIN'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          'X-Content-Type-Options'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'nosniff'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">})\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Le piège classique : oublier que \u003Ccode>swr\u003C/code> (Stale-While-Revalidate) dans Nuxt 3 est l'équivalent d'ISR dans Next.js, mais avec des différences de comportement au premier hit. Si votre cache CDN n'est pas warm, le premier visiteur (potentiellement Googlebot) reçoit une réponse SSR complète mais plus lente. Mesurez votre TTFB sur le cold start.\u003C/p>\n\u003Ch3>Configuration équivalente dans Next.js (migration inverse)\u003C/h3>\n\u003Cp>Si vous allez de Nuxt vers Next.js avec App Router :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">// app/produits/[slug]/page.tsx\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> { Metadata } \u003C/span>\u003Cspan style=\"color:#F97583\">from\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'next'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">// Équivalent du swr: 3600 de Nuxt\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">export\u003C/span>\u003Cspan style=\"color:#F97583\"> const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> revalidate\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 3600\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">export\u003C/span>\u003Cspan style=\"color:#F97583\"> async\u003C/span>\u003Cspan style=\"color:#F97583\"> function\u003C/span>\u003Cspan style=\"color:#B392F0\"> generateMetadata\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  { \u003C/span>\u003Cspan style=\"color:#FFAB70\">params\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> { \u003C/span>\u003Cspan style=\"color:#FFAB70\">params\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> { \u003C/span>\u003Cspan style=\"color:#FFAB70\">slug\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#79B8FF\"> string\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> } }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#B392F0\"> Promise\u003C/span>\u003Cspan style=\"color:#E1E4E8\">&#x3C;\u003C/span>\u003Cspan style=\"color:#B392F0\">Metadata\u003C/span>\u003Cspan style=\"color:#E1E4E8\">> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> product\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#F97583\"> await\u003C/span>\u003Cspan style=\"color:#B392F0\"> getProduct\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(params.slug)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  return\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    title: product.seoTitle,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    description: product.seoDescription,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    alternates: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      canonical: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">`https://www.maboutique.fr/produits/${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">params\u003C/span>\u003Cspan style=\"color:#9ECBFF\">.\u003C/span>\u003Cspan style=\"color:#E1E4E8\">slug\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}`\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    openGraph: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      title: product.seoTitle,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      description: product.seoDescription,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      images: [product.ogImage]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">export\u003C/span>\u003Cspan style=\"color:#F97583\"> default\u003C/span>\u003Cspan style=\"color:#F97583\"> async\u003C/span>\u003Cspan style=\"color:#F97583\"> function\u003C/span>\u003Cspan style=\"color:#B392F0\"> ProductPage\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  { \u003C/span>\u003Cspan style=\"color:#FFAB70\">params\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> { \u003C/span>\u003Cspan style=\"color:#FFAB70\">params\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> { \u003C/span>\u003Cspan style=\"color:#FFAB70\">slug\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#79B8FF\"> string\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> } }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> product\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#F97583\"> await\u003C/span>\u003Cspan style=\"color:#B392F0\"> getProduct\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(params.slug)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">  // JSON-LD injecté dans le HTML serveur\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> jsonLd\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    '@context'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'https://schema.org'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    '@type'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Product'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    name: product.name,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    description: product.description,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    offers: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">      '@type'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Offer'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      price: product.price,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      priceCurrency: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'EUR'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      availability: product.inStock\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        ?\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'https://schema.org/InStock'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        :\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'https://schema.org/OutOfStock'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  return\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    &#x3C;>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">      &#x3C;\u003C/span>\u003Cspan style=\"color:#FFAB70\">script\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        type\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"application/ld+json\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        dangerouslySetInnerHTML\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{{ \u003C/span>\u003Cspan style=\"color:#B392F0\">__html\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">JSON\u003C/span>\u003Cspan style=\"color:#E1E4E8\">.\u003C/span>\u003Cspan style=\"color:#B392F0\">stringify\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(jsonLd) }}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">      />\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      &#x3C;\u003C/span>\u003Cspan style=\"color:#B392F0\">h1\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>{product.name}\u003C/span>\u003Cspan style=\"color:#F97583\">&#x3C;/\u003C/span>\u003Cspan style=\"color:#E1E4E8\">h1\u003C/span>\u003Cspan style=\"color:#F97583\">>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      {\u003C/span>\u003Cspan style=\"color:#6A737D\">/* ... */\u003C/span>\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    &#x3C;/>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  )\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Point critique : dans Next.js App Router, \u003Ccode>generateMetadata\u003C/code> s'exécute côté serveur et injecte les balises \u003Ccode>&#x3C;head>\u003C/code> dans le HTML initial. Si vous oubliez cette fonction et mettez vos meta dans un \u003Ccode>useEffect\u003C/code> côté client, Googlebot ne les verra pas dans le HTML initial (même s'il exécute du JavaScript, le timing est aléatoire).\u003C/p>\n\u003Ch2>Le plan de redirections : l'étape qui fait ou défait la migration\u003C/h2>\n\u003Cp>Si vos URLs ne changent pas entre les deux frameworks, vous n'avez pas besoin de redirections. C'est le scénario idéal. Mais en pratique, trois situations forcent un changement d'URL :\u003C/p>\n\u003Col>\n\u003Cli>\u003Cstrong>Le trailing slash\u003C/strong> : Next.js par défaut ne met pas de trailing slash, Nuxt 3 si (\u003Ccode>trailingSlash: true\u003C/code> dans la config). Si vous ne harmonisez pas, chaque URL est en doublon.\u003C/li>\n\u003Cli>\u003Cstrong>Les routes dynamiques catch-all\u003C/strong> : le pattern \u003Ccode>[...slug]\u003C/code> peut générer des paths différents selon le framework.\u003C/li>\n\u003Cli>\u003Cstrong>Les URLs générées par des plugins\u003C/strong> : sitemap, pagination, filtres à facettes.\u003C/li>\n\u003C/ol>\n\u003Ch3>Construire la table de redirections\u003C/h3>\n\u003Cp>Partez de votre baseline Screaming Frog. Pour chaque URL indexée, mappez l'URL cible dans le nouveau framework. Le format de travail le plus efficace :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">source,\u003C/span>\u003Cspan style=\"color:#F97583\">target,\u003C/span>\u003Cspan style=\"color:#B392F0\">status\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">/produits/chaussure-running-x500,\u003C/span>\u003Cspan style=\"color:#F97583\">/produits/chaussure-running-x500,\u003C/span>\u003Cspan style=\"color:#B392F0\">200\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">/produits/chaussure-running-x500/,\u003C/span>\u003Cspan style=\"color:#F97583\">/produits/chaussure-running-x500,\u003C/span>\u003Cspan style=\"color:#B392F0\">301\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">/categorie/homme/chaussures,\u003C/span>\u003Cspan style=\"color:#F97583\">/categories/homme/chaussures,\u003C/span>\u003Cspan style=\"color:#B392F0\">301\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">/blog/article-ancien?page=2,\u003C/span>\u003Cspan style=\"color:#F97583\">/blog/article-ancien?page=2,\u003C/span>\u003Cspan style=\"color:#B392F0\">200\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Implémentez les redirections au niveau le plus proche du serveur. Ne les mettez pas dans le code applicatif du framework — un bug de déploiement les ferait sauter.\u003C/p>\n\u003Ch3>Redirections au niveau Nginx\u003C/h3>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># /etc/nginx/conf.d/redirections-migration.conf\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Trailing slash → sans trailing slash (migration Nuxt → Next.js)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">rewrite\u003C/span>\u003Cspan style=\"color:#DBEDFF\"> ^(.+)/$\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> $1 \u003C/span>\u003Cspan style=\"color:#F97583\">permanent\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Changement de segment d'URL\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">location\u003C/span>\u003Cspan style=\"color:#F97583\"> ~\u003C/span>\u003Cspan style=\"color:#DBEDFF\"> ^/categorie/(.*)$ \u003C/span>\u003Cspan style=\"color:#E1E4E8\">{\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 301\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> /categories/$1;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Redirections unitaires depuis un map (performant pour des milliers d'URLs)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">map\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> $\u003C/span>\u003Cspan style=\"color:#FFAB70\">request_uri\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> $redirect_target {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    include /etc/nginx/redirections-migration.map;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">server\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    # ...\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ($redirect_target) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        return\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 301\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> $redirect_target;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Le fichier \u003Ccode>.map\u003C/code> contient une ligne par redirection — gérable même pour 10 000+ URLs. C'est plus performant qu'une cascade de \u003Ccode>location\u003C/code> blocks, et ça ne touche pas au code applicatif.\u003C/p>\n\u003Cp>Si vous êtes sur Vercel ou Netlify, utilisez les fichiers de redirections natifs (\u003Ccode>vercel.json\u003C/code> ou \u003Ccode>_redirects\u003C/code>), mais soyez conscient des limites : Vercel supporte jusqu'à 1 024 redirections dans \u003Ccode>vercel.json\u003C/code> en plan Pro. Au-delà, il faut passer par le middleware Edge — qui ajoute de la latence.\u003C/p>\n\u003Cp>Pour les stratégies de redirection au niveau CDN, la technique d'\u003Ca href=\"/blog/edge-seo-modifier-les-reponses-http-au-niveau-cdn\">Edge SEO via modification des réponses HTTP\u003C/a> offre une flexibilité supplémentaire sans toucher au code applicatif.\u003C/p>\n\u003Ch2>Scénario concret : migration d'un e-commerce de 18 000 pages\u003C/h2>\n\u003Cp>Prenons le cas réaliste d'un e-commerce mode avec cette surface :\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>12 400\u003C/strong> fiches produit (\u003Ccode>/produits/[slug]\u003C/code>)\u003C/li>\n\u003Cli>\u003Cstrong>380\u003C/strong> pages catégorie (\u003Ccode>/categories/[...path]\u003C/code>)\u003C/li>\n\u003Cli>\u003Cstrong>1 200\u003C/strong> pages de blog (\u003Ccode>/blog/[slug]\u003C/code>)\u003C/li>\n\u003Cli>\u003Cstrong>4 020\u003C/strong> pages de filtre à facettes (indexées avec canonical vers la catégorie parente)\u003C/li>\n\u003Cli>\u003Cstrong>Total indexé dans GSC\u003C/strong> : ~14 200 pages (les facettes sont partiellement indexées)\u003C/li>\n\u003Cli>\u003Cstrong>Trafic organique\u003C/strong> : 185 000 sessions/mois\u003C/li>\n\u003Cli>\u003Cstrong>Framework source\u003C/strong> : Next.js 14 (App Router, déployé sur Vercel)\u003C/li>\n\u003Cli>\u003Cstrong>Framework cible\u003C/strong> : Nuxt 3 (déployé sur un VPS avec Nginx + PM2)\u003C/li>\n\u003C/ul>\n\u003Ch3>Chronologie de migration\u003C/h3>\n\u003Cp>\u003Cstrong>Semaine -4 à -2 : Préparation\u003C/strong>\u003C/p>\n\u003Cp>Crawl baseline avec Screaming Frog. Export GSC des 16 derniers mois de données (pour capturer la saisonnalité). Identification de 340 URLs avec des problèmes pré-existants (soft 404, canonicals incorrects). Décision de les corriger pendant la migration — une erreur classique est de traiter ces corrections comme un bonus gratuit, mais chaque changement simultané complique le diagnostic post-migration.\u003C/p>\n\u003Cp>\u003Cstrong>Semaine -2 à J-0 : Développement\u003C/strong>\u003C/p>\n\u003Cul>\n\u003Cli>Environnement staging Nuxt 3 avec toutes les routes mappées\u003C/li>\n\u003Cli>Crawl Screaming Frog du staging : comparaison automatisée des title, H1, canonical, status codes avec la baseline\u003C/li>\n\u003Cli>Test de rendu SSR sur 200 URLs échantillonnées via \u003Ccode>curl\u003C/code> + script bash\u003C/li>\n\u003Cli>Validation des données structurées JSON-LD (Product, BreadcrumbList, Article) avec le \u003Ca href=\"https://search.google.com/test/rich-results\">Rich Results Test\u003C/a> de Google\u003C/li>\n\u003Cli>Mise en place du fichier de redirections Nginx (287 redirections identifiées)\u003C/li>\n\u003C/ul>\n\u003Cp>\u003Cstrong>J-0 : Bascule\u003C/strong>\u003C/p>\n\u003Cp>Déploiement en soirée (20h, heure de faible trafic). Vérification immédiate :\u003C/p>\n\u003Col>\n\u003Cli>Curl sur 50 URLs critiques (top trafic) : status 200, HTML contient H1 et meta\u003C/li>\n\u003Cli>Vérification des redirections : les 287 retournent bien 301\u003C/li>\n\u003Cli>Soumission du sitemap mis à jour dans Google Search Console\u003C/li>\n\u003Cli>Demande d'indexation manuelle sur les 20 pages les plus importantes\u003C/li>\n\u003C/ol>\n\u003Cp>\u003Cstrong>J+1 à J+7 : Monitoring intensif\u003C/strong>\u003C/p>\n\u003Cul>\n\u003Cli>Crawl quotidien d'un échantillon de 500 URLs pour détecter les régressions SSR\u003C/li>\n\u003Cli>Surveillance des logs serveur : pic de 404 = redirections manquantes\u003C/li>\n\u003Cli>Vérification dans GSC du rapport \"Pages\" : les nouvelles URLs commencent à apparaître dans \"Découverte\"\u003C/li>\n\u003C/ul>\n\u003Cp>\u003Cstrong>J+7 à J+30 : Stabilisation\u003C/strong>\u003C/p>\n\u003Cul>\n\u003Cli>Le trafic organique chute de 12% la première semaine — c'est dans la fourchette attendue pour une migration propre\u003C/li>\n\u003Cli>Récupération à 95% du trafic initial à J+21\u003C/li>\n\u003Cli>Retour au niveau pré-migration à J+28\u003C/li>\n\u003Cli>Identification de 43 URLs orphelines (présentes dans la baseline mais absentes du nouveau sitemap) — corrigées à J+10\u003C/li>\n\u003C/ul>\n\u003Cp>Un outil de monitoring comme Seogard détecte automatiquement les régressions de type \"meta description disparue\" ou \"SSR cassé retournant un body vide\" dès le déploiement, sans attendre que le trafic chute pour s'en rendre compte.\u003C/p>\n\u003Ch3>Ce qui aurait mal tourné sans préparation\u003C/h3>\n\u003Cp>Sans la baseline Screaming Frog, les 43 URLs orphelines auraient été découvertes uniquement via la chute de trafic — entre 2 et 4 semaines plus tard. Sans le test SSR systématique, les pages catégorie servies en CSR pur (un bug dans le composant \u003Ccode>AsyncData\u003C/code>) auraient perdu leur indexation pendant des semaines. Sans les redirections pour le trailing slash, les 12 400 fiches produit auraient eu un doublon indexable.\u003C/p>\n\u003Ch2>Checklist des éléments SEO à transférer entre frameworks\u003C/h2>\n\u003Cp>Au-delà des URLs et du SSR, une migration de framework touche des dizaines d'éléments SEO rarement listés dans les guides génériques.\u003C/p>\n\u003Ch3>Balises meta et canonicals\u003C/h3>\n\u003Cp>Chaque template de page dans le nouveau framework doit reproduire exactement les mêmes balises que l'ancien. Pas \"à peu près les mêmes\" — exactement les mêmes, caractère par caractère pour les canonicals.\u003C/p>\n\u003Cp>Vérifiez en particulier :\u003C/p>\n\u003Cul>\n\u003Cli>Les canonicals auto-référentiels (chaque page pointe vers elle-même)\u003C/li>\n\u003Cli>Les canonicals des pages à facettes (pointent vers la catégorie parente)\u003C/li>\n\u003Cli>Les meta robots sur les pages de compte, panier, checkout\u003C/li>\n\u003Cli>Les balises hreflang si le site est multilingue\u003C/li>\n\u003C/ul>\n\u003Cp>Sur le sujet du contenu dupliqué généré par les facettes, la gestion des \u003Ca href=\"/blog/contenu-duplique-causes-techniques-et-solutions\">canonicals et du contenu dupliqué\u003C/a> est un prérequis à maîtriser avant la migration.\u003C/p>\n\u003Ch3>Données structurées JSON-LD\u003C/h3>\n\u003Cp>Les données structurées sont souvent générées dynamiquement dans le composant page. Lors du changement de framework, le format peut subtilement changer (ordre des propriétés, présence/absence de champs optionnels). Ce n'est pas un problème SEO en soi, mais c'est l'occasion de valider que tout est conforme.\u003C/p>\n\u003Cp>Exportez les données structurées de chaque template avec un script :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">// scripts/validate-jsonld.mjs\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">// Exécution : node scripts/validate-jsonld.mjs urls.txt\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> { readFileSync } \u003C/span>\u003Cspan style=\"color:#F97583\">from\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'fs'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> urls\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#B392F0\"> readFileSync\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(process.argv[\u003C/span>\u003Cspan style=\"color:#79B8FF\">2\u003C/span>\u003Cspan style=\"color:#E1E4E8\">], \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'utf-8'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  .\u003C/span>\u003Cspan style=\"color:#B392F0\">split\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#79B8FF\">\\n\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  .\u003C/span>\u003Cspan style=\"color:#B392F0\">filter\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(Boolean)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (\u003C/span>\u003Cspan style=\"color:#F97583\">const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> url\u003C/span>\u003Cspan style=\"color:#F97583\"> of\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> urls) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  try\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> res\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#F97583\"> await\u003C/span>\u003Cspan style=\"color:#B392F0\"> fetch\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(url, {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      headers: { \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'User-Agent'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'SEO-Migration-Validator/1.0'\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    })\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> html\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#F97583\"> await\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> res.\u003C/span>\u003Cspan style=\"color:#B392F0\">text\u003C/span>\u003Cspan style=\"color:#E1E4E8\">()\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    // Extraction de tous les blocs JSON-LD\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> jsonLdBlocks\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> html.\u003C/span>\u003Cspan style=\"color:#B392F0\">match\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">      /\u003C/span>\u003Cspan style=\"color:#DBEDFF\">&#x3C;script type=\"application\u003C/span>\u003Cspan style=\"color:#85E89D;font-weight:bold\">\\/\u003C/span>\u003Cspan style=\"color:#DBEDFF\">ld\u003C/span>\u003Cspan style=\"color:#85E89D;font-weight:bold\">\\+\u003C/span>\u003Cspan style=\"color:#DBEDFF\">json\">(\u003C/span>\u003Cspan style=\"color:#79B8FF\">[\\s\\S]\u003C/span>\u003Cspan style=\"color:#F97583\">*?\u003C/span>\u003Cspan style=\"color:#DBEDFF\">)&#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D;font-weight:bold\">\\/\u003C/span>\u003Cspan style=\"color:#DBEDFF\">script>\u003C/span>\u003Cspan style=\"color:#9ECBFF\">/\u003C/span>\u003Cspan style=\"color:#F97583\">g\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    )\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (\u003C/span>\u003Cspan style=\"color:#F97583\">!\u003C/span>\u003Cspan style=\"color:#E1E4E8\">jsonLdBlocks \u003C/span>\u003Cspan style=\"color:#F97583\">||\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> jsonLdBlocks.\u003C/span>\u003Cspan style=\"color:#79B8FF\">length\u003C/span>\u003Cspan style=\"color:#F97583\"> ===\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 0\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      console.\u003C/span>\u003Cspan style=\"color:#B392F0\">error\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">`[MISSING] ${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">url\u003C/span>\u003Cspan style=\"color:#9ECBFF\">} — Aucun JSON-LD trouvé`\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">      continue\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (\u003C/span>\u003Cspan style=\"color:#F97583\">const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> block\u003C/span>\u003Cspan style=\"color:#F97583\"> of\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> jsonLdBlocks) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">      const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> json\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> block\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        .\u003C/span>\u003Cspan style=\"color:#B392F0\">replace\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'&#x3C;script type=\"application/ld+json\">'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">''\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        .\u003C/span>\u003Cspan style=\"color:#B392F0\">replace\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'&#x3C;/script>'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">''\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">      try\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> parsed\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#79B8FF\"> JSON\u003C/span>\u003Cspan style=\"color:#E1E4E8\">.\u003C/span>\u003Cspan style=\"color:#B392F0\">parse\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(json)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> type\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> parsed[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'@type'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">||\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> parsed?.[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'@graph'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]?.[\u003C/span>\u003Cspan style=\"color:#79B8FF\">0\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]?.[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'@type'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">||\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'unknown'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        console.\u003C/span>\u003Cspan style=\"color:#B392F0\">log\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">`[OK] ${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">url\u003C/span>\u003Cspan style=\"color:#9ECBFF\">} — @type: ${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">type\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}`\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      } \u003C/span>\u003Cspan style=\"color:#F97583\">catch\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (e) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        console.\u003C/span>\u003Cspan style=\"color:#B392F0\">error\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">`[INVALID JSON] ${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">url\u003C/span>\u003Cspan style=\"color:#9ECBFF\">} — ${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">e\u003C/span>\u003Cspan style=\"color:#9ECBFF\">.\u003C/span>\u003Cspan style=\"color:#E1E4E8\">message\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}`\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  } \u003C/span>\u003Cspan style=\"color:#F97583\">catch\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (e) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    console.\u003C/span>\u003Cspan style=\"color:#B392F0\">error\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">`[FETCH ERROR] ${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">url\u003C/span>\u003Cspan style=\"color:#9ECBFF\">} — ${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">e\u003C/span>\u003Cspan style=\"color:#9ECBFF\">.\u003C/span>\u003Cspan style=\"color:#E1E4E8\">message\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}`\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Exécutez ce script sur votre baseline (ancien site) et sur le staging (nouveau site), puis diff les résultats.\u003C/p>\n\u003Ch3>Sitemap XML\u003C/h3>\n\u003Cp>Next.js et Nuxt gèrent les sitemaps différemment. Next.js App Router permet de créer un \u003Ccode>app/sitemap.ts\u003C/code> qui génère dynamiquement le sitemap. Nuxt 3 utilise le module \u003Ccode>@nuxtjs/sitemap\u003C/code>.\u003C/p>\n\u003Cp>Le piège : le nouveau sitemap doit être soumis \u003Cstrong>immédiatement\u003C/strong> après la bascule. Si Google crawle le site entre le déploiement et la soumission du sitemap, il peut découvrir des 404 avant de trouver les redirections.\u003C/p>\n\u003Ch3>Fichier robots.txt\u003C/h3>\n\u003Cp>Vérifiez que le \u003Ccode>robots.txt\u003C/code> du nouveau framework n'est pas celui par défaut (qui peut contenir un \u003Ccode>Disallow: /\u003C/code> en mode staging). C'est un classique qui bloque le crawl de l'intégralité du site pendant des heures ou des jours.\u003C/p>\n\u003Ch3>Performance et Core Web Vitals\u003C/h3>\n\u003Cp>Le changement de framework impacte les métriques de performance. Nuxt 3 avec Nitro a un profil de performance différent de Next.js avec le runtime Edge de Vercel. Mesurez le TTFB, le LCP et le CLS avant et après migration sur vos templates principaux.\u003C/p>\n\u003Cp>Un changement de framework ne devrait pas dégrader les Core Web Vitals si le code est correctement implémenté. Mais le \"correctement\" est un piège : une hydratation client trop agressive dans Nuxt, ou un bundle trop lourd dans Next.js, peut ajouter 200-500ms de LCP.\u003C/p>\n\u003Ch2>Monitoring post-migration : les 30 jours critiques\u003C/h2>\n\u003Cp>Les KPIs à surveiller quotidiennement pendant le premier mois :\u003C/p>\n\u003Ch3>Dans Google Search Console\u003C/h3>\n\u003Cul>\n\u003Cli>\u003Cstrong>Couverture d'indexation\u003C/strong> : le nombre de pages \"Valides\" ne doit pas chuter. Si des pages passent en \"Exclues\" avec la raison \"Page avec redirection\", c'est normal temporairement.\u003C/li>\n\u003Cli>\u003Cstrong>Performance\u003C/strong> : suivez les clics et impressions par type de page (regex dans le filtre de pages). Une chute de 10-15% la première semaine est normale. Au-delà de 20%, investiguez.\u003C/li>\n\u003Cli>\u003Cstrong>Rapport d'exploration\u003C/strong> : vérifiez que le crawl rate ne s'effondre pas. Un TTFB dégradé sur le nouveau framework peut réduire le crawl budget alloué par Google.\u003C/li>\n\u003C/ul>\n\u003Cp>Pour tirer le maximum de Search Console pendant cette phase critique, les \u003Ca href=\"/blog/google-search-console-les-rapports-que-vous-ignorez\">rapports souvent ignorés de GSC\u003C/a> contiennent des signaux précieux pour détecter les problèmes tôt.\u003C/p>\n\u003Ch3>Dans vos logs serveur\u003C/h3>\n\u003Cp>Parsez les logs pour identifier :\u003C/p>\n\u003Cul>\n\u003Cli>Les URLs avec un volume anormal de 404 (redirections manquantes)\u003C/li>\n\u003Cli>Les User-Agents Googlebot qui reçoivent des réponses différentes des visiteurs normaux (problème de caching ou de rendering conditionnel)\u003C/li>\n\u003Cli>Les temps de réponse supérieurs à 800ms (seuil critique pour le crawl budget)\u003C/li>\n\u003C/ul>\n\u003Ch3>Avec Screaming Frog en mode monitoring\u003C/h3>\n\u003Cp>Configurez un crawl hebdomadaire pendant les 30 premiers jours, puis mensuel. Comparez systématiquement avec la baseline. Tout écart doit être investigué.\u003C/p>\n\u003Cp>Pour suivre l'impact global de la migration avec les bons indicateurs, les \u003Ca href=\"/blog/mesurer-l-impact-seo-technique-quels-kpis-suivre\">KPIs de suivi SEO technique\u003C/a> fournissent un cadre structuré.\u003C/p>\n\u003Ch2>Les edge cases que personne ne mentionne\u003C/h2>\n\u003Ch3>Les pages en cache de Google pendant la transition\u003C/h3>\n\u003Cp>Google peut continuer à afficher l'ancienne version de vos pages dans son cache pendant 2 à 4 semaines après la migration. Ce n'est pas un problème SEO, mais ça peut perturber votre monitoring si vous comparez le cache Google avec votre nouveau site.\u003C/p>\n\u003Ch3>Les backlinks pointant vers des URLs avec query strings\u003C/h3>\n\u003Cp>Vos backlinks les plus précieux peuvent pointer vers des URLs avec des paramètres (\u003Ccode>?utm_source=...\u003C/code>, \u003Ccode>?ref=...\u003C/code>). Vérifiez que votre nouveau framework gère ces paramètres correctement et ne retourne pas de 404 dessus. Next.js et Nuxt ont des comportements différents sur les query strings non déclarées dans le routing.\u003C/p>\n\u003Ch3>L'impact sur les bots IA\u003C/h3>\n\u003Cp>Les bots de type GPTBot ou ClaudeBot crawlent de plus en plus agressivement. Un changement de framework peut modifier votre \u003Ccode>robots.txt\u003C/code> et accidentellement bloquer ou autoriser ces bots. L'\u003Ca href=\"/blog/chatgpt-now-crawls-3-6x-more-than-googlebot-what-24m-requests-reveal\">augmentation du crawl par les bots IA\u003C/a> rend ce point d'autant plus critique — un pic de crawl IA pendant la migration peut surcharger votre serveur et dégrader les temps de réponse pour Googlebot.\u003C/p>\n\u003Ch3>Les tests A/B SEO pendant une migration\u003C/h3>\n\u003Cp>Ne lancez pas de test A/B SEO pendant une migration de framework. Vous ne pourrez pas isoler les variables. Attendez au minimum 6 semaines post-migration avec un trafic stabilisé. L'article sur \u003Ca href=\"/blog/a-b-testing-seo-tester-sans-penaliser-le-referencement\">l'A/B testing SEO sans pénaliser le référencement\u003C/a> détaille les conditions nécessaires pour des tests fiables.\u003C/p>\n\u003Cp>Si votre pipeline CI/CD intègre des checks SEO automatisés, adaptez-les au nouveau framework dès le premier commit. Les \u003Ca href=\"/blog/automatiser-les-checks-seo-dans-le-ci-cd\">checks SEO dans le CI/CD\u003C/a> sont votre filet de sécurité : ils détectent les meta manquantes, les canonicals cassés ou le SSR désactivé avant que le code n'atteigne la production.\u003C/p>\n\u003Chr>\n\u003Cp>Une migration de framework n'est pas un projet SEO — c'est un projet d'infrastructure qui a des conséquences SEO massives si mal exécuté. La baseline, les redirections, le SSR et le monitoring post-migration sont les quatre piliers qui séparent une migration transparente d'une catastrophe organique. Outillez chaque étape : Screaming Frog pour la baseline, \u003Ccode>curl\u003C/code> et des scripts custom pour le SSR, Nginx pour les redirections, et un monitoring continu avec Seogard pour détecter instantanément les régressions que vos tests manuels rateront forcément.\u003C/p>\n\u003Cpre>\u003Ccode>\u003C/code>\u003C/pre>",null,14,[18,19,20,21,22],"migration","nextjs","nuxt","framework","seo","Migration Next.js vers Nuxt (ou l'inverse) sans perte SEO","Thu Apr 09 2026 23:56:54 GMT+0000 (Coordinated Universal Time)",[26,37],{"_id":27,"slug":28,"__v":6,"author":7,"canonical":29,"category":10,"createdAt":30,"date":12,"description":31,"image":15,"imageAlt":15,"readingTime":16,"tags":32,"title":35,"updatedAt":36},"69d7e9c3aa6b273b0c95cc57","migration-http-vers-https-checklist-seo-complete","https://seogard.io/blog/migration-http-vers-https-checklist-seo-complete","2026-04-09T18:02:43.120Z","Checklist technique pour migrer de HTTP à HTTPS sans perdre de trafic organique. Redirections, HSTS, Search Console, mixed content.",[33,18,34,22],"https","redirections","Migration HTTP vers HTTPS : checklist SEO complète","Thu Apr 09 2026 18:02:43 GMT+0000 (Coordinated Universal Time)",{"_id":38,"slug":39,"__v":6,"author":7,"canonical":40,"category":10,"createdAt":41,"date":12,"description":42,"image":15,"imageAlt":15,"readingTime":16,"tags":43,"title":46,"updatedAt":47},"69d8372aaa6b273b0cd3ab6d","refonte-de-site-les-20-verifications-seo-indispensables","https://seogard.io/blog/refonte-de-site-les-20-verifications-seo-indispensables","2026-04-09T23:32:58.408Z","Checklist technique complète pour réussir une refonte sans perdre de trafic organique. 20 points de contrôle concrets avec code et config.",[44,18,45,22],"refonte","checklist","Refonte de site : 20 vérifications SEO indispensables","Thu Apr 09 2026 23:32:58 GMT+0000 (Coordinated Universal Time)"]