Pagination SEO : rel=prev/next est mort, quelles alternatives

En mars 2019, Google a confirmé via le compte Twitter de John Mueller qu'il n'utilisait plus rel="prev" et rel="next" comme signal d'indexation — et ce depuis plusieurs années déjà. Pour les sites à fort volume de pages — catalogues e-commerce, annuaires, sites médias — cette annonce a laissé un vide architectural réel. La question n'est plus de savoir comment implémenter rel=prev/next correctement, mais comment structurer la pagination pour que Googlebot crawle et indexe efficacement des milliers de pages sans gaspiller de crawl budget.

Ce que Google faisait réellement avec rel=prev/next

Avant de chercher des alternatives, il faut comprendre ce que rel=prev/next apportait concrètement. Google utilisait ces annotations pour deux choses : consolider les signaux de ranking des pages paginées (comme un canonical souple vers la séquence entière), et guider Googlebot dans l'ordre de découverte des URLs paginées.

En pratique, Googlebot consolidait le PageRank et les signaux d'ancrage de /category/shoes?page=1 à ?page=50 en un ensemble cohérent. La page 1 captait l'essentiel du ranking, les pages profondes servaient de réservoir de contenu crawlable.

Depuis l'abandon, chaque page paginée est traitée comme une page indépendante. Google la crawle, l'évalue et décide de l'indexer — ou pas — sur ses propres mérites. Cela a des conséquences directes :

  • Les pages paginées profondes (?page=30, ?page=50) sont souvent jugées thin content car elles ne contiennent que des listings répétitifs avec un template identique.
  • Le crawl budget est consommé par des dizaines de pages paginées à faible valeur ajoutée.
  • Sans consolidation automatique, les signaux de liens se diluent sur l'ensemble des pages.

La documentation officielle de Google sur la pagination et la recherche confirme que la stratégie recommandée repose désormais sur l'architecture du site elle-même, pas sur des balises de relation.

Stratégie 1 : le "view all" sélectif et la page de catégorie enrichie

La première alternative — et la plus robuste — consiste à concentrer la valeur SEO sur la page de catégorie principale en y exposant un maximum de contenu crawlable, et à réduire la dépendance aux pages paginées.

Le principe

Plutôt que de paginer en 50 pages de 24 produits, vous exposez les 200-300 premiers éléments sur la page principale avec un lazy loading progressif côté navigateur, tout en gardant l'intégralité du contenu dans le HTML initial ou via SSR pour Googlebot.

Quand ça fonctionne

Cette approche est viable quand votre catégorie contient moins de ~500 items et que le poids HTML reste sous contrôle (< 3 Mo de HTML parsé). Au-delà, le temps de parsing DOM devient un problème de performance qui dégrade vos Core Web Vitals, et notamment le LCP.

Implémentation concrète

Voici un pattern Next.js App Router qui sert un HTML complet côté serveur pour les bots, tout en paginant côté client pour les utilisateurs :

// app/category/[slug]/page.tsx
import { getProducts, getCategoryMeta } from '@/lib/api';
import { ProductGrid } from '@/components/ProductGrid';
import { LoadMoreButton } from '@/components/LoadMoreButton';

interface Props {
  params: { slug: string };
  searchParams: { page?: string };
}

