Migration Next.js vers Nuxt (ou l'inverse) sans perte SEO

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.

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.

Cartographier l'existant avant de toucher une ligne de code

La 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.

Inventaire exhaustif des URLs indexées

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.

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 :

  • URL, status code, title, meta description, canonical, hreflang
  • En-têtes H1, structured data (JSON-LD)
  • Temps de réponse serveur (TTFB)
  • Mode de rendu (vérifiez si la page est SSR ou CSR via l'extraction custom du HTML brut)
# Export Screaming Frog en CLI pour intégration CI
screamingfrog --crawl https://www.maboutique.fr \
  --headless \
  --output-folder ./crawl-baseline \
  --export-tabs "Internal:All,Response Codes:All" \
  --save-crawl ./crawl-baseline/baseline.seospider

Cet 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).

Documenter la structure d'URL existante

Next.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 :

# Next.js App Router
app/produits/[slug]/page.tsx        → /produits/chaussure-running-x500
app/categories/[...path]/page.tsx   → /categories/homme/chaussures/running

# Nuxt 3
pages/produits/[slug].vue           → /produits/chaussure-running-x500
pages/categories/[...path].vue      → /categories/homme/chaussures/running

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 (/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.

Si 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.

Pour un audit complet des vérifications à prévoir, appuyez-vous sur la checklist des 20 vérifications SEO de refonte qui couvre les fondamentaux au-delà du framework.

Gérer le SSR : le vrai risque invisible

Le 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.

Vérifier le rendu serveur page par page

Ne 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.

Voici comment vérifier que le HTML servi contient bien le contenu critique :

# Vérifier le HTML brut reçu par un crawler (sans exécution JS)
curl -s -A "Googlebot" https://staging.maboutique.fr/produits/chaussure-running-x500 \
  | grep -c "<h1>"

# Doit retourner 1. Si 0, le H1 est rendu côté client uniquement.

# Vérification plus complète avec extraction du title et de la meta description
curl -s https://staging.maboutique.fr/produits/chaussure-running-x500 \
  | grep -oP '(?<=<title>).*?(?=</title>)'

curl -s https://staging.maboutique.fr/produits/chaussure-running-x500 \
  | grep -oP '(?<=name="description" content=").*?(?=")'

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.

Les Chrome DevTools offrent des méthodes avancées pour inspecter le rendu côté serveur vs côté client, notamment via la désactivation de JavaScript dans le panneau Network.

Configuration SSR dans Nuxt 3 avec routeRules

Si 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 :

// nuxt.config.ts
export default defineNuxtConfig({
  ssr: true, // SSR activé globalement

  routeRules: {
    // Pages produit : SSR + cache CDN 1h (équivalent ISR Next.js)
    '/produits/**': {
      swr: 3600,
      headers: { 'Cache-Control': 's-maxage=3600, stale-while-revalidate=86400' }
    },

    // Pages catégorie : SSR pur, pas de cache (contenu dynamique)
    '/categories/**': {
      swr: false
    },

    // Pages statiques (CGV, mentions légales) : pré-rendues au build
    '/pages/**': {
      prerender: true
    },

    // Dashboard utilisateur : pas de SSR, pas d'indexation
    '/compte/**': {
      ssr: false,
      headers: { 'X-Robots-Tag': 'noindex' }
    }
  },

  // Reproduire les headers SEO critiques
  nitro: {
    routeRules: {
      '/**': {
        headers: {
          'X-Frame-Options': 'SAMEORIGIN',
          'X-Content-Type-Options': 'nosniff'
        }
      }
    }
  }
})

Le 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.

Configuration équivalente dans Next.js (migration inverse)

Si vous allez de Nuxt vers Next.js avec App Router :

// app/produits/[slug]/page.tsx
import { Metadata } from 'next'

// Équivalent du swr: 3600 de Nuxt
export const revalidate = 3600

export async function generateMetadata(
  { params }: { params: { slug: string } }
): Promise<Metadata> {
  const product = await getProduct(params.slug)

  return {
    title: product.seoTitle,
    description: product.seoDescription,
    alternates: {
      canonical: `https://www.maboutique.fr/produits/${params.slug}`
    },
    openGraph: {
      title: product.seoTitle,
      description: product.seoDescription,
      images: [product.ogImage]
    }
  }
}

export default async function ProductPage(
  { params }: { params: { slug: string } }
) {
  const product = await getProduct(params.slug)

  // JSON-LD injecté dans le HTML serveur
  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Product',
    name: product.name,
    description: product.description,
    offers: {
      '@type': 'Offer',
      price: product.price,
      priceCurrency: 'EUR',
      availability: product.inStock
        ? 'https://schema.org/InStock'
        : 'https://schema.org/OutOfStock'
    }
  }

  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      <h1>{product.name}</h1>
      {/* ... */}
    </>
  )
}