export default async function CategoryPage({ params, searchParams }: Props) {
  const page = parseInt(searchParams.page || '1', 10);
  const pageSize = 48;
  
  // SSR : on charge les N premières pages pour Googlebot
  // Heuristique : user-agent n'est pas fiable, on charge
  // systématiquement les 3 premières pages en SSR
  const ssrPages = 3;
  const totalSSRProducts = ssrPages * pageSize; // 144 produits
  
  const { products, totalCount } = await getProducts({
    category: params.slug,
    limit: totalSSRProducts,
    offset: 0,
  });

  const category = await getCategoryMeta(params.slug);
  const totalPages = Math.ceil(totalCount / pageSize);

  return (
    <main>
      <h1>{category.name}</h1>
      <p>{category.description}</p> {/* Contenu unique, pas du boilerplate */}
      
      <ProductGrid products={products} />
      
      {/* Navigation HTML pour les bots — liens directs vers les pages */}
      <nav aria-label="Pagination" className="pagination-nav">
        {Array.from({ length: Math.min(totalPages, 10) }, (_, i) => (
          <a 
            key={i + 1} 
            href={`/category/${params.slug}?page=${i + 1}`}
          >
            {i + 1}
          </a>
        ))}
        {totalPages > 10 && (
          <a href={`/category/${params.slug}?page=${totalPages}`}>
            {totalPages}
          </a>
        )}
      </nav>
      
      {/* Client-side : bouton "Charger plus" pour les humains */}
      <LoadMoreButton 
        categorySlug={params.slug} 
        initialOffset={totalSSRProducts}
        pageSize={pageSize}
        totalCount={totalCount}
      />
    </main>
  );
}

Le point clé : la navigation HTML <nav> avec des liens <a href> réels est toujours présente dans le DOM initial. Googlebot les suit. Les utilisateurs interagissent avec le bouton "Charger plus" qui utilise fetch() côté client. Les deux parcours coexistent.

Le trade-off

Cette approche augmente le poids de la page initiale et le temps serveur (3 requêtes DB au lieu d'une). Surveillez votre TTFB — si la page de catégorie passe au-dessus de 800 ms, implémentez un cache serveur ou réduisez le nombre de pages SSR.

Stratégie 2 : architecture de pagination crawl-friendly sans rel=prev/next

Pour les sites à très fort volume — un e-commerce de 15 000 produits répartis en 200 catégories, un site d'annonces immobilières avec 80 000 listings — la page "view all" n'est pas viable. Vous devez paginer. La question est : comment structurer les liens entre pages paginées pour maximiser l'efficacité du crawl.

Le problème de la pagination linéaire

Une pagination classique 1 2 3 ... 50 force Googlebot à crawler séquentiellement. Pour atteindre la page 50, il doit traverser au minimum 2-3 niveaux de liens (page 1 → page 10 → page 50). Chaque niveau supplémentaire réduit la probabilité que Googlebot atteigne les pages profondes lors d'une session de crawl donnée.

Screaming Frog vous montre ce phénomène clairement : lancez un crawl de votre site avec une profondeur max de 10, et regardez combien de pages paginées profondes sont atteintes. Sur un catalogue de 15 000 produits paginés à 24 par page (625 pages de pagination), les pages au-delà de la page ~20 sont souvent à une profondeur de crawl > 8.

La pagination en "sauts logarithmiques"

Au lieu d'une navigation linéaire, implémentez une navigation qui permet d'atteindre n'importe quelle page en 2-3 clics maximum :

<!-- Page actuelle : 25 sur 200 -->
<nav aria-label="Pagination" class="pagination">
  <!-- Toujours visible : première page -->
  <a href="/chaussures/">1</a>
  
  <!-- Saut arrière large -->
  <a href="/chaussures/?page=10">10</a>
  
  <!-- Fenêtre autour de la page courante -->
  <a href="/chaussures/?page=23">23</a>
  <a href="/chaussures/?page=24">24</a>
  <span aria-current="page">25</span>
  <a href="/chaussures/?page=26">26</a>
  <a href="/chaussures/?page=27">27</a>
  
  <!-- Saut avant large -->
  <a href="/chaussures/?page=50">50</a>
  <a href="/chaussures/?page=100">100</a>
  
  <!-- Toujours visible : dernière page -->
  <a href="/chaussures/?page=200">200</a>
</nav>

Ce pattern (inspiré de la pagination de Google lui-même) garantit que chaque page paginée est accessible en 3 clics maximum depuis la page 1. Googlebot peut ainsi "sauter" de la page 1 à la page 100, puis de la page 100 à la page 150, sans traverser 149 pages intermédiaires.

Scénario réel : migration de pagination linéaire vers logarithmique

Un site e-commerce mode avec 14 800 produits répartis en 45 catégories. Pagination à 24 produits par page, soit ~617 pages paginées au total. Avant la migration :

  • Profondeur de crawl moyenne des pages paginées : 9.2 (mesuré via Screaming Frog)
  • Pages paginées crawlées par Google en 30 jours (Search Console > Statistiques de crawl) : 38% du total
  • Pages paginées indexées : 22% du total
  • Produits non indexés car situés sur des pages paginées profondes jamais crawlées : ~4 100

Après passage à la pagination logarithmique + ajout des pages paginées profondes dans le sitemap XML :

  • Profondeur de crawl moyenne : 4.1
  • Pages paginées crawlées en 30 jours : 81%
  • Pages paginées indexées : 64%
  • Gain de produits indexés : +3 200 produits

La profondeur de crawl réduite a également libéré du crawl budget pour d'autres sections du site.

Stratégie 3 : infinite scroll et "Load More" sans détruire l'indexation

L'infinite scroll est le cauchemar SEO classique. Googlebot ne scrolle pas, ne clique pas sur "Charger plus", et n'exécute pas les IntersectionObserver. Si votre contenu n'existe que derrière une interaction JavaScript, il n'existe pas pour Google.

La documentation de Google sur le chargement incrémental de pages est explicite : chaque "état" de votre infinite scroll doit correspondre à une URL distincte, accessible via un lien HTML standard.

Le pattern hybride qui fonctionne

Le principe : infinite scroll pour l'utilisateur, pagination HTML classique pour les bots. Les deux coexistent dans le même DOM.

<!-- Contenu initial servi par le serveur -->
<div id="product-list">
  <!-- 24 premiers produits rendus côté serveur -->
  <div class="product-card" data-id="1">...</div>
  <div class="product-card" data-id="24">...</div>
</div>

<!-- Zone d'injection pour l'infinite scroll (vide au chargement) -->
<div id="dynamic-products"></div>

<!-- Pagination HTML — TOUJOURS dans le DOM, visible ou non -->
<!-- CSS : visible par défaut, masqué via JS après hydratation -->
<nav id="pagination-fallback" aria-label="Pagination">
  <a href="/chaussures/?page=2">Page 2</a>
  <a href="/chaussures/?page=3">Page 3</a>
  <a href="/chaussures/?page=4">Page 4</a>
  <a href="/chaussures/?page=5">Page 5</a>
  <a href="/chaussures/?page=10">Page 10</a>
  <a href="/chaussures/?page=26">Dernière page</a>
</nav>

<script type="module">
  // Masquer la pagination statique et activer l'infinite scroll
  // uniquement côté client après hydratation
  const paginationNav = document.getElementById('pagination-fallback');
  if (paginationNav) {
    paginationNav.style.display = 'none';
  }
  
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        loadNextPage();
      }
    });
  }, { rootMargin: '400px' });

  const sentinel = document.createElement('div');
  sentinel.id = 'scroll-sentinel';
  document.getElementById('dynamic-products').after(sentinel);
  observer.observe(sentinel);
  
  let currentPage = 1;
  
  async function loadNextPage() {
    currentPage++;
    const res = await fetch(`/api/products?category=chaussures&page=${currentPage}`);
    const html = await res.text();
    document.getElementById('dynamic-products').insertAdjacentHTML('beforeend', html);
    
    // Mise à jour de l'URL pour le partage et l'historique
    history.replaceState(null, '', `?page=${currentPage}`);
  }
</script>

Le piège du display: none

Masquer la pagination via JavaScript après hydratation (comme ci-dessus) est acceptable — Googlebot parse le HTML initial avant exécution JS, il voit les liens. En revanche, si vous mettez display: none directement dans le CSS servi au premier rendu, Googlebot pourrait ignorer ces liens. La nuance est documentée dans les guidelines Google sur le contenu masqué : le contenu masqué par CSS au chargement initial est traité différemment du contenu masqué par JS après interaction.