Point critique : dans Next.js App Router, generateMetadata s'exécute côté serveur et injecte les balises <head> 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).

Le plan de redirections : l'étape qui fait ou défait la migration

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 :

  1. 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.
  2. Les routes dynamiques catch-all : le pattern [...slug] peut générer des paths différents selon le framework.
  3. Les URLs générées par des plugins : sitemap, pagination, filtres à facettes.

Construire la table de redirections

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 :

source,target,status
/produits/chaussure-running-x500,/produits/chaussure-running-x500,200
/produits/chaussure-running-x500/,/produits/chaussure-running-x500,301
/categorie/homme/chaussures,/categories/homme/chaussures,301
/blog/article-ancien?page=2,/blog/article-ancien?page=2,200

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.

Redirections au niveau Nginx

# /etc/nginx/conf.d/redirections-migration.conf

# Trailing slash → sans trailing slash (migration Nuxt → Next.js)
rewrite ^(.+)/$ $1 permanent;

# Changement de segment d'URL
location ~ ^/categorie/(.*)$ {
    return 301 /categories/$1;
}

# Redirections unitaires depuis un map (performant pour des milliers d'URLs)
map $request_uri $redirect_target {
    include /etc/nginx/redirections-migration.map;
}

server {
    # ...
    if ($redirect_target) {
        return 301 $redirect_target;
    }
}

Le 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.

Si 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.

Pour les stratégies de redirection au niveau CDN, la technique d'Edge SEO via modification des réponses HTTP offre une flexibilité supplémentaire sans toucher au code applicatif.

Scénario concret : migration d'un e-commerce de 18 000 pages

Prenons le cas réaliste d'un e-commerce mode avec cette surface :

  • 12 400 fiches produit (/produits/[slug])
  • 380 pages catégorie (/categories/[...path])
  • 1 200 pages de blog (/blog/[slug])
  • 4 020 pages de filtre à facettes (indexées avec canonical vers la catégorie parente)
  • Total indexé dans GSC : ~14 200 pages (les facettes sont partiellement indexées)
  • Trafic organique : 185 000 sessions/mois
  • Framework source : Next.js 14 (App Router, déployé sur Vercel)
  • Framework cible : Nuxt 3 (déployé sur un VPS avec Nginx + PM2)

Chronologie de migration

Semaine -4 à -2 : Préparation

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.

Semaine -2 à J-0 : Développement

  • Environnement staging Nuxt 3 avec toutes les routes mappées
  • Crawl Screaming Frog du staging : comparaison automatisée des title, H1, canonical, status codes avec la baseline
  • Test de rendu SSR sur 200 URLs échantillonnées via curl + script bash
  • Validation des données structurées JSON-LD (Product, BreadcrumbList, Article) avec le Rich Results Test de Google
  • Mise en place du fichier de redirections Nginx (287 redirections identifiées)

J-0 : Bascule

Déploiement en soirée (20h, heure de faible trafic). Vérification immédiate :

  1. Curl sur 50 URLs critiques (top trafic) : status 200, HTML contient H1 et meta
  2. Vérification des redirections : les 287 retournent bien 301
  3. Soumission du sitemap mis à jour dans Google Search Console
  4. Demande d'indexation manuelle sur les 20 pages les plus importantes

J+1 à J+7 : Monitoring intensif

  • Crawl quotidien d'un échantillon de 500 URLs pour détecter les régressions SSR
  • Surveillance des logs serveur : pic de 404 = redirections manquantes
  • Vérification dans GSC du rapport "Pages" : les nouvelles URLs commencent à apparaître dans "Découverte"

J+7 à J+30 : Stabilisation

  • Le trafic organique chute de 12% la première semaine — c'est dans la fourchette attendue pour une migration propre
  • Récupération à 95% du trafic initial à J+21
  • Retour au niveau pré-migration à J+28
  • Identification de 43 URLs orphelines (présentes dans la baseline mais absentes du nouveau sitemap) — corrigées à J+10

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.