Le pattern le plus sûr : servez la pagination visible dans le HTML, laissez JavaScript la masquer côté client. Vérifiez dans Chrome DevTools avec JavaScript désactivé (chrome://settings/content/javascript) que les liens de pagination restent visibles et cliquables.

Attention à history.replaceState

L'utilisation de replaceState pour mettre à jour l'URL pendant le scroll est un nice-to-have pour l'UX (l'utilisateur peut partager l'URL de la "page" où il se trouve). Mais Googlebot n'exécute pas les interactions utilisateur, donc cette URL dynamique n'est jamais vue par le crawler. Ce qui compte, ce sont les liens <a href> dans le HTML initial.

Stratégie 4 : faceted navigation comme substitut de la pagination profonde

Sur les catalogues volumineux, la pagination n'est souvent qu'un palliatif à une architecture de navigation insuffisante. Si votre catégorie "Chaussures" contient 3 000 produits et nécessite 125 pages de pagination, le vrai problème est l'absence de sous-catégories ou de facettes indexables.

Transformer la pagination en arborescence

Au lieu de :

/chaussures/ (3000 produits, 125 pages)
  └── /chaussures/?page=2
  └── /chaussures/?page=3
  └── ...
  └── /chaussures/?page=125

Créez :

/chaussures/ (page hub, liens vers sous-catégories)
  ├── /chaussures/running/ (400 produits, 17 pages)
  ├── /chaussures/ville/ (350 produits, 15 pages)
  ├── /chaussures/randonnee/ (280 produits, 12 pages)
  └── /chaussures/enfant/ (200 produits, 9 pages)

Chaque sous-catégorie a une profondeur de pagination réduite (< 20 pages), ce qui élimine le problème des pages profondes inaccessibles. En bonus, chaque sous-catégorie cible un intent de recherche spécifique avec un <h1> et du contenu éditorial dédié, ce qui la rend indexable sur ses propres mérites.

La limite : filtres combinatoires et index bloat

La faceted navigation mal maîtrisée crée l'effet inverse — une explosion de combinaisons d'URLs (/chaussures/running/rouge/taille-42/marque-nike/) qui génère de l'index bloat. La règle : n'indexez que les facettes qui correspondent à un volume de recherche réel. Les autres doivent être bloquées via robots meta noindex ou exclues du crawl via robots.txt.

Concrètement, lancez un export Search Console > Pages > Performances, filtrez par les URLs facettées, et identifiez celles qui génèrent des impressions. Les autres sont des candidates au noindex.

Stratégie 5 : canonical et gestion de la duplication sur les pages paginées

L'une des erreurs les plus fréquentes post-rel=prev/next : canonicaliser toutes les pages paginées vers la page 1. C'est destructeur. Si /chaussures/?page=5 pointe son canonical vers /chaussures/, Google comprend que page 5 est un doublon de page 1 et ignore son contenu. Les produits des pages 2 à N ne sont plus découverts via le crawl de la pagination.

La règle : chaque page paginée est son propre canonical

<!-- Sur /chaussures/?page=5 -->
<link rel="canonical" href="https://www.monsite.fr/chaussures/?page=5" />

<!-- Sur /chaussures/ (page 1) -->
<link rel="canonical" href="https://www.monsite.fr/chaussures/" />

Chaque page paginée est une page distincte avec un canonical auto-référent. C'est la recommandation explicite de Google.

Le cas des paramètres d'URL multiples

Si votre pagination utilise des paramètres comme ?page=5&sort=price&color=red, vous devez décider quels paramètres sont canoniques. Le tri et l'ordre ne changent pas le contenu — ils réorganisent les mêmes produits. La couleur change le contenu — c'est un filtre.

Configuration Nginx pour forcer un canonical propre en supprimant les paramètres de tri :

# Dans le bloc server ou location
# Réécriture des paramètres de tri vers le canonical sans tri
if ($arg_sort) {
    # Construire l'URL sans le paramètre sort
    set $clean_args '';
    
    # Si page est présent, le conserver
    if ($arg_page) {
        set $clean_args "page=$arg_page";
    }
    
    # Redirection 301 vers l'URL sans sort
    rewrite ^(.*)$ $1?$clean_args? permanent;
}

# Normalisation page=1 vers l'URL sans paramètre
if ($arg_page = "1") {
    rewrite ^(.*)$ $1? permanent;
}

La redirection ?page=1/ est critique. Sans elle, vous avez deux URLs pour le même contenu, et Googlebot perd du crawl budget à crawler les deux.

Vérifiez ce comportement avec l'API URL Inspection de Search Console pour confirmer que Google voit bien le canonical attendu sur chaque page paginée.

Stratégie 6 : sitemap et signaux de découverte pour les pages paginées

Les pages paginées sont rarement liées depuis d'autres sections du site. Leur seul chemin de découverte est la navigation de pagination elle-même. Pour renforcer la découverte, ajoutez-les dans votre sitemap XML.

Mais attention : n'ajoutez pas toutes les pages paginées aveuglément. Une page ?page=47 d'une catégorie de 50 pages contient probablement 3 produits. Cette page n'a aucune valeur SEO propre.

Critères d'inclusion dans le sitemap

Incluez une page paginée dans le sitemap si :

  • Elle contient au moins 50% de la capacité de produits par page (12+ produits sur une page de 24)
  • Les produits qu'elle contient ne sont pas accessibles via une autre URL déjà dans le sitemap (sous-catégorie, page produit directe)
  • Elle n'est pas en noindex

Dans Screaming Frog, exportez la liste des pages paginées (filtrez par URL contenant ?page=), croisez avec le rapport d'indexation Search Console, et identifiez celles qui sont "Discovered - currently not indexed" ou "Crawled - currently not indexed". Ce sont les candidates prioritaires pour l'ajout au sitemap — Google les connaît mais ne les juge pas assez importantes pour les indexer. Le sitemap renforce le signal de découverte.

Pour les sites où les pages paginées contiennent des produits qui changent fréquemment (réassort, promotions), la gestion du sitemap avec des <lastmod> précis aide Googlebot à prioriser le re-crawl des pages paginées mises à jour.

Monitoring continu : détecter les régressions de pagination

La pagination est un système fragile. Une mise à jour du CMS qui change le format des URLs (?page=?p=), un déploiement front qui casse le rendu SSR de la navigation, un filtre qui génère des milliers de nouvelles combinaisons — ces régressions passent inaperçues pendant des semaines si vous n'avez pas de monitoring en place.

Les signaux d'alerte à surveiller dans Search Console :

  • Hausse soudaine des pages "Discovered - currently not indexed" sur les URLs paginées
  • Baisse du nombre de pages crawlées par jour (visible dans Statistiques de crawl)
  • Apparition de soft 404 sur des pages paginées qui fonctionnaient

Un outil de monitoring comme SEOGard détecte automatiquement ces régressions : disparition de liens de pagination dans le HTML, changement de canonical sur les pages paginées, ou augmentation du nombre de pages qui ne passent plus les critères de crawlabilité. Le coût d'un problème de pagination non détecté pendant 3 semaines sur un catalogue de 15 000 produits se mesure directement en produits désindexés et en chiffre d'affaires organique perdu.


rel=prev/next était un raccourci élégant. Son absence force une réflexion architecturale plus profonde — et c'est plutôt une bonne chose. Les sites qui investissent dans une pagination correctement structurée, avec des chemins de crawl courts, des canonicals propres et un monitoring actif, récupèrent un avantage concurrentiel réel sur les catalogues qui se reposent encore sur des annotations abandonnées depuis 7 ans.

Articles connexes

Crawl7 mars 2026

URL Inspection API : automatiser le diagnostic d'indexation

Exploitez l'API URL Inspection de Search Console pour surveiller l'indexation à grande échelle. Code, architecture et cas concrets.

Crawl6 mars 2026

Index bloat : identifier et éliminer l'inflation d'index

Diagnostic et résolution de l'index bloat : méthodes concrètes pour réduire les pages inutiles indexées et récupérer du crawl budget.

Crawl5 mars 2026

Pourquoi Google n'indexe pas vos pages : diagnostic complet

Diagnostic technique des problèmes d'indexation Google : crawl, rendu JS, directives, qualité. Méthodes et outils pour identifier et corriger chaque blocage.