Ce qui aurait mal tourné sans préparation

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 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.

Checklist des éléments SEO à transférer entre frameworks

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.

Balises meta et canonicals

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.

Vérifiez en particulier :

  • Les canonicals auto-référentiels (chaque page pointe vers elle-même)
  • Les canonicals des pages à facettes (pointent vers la catégorie parente)
  • Les meta robots sur les pages de compte, panier, checkout
  • Les balises hreflang si le site est multilingue

Sur le sujet du contenu dupliqué généré par les facettes, la gestion des canonicals et du contenu dupliqué est un prérequis à maîtriser avant la migration.

Données structurées JSON-LD

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.

Exportez les données structurées de chaque template avec un script :

// scripts/validate-jsonld.mjs
// Exécution : node scripts/validate-jsonld.mjs urls.txt

import { readFileSync } from 'fs'

const urls = readFileSync(process.argv[2], 'utf-8')
  .split('\n')
  .filter(Boolean)

for (const url of urls) {
  try {
    const res = await fetch(url, {
      headers: { 'User-Agent': 'SEO-Migration-Validator/1.0' }
    })
    const html = await res.text()

    // Extraction de tous les blocs JSON-LD
    const jsonLdBlocks = html.match(
      /<script type="application\/ld\+json">([\s\S]*?)<\/script>/g
    )

    if (!jsonLdBlocks || jsonLdBlocks.length === 0) {
      console.error(`[MISSING] ${url} — Aucun JSON-LD trouvé`)
      continue
    }

    for (const block of jsonLdBlocks) {
      const json = block
        .replace('<script type="application/ld+json">', '')
        .replace('</script>', '')

      try {
        const parsed = JSON.parse(json)
        const type = parsed['@type'] || parsed?.['@graph']?.[0]?.['@type'] || 'unknown'
        console.log(`[OK] ${url} — @type: ${type}`)
      } catch (e) {
        console.error(`[INVALID JSON] ${url} — ${e.message}`)
      }
    }
  } catch (e) {
    console.error(`[FETCH ERROR] ${url} — ${e.message}`)
  }
}

Exécutez ce script sur votre baseline (ancien site) et sur le staging (nouveau site), puis diff les résultats.

Sitemap XML

Next.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.

Le 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.

Fichier robots.txt

Vé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.

Performance et Core Web Vitals

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.

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.

Monitoring post-migration : les 30 jours critiques

Les KPIs à surveiller quotidiennement pendant le premier mois :

Dans Google Search Console

  • 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.
  • 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.
  • 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.

Pour tirer le maximum de Search Console pendant cette phase critique, les rapports souvent ignorés de GSC contiennent des signaux précieux pour détecter les problèmes tôt.

Dans vos logs serveur

Parsez les logs pour identifier :

  • Les URLs avec un volume anormal de 404 (redirections manquantes)
  • Les User-Agents Googlebot qui reçoivent des réponses différentes des visiteurs normaux (problème de caching ou de rendering conditionnel)
  • Les temps de réponse supérieurs à 800ms (seuil critique pour le crawl budget)

Avec Screaming Frog en mode monitoring

Configurez un crawl hebdomadaire pendant les 30 premiers jours, puis mensuel. Comparez systématiquement avec la baseline. Tout écart doit être investigué.

Pour suivre l'impact global de la migration avec les bons indicateurs, les KPIs de suivi SEO technique fournissent un cadre structuré.

Les edge cases que personne ne mentionne

Les pages en cache de Google pendant la transition

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.

Les backlinks pointant vers des URLs avec query strings

Vos 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.

L'impact sur les bots IA

Les 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 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.

Les tests A/B SEO pendant une migration

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 l'A/B testing SEO sans pénaliser le référencement détaille les conditions nécessaires pour des tests fiables.

Si 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 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.


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, 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.

Articles connexes

Migration9 avril 2026

Migration HTTP vers HTTPS : checklist SEO complète

Checklist technique pour migrer de HTTP à HTTPS sans perdre de trafic organique. Redirections, HSTS, Search Console, mixed content.

Migration9 avril 2026

Refonte de site : 20 vérifications SEO indispensables

Checklist technique complète pour réussir une refonte sans perdre de trafic organique. 20 points de contrôle concrets avec code et config.