Un site e-commerce de 22 000 pages migre son catalogue produit vers un nouveau PIM. Trois semaines plus tard, Google Search Console signale 4 200 soft 404 — alors que le serveur renvoie bien un status 200 sur chacune de ces URLs. Le trafic organique chute de 18 % en un mois, et personne dans l'équipe ne comprend pourquoi : le site "fonctionne", les pages "s'affichent". Le problème est ailleurs, dans l'écart entre ce que le serveur déclare et ce que Google interprète.
La distinction fondamentale : status code HTTP vs classification Google
Un 404 classique est limpide. Le serveur renvoie un HTTP 404 Not Found, le navigateur affiche une page d'erreur, Googlebot enregistre que la ressource n'existe plus. Le contrat est clair entre le serveur et le crawler.
Un soft 404 est une classification que Google applique unilatéralement, indépendamment du status code HTTP. Le serveur renvoie un 200 OK, mais Google estime que le contenu de la page correspond à une erreur. La documentation officielle de Google Search Central le définit ainsi : une page qui renvoie un code de succès mais dont le contenu indique à l'utilisateur que la page n'existe pas, ou une page avec peu ou pas de contenu utile.
Ce que Google considère comme soft 404
Trois patterns déclenchent systématiquement la classification :
Pages vides ou quasi-vides. Un template de page catégorie qui s'affiche correctement mais ne contient aucun produit. Le header, le footer, la navigation sont là — mais le body principal est vide. Google voit un ratio signal/bruit trop faible et classifie en soft 404.
Pages d'erreur personnalisées sans le bon status code. Le cas classique : votre application renvoie une page stylisée "Oops, ce produit n'est plus disponible" avec un design soigné, mais le serveur envoie un 200 au lieu d'un 404 ou 410. Google lit le contenu, détecte le pattern d'erreur, et classifie en soft 404.
Redirections vers la homepage ou vers une page générique. Quand une URL de produit supprimé redirige vers la homepage via un 302 ou même un 301, Google peut aussi appliquer un signal de soft 404 sur la destination si le contenu ne correspond manifestement pas à l'intent de l'URL d'origine.
Pourquoi cette distinction technique compte
Un vrai 404 est traité proprement par Google : la page est désindexée, le crawl budget n'est plus gaspillé après quelques re-crawls de vérification. Un soft 404 crée un état ambigu. Google continue de crawler la page (elle renvoie 200, donc elle "existe"), tente de comprendre pourquoi le contenu ne correspond pas, et finit par la marquer comme soft 404 dans l'index — mais souvent après plusieurs semaines de crawl inutile.
Sur un site de 22 000 pages, 4 200 soft 404 représentent 19 % de l'espace de crawl consommé pour rien. C'est une hémorragie silencieuse de crawl budget que les logs serveur ne révèlent pas si vous ne regardez que les status codes.
Diagnostic : identifier les soft 404 à grande échelle
Google Search Console : le point de départ
Le rapport "Pages" (anciennement "Couverture") dans Google Search Console est le seul endroit où Google expose explicitement sa classification soft 404. Filtrez par le statut "Soft 404" dans la section "Pourquoi les pages ne sont pas indexées".
Le piège : ce rapport n'est pas temps réel. Google peut mettre 2 à 6 semaines pour signaler un soft 404 après la détection. Sur un site avec des flux produit dynamiques (marketplace, comparateur), le problème est souvent massif avant d'être visible dans la Search Console.
Screaming Frog : simulation locale
Screaming Frog ne peut pas détecter les soft 404 au sens Google (c'est une classification algorithmique), mais il peut identifier les candidats probables. Configurez un crawl avec ces filtres :
Configuration > Spider > Advanced
☑ Check "Respect Robots.txt"
Reports > Response Codes > Filter: 200
Custom Search > Contains:
- "aucun résultat"
- "produit indisponible"
- "page introuvable"
- "no results found"
- "0 produit"
Bulk Export > Response Codes > Client Error (4xx)
L'astuce est dans le Custom Search. En croisant les pages qui renvoient un 200 mais contiennent des patterns textuels d'erreur, vous obtenez une liste de candidats soft 404 avant même que Google ne les signale.
Pour un diagnostic plus fin, exportez la colonne "Word Count" et filtrez les pages avec moins de 50 mots dans le body (hors navigation). Une page produit avec un word count de 30 mots sur un site dont la moyenne est 350 est suspecte.
Vérification via la ligne de commande
Pour valider rapidement le status code réel d'une URL suspecte sans ouvrir un navigateur :
# Vérifier le status code HTTP et les headers de redirection
curl -s -o /dev/null -w "%{http_code} %{redirect_url}" -L "https://shop.example.fr/produit/chaussure-running-xyz-42"
# Vérifier le contenu brut renvoyé (utile pour les SPA)
curl -s "https://shop.example.fr/produit/chaussure-running-xyz-42" | grep -i -E "(introuvable|indisponible|no results|404|error)"
# Comparer le content-length d'une page normale vs suspecte
curl -sI "https://shop.example.fr/produit/chaussure-running-abc-42" | grep -i content-length
curl -sI "https://shop.example.fr/produit/chaussure-running-xyz-42" | grep -i content-length
Un écart significatif de Content-Length entre une page produit active et une page suspecte est un indicateur fiable. Si votre page produit type fait 45 Ko et que la suspecte en fait 8 Ko, le template se charge mais le contenu produit est absent.
Chrome DevTools pour les sites rendus côté client
Sur les applications JavaScript (SPA, React, Vue), le problème est plus insidieux. Le serveur renvoie systématiquement un 200 avec un shell HTML minimal, puis le framework JS fait le rendu côté client. Si l'API produit renvoie une erreur, le composant affiche un message "produit non trouvé" — mais le status HTTP reste 200.
Pour diagnostiquer :
- Ouvrez Chrome DevTools > Network
- Filtrez par "Doc" pour voir le document HTML initial
- Vérifiez le status code du document principal
- Puis filtrez par "XHR/Fetch" pour voir les appels API sous-jacents
- Si l'API produit renvoie un
404ou un body vide mais que le document principal est200, vous avez un soft 404 en puissance
Les quatre patterns de soft 404 les plus fréquents (et comment les corriger)
Pattern 1 : pages catégorie sans produit
Le cas le plus répandu en e-commerce. Une catégorie contenait 150 produits, les stocks sont épuisés ou les produits désactivés. Le template de catégorie s'affiche avec ses filtres, son breadcrumb, son texte SEO — mais la grille produit est vide.
Correction côté serveur (Node.js / Express) :
// middleware/categoryHandler.ts
import { Request, Response, NextFunction } from 'express';
import { getCategoryProducts } from '../services/catalog';
export async function categoryHandler(req: Request, res: Response, next: NextFunction) {
const { slug } = req.params;
const products = await getCategoryProducts(slug);
if (!products || products.length === 0) {
// Option A : 404 franc si la catégorie ne devrait plus exister
// res.status(404).render('404', { message: 'Catégorie vide' });
// Option B : 410 Gone si la catégorie est définitivement supprimée
// res.status(410).render('gone', { slug });
// Option C : garder la page mais injecter un noindex
// (uniquement si la catégorie peut se re-remplir bientôt)
res.set('X-Robots-Tag', 'noindex');
res.status(200).render('category', {
slug,
products: [],
isEmpty: true,
seoNotice: 'Cette catégorie est temporairement vide.'
});
return;
}
res.status(200).render('category', { slug, products, isEmpty: false });
}
Le choix entre ces trois options dépend du contexte métier. Si la catégorie "Promotions Saint-Valentin" est saisonnière, un noindex temporaire est plus adapté qu'un 404. Si la catégorie "Cassettes VHS" ne reviendra jamais, un 410 Gone signale clairement à Google que la ressource est définitivement supprimée — Google désindexe plus rapidement avec un 410 qu'avec un 404.
Pattern 2 : produits épuisés sur un site e-commerce
Ce pattern mérite un traitement nuancé. Un produit épuisé temporairement ne devrait pas renvoyer un 404 — la page a potentiellement des backlinks, du PageRank, et le produit peut revenir en stock. Un produit définitivement retiré du catalogue doit être traité différemment.
Pour un produit temporairement indisponible : gardez la page en 200, maintenez le contenu descriptif complet, affichez clairement l'indisponibilité, et désactivez le bouton d'achat. Google n'appliquera pas de soft 404 si le contenu substantiel (description, specs, images) est toujours présent. Renforcez le schema Product avec "availability": "https://schema.org/OutOfStock".
Pour un produit définitivement retiré : renvoyez un 301 vers le produit successeur s'il existe, ou un 301 vers la catégorie parente. Si aucune redirection pertinente n'existe, renvoyez un 410 Gone. Ne redirigez pas vers la homepage — c'est le meilleur moyen de récolter des soft 404 sur la homepage elle-même.
Pattern 3 : pages de recherche interne indexées
Les URLs de recherche interne (/search?q=chaussure+rouge) sont souvent des nids à soft 404. Quand la requête ne renvoie aucun résultat, la page affiche "0 résultat" avec un 200. Google classifie en soft 404.
Correction via Nginx :
# Bloquer l'indexation des pages de recherche interne
location /search {
# Ajouter X-Robots-Tag noindex sur toutes les pages de recherche
add_header X-Robots-Tag "noindex, follow" always;
# Passer la requête au backend
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Alternative : renvoyer un 404 si le paramètre q est vide
location /search {
if ($arg_q = "") {
return 404;
}
proxy_pass http://backend;
}
Combinez cette approche avec une règle Disallow: /search dans le robots.txt. La ceinture et les bretelles : le robots.txt empêche le crawl, le X-Robots-Tag empêche l'indexation si Googlebot atteint quand même la page via un lien.
Pattern 4 : pages rendues en JavaScript avec API fallback défaillante
Le pattern le plus traître. Votre SPA React ou Vue fait un appel API au chargement. Si l'API échoue ou renvoie un objet vide, le composant affiche un fallback — souvent un message d'erreur ou un spinner infini. Googlebot reçoit un 200 avec un DOM quasi-vide après rendu.
Correction dans Next.js (App Router) :
// app/produit/[slug]/page.tsx
import { notFound } from 'next/navigation';
import { getProduct } from '@/lib/api';
interface ProductPageProps {
params: { slug: string };
}
export default async function ProductPage({ params }: ProductPageProps) {
const product = await getProduct(params.slug);
// notFound() déclenche un vrai 404 HTTP côté serveur
// Next.js renvoie le status code 404 dans la réponse SSR
if (!product || !product.id) {
notFound();
}
// Vérification supplémentaire : produit avec contenu insuffisant
if (!product.description || product.description.length < 50) {
// Log pour investigation — potentiel problème de data pipeline
console.warn(`[SEO] Produit ${params.slug} : contenu insuffisant (${product.description?.length || 0} chars)`);
// Deux options selon le contexte :
// 1. notFound() si le produit ne devrait pas être public
// 2. Servir la page avec un warning interne pour l'équipe contenu
}
return (
<main>
<h1>{product.name}</h1>
<p>{product.description}</p>
{/* ... reste du composant */}
</main>
);
}
// Optionnel : page 404 personnalisée au niveau du segment
// app/produit/[slug]/not-found.tsx
export default function ProductNotFound() {
return (
<main>
<h1>Produit non disponible</h1>
<p>Ce produit n'existe plus dans notre catalogue.</p>
<a href="/catalogue">Voir tous nos produits</a>
</main>
);
}
Le point crucial : notFound() dans Next.js App Router renvoie bien un HTTP 404 au niveau du serveur. C'est la raison pour laquelle le SSR est préférable à un rendu purement client pour les sites à contenu dynamique — vous gardez le contrôle du status code HTTP.
Scénario réel : migration d'un catalogue de 15 000 produits
Prenons un cas concret. Un retailer mode opère un site de 15 000 URLs produit, 400 catégories, 50 pages de contenu éditorial. Le site tourne sur React avec un rendu côté serveur via Next.js.
Le retailer change de PIM (Product Information Management). Pendant la migration, 3 200 produits sont temporairement sans données dans le nouveau PIM. Le template produit s'affiche — header, navigation, footer, sidebar de recommandations — mais la zone centrale produit est vide : pas de titre produit, pas de description, pas de prix, pas d'image.
Chronologie de l'incident
Jour 0 : La migration PIM est déployée en production. Le monitoring applicatif est vert — aucune erreur 5xx, temps de réponse stables.
Jour 3-5 : Googlebot crawle les 3 200 URLs impactées. Reçoit un 200 OK avec un DOM contenant ~800 caractères (header + footer) au lieu des ~3 500 caractères habituels pour une page produit. Aucune alerte dans les outils de monitoring classiques.
Jour 14 : Google Search Console commence à signaler des soft 404. Les premiers rapports montrent 800 URLs classifiées.
Jour 21 : Le rapport grimpe à 2 900 URLs en soft 404. Le trafic organique sur les pages produit baisse de 22 %. Le trafic catégorie baisse de 8 % (les pages catégorie perdent des produits dans leur maillage interne, ce qui dilue leur pertinence).
Jour 28 : L'équipe SEO identifie enfin le problème. Les données PIM manquantes sont corrigées en urgence.
Jour 35-50 : Après correction, Google re-crawle progressivement les URLs. La classification soft 404 est levée par lots. Le retour au trafic d'origine prend 3 à 5 semaines supplémentaires.
Bilan : 7 semaines de perte de trafic
Le coût total ? 7 semaines de trafic dégradé, soit une baisse cumulée estimée entre 15 et 25 % du trafic organique produit sur la période. Pour un site avec un revenu organique mensuel de 200K€, c'est entre 50K€ et 90K€ de manque à gagner.
Le problème aurait été détectable dès le jour 0 avec un monitoring qui compare le contenu rendu aux baselines historiques. Un outil de monitoring SEO technique comme Seogard détecte ce type de régression en quelques heures — la disparition soudaine de contenu substantiel sur des milliers de pages déclenche une alerte avant même que Google ne commence à crawler.
Stratégie de gestion des status codes : l'arbre de décision
La gestion des pages disparues ou dégradées n'est pas binaire. Voici l'arbre de décision technique que vous devriez implémenter :
La page est définitivement supprimée
- Le contenu avait des backlinks significatifs →
301vers la page la plus pertinente (produit similaire, catégorie parente). Cf. checklist des redirections de migration. - Le contenu n'avait aucun backlink ni trafic →
410 Gone. Plus propre qu'un404— Google comprend que c'est une décision intentionnelle et désindexe plus vite. - Aucune redirection pertinente n'est possible →
404ou410. Ne redirigez pas vers la homepage.
La page est temporairement vide
- Le contenu reviendra sous 2-4 semaines → gardez le
200, ajoutez un<meta name="robots" content="noindex">temporaire, maintenez le maximum de contenu existant. - Le contenu reviendra mais sans date précise →
200avecnoindex, ou503 Service Unavailableavec unRetry-Afterheader si la page entière est impactée.
La page existe mais avec un contenu dégradé
- Contenu partiel (description présente, images manquantes) → gardez le
200, ne touchez pas au robots. Corrigez les données en priorité. - Contenu quasi-absent (shell HTML vide) → forcez un
404côté serveur. Ne laissez pas Google décider — sa classification soft 404 est plus lente et plus imprévisible que votre propre404.
Cas particulier : trailing slash et faux 404
Un edge case fréquent : des URLs avec et sans trailing slash qui renvoient des status codes différents. https://shop.example.fr/categorie/chaussures renvoie un 200, mais https://shop.example.fr/categorie/chaussures/ renvoie un soft 404 (ou l'inverse). Googlebot peut crawler les deux variantes si elles coexistent dans votre sitemap, vos liens internes ou vos backlinks. Normalisez le trailing slash au niveau serveur et canonicalisez proprement.
Monitoring continu : détecter avant Google
L'approche réactive (attendre le rapport Search Console) coûte cher. Voici les mécanismes de détection proactive à mettre en place.
Monitoring du word count par template
Implémentez un check automatisé qui mesure le contenu textuel de vos pages critiques. Le principe : définir un seuil minimum de contenu par type de page et alerter quand le contenu passe en dessous.
// scripts/check-content-health.ts
import puppeteer from 'puppeteer';
interface ContentCheck {
url: string;
template: 'product' | 'category' | 'article';
wordCount: number;
hasMainContent: boolean;
statusCode: number;
}
const THRESHOLDS: Record<string, number> = {
product: 100, // Un produit doit avoir au minimum 100 mots de contenu
category: 50, // Une catégorie avec des produits a au moins 50 mots
article: 300, // Un article de blog doit dépasser 300 mots
};
async function checkPage(url: string, template: string): Promise<ContentCheck> {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
const response = await page.goto(url, { waitUntil: 'networkidle2' });
const statusCode = response?.status() ?? 0;
// Extraire le texte du contenu principal (exclure header/footer/nav)
const mainContent = await page.evaluate(() => {
const main = document.querySelector('main, [role="main"], .product-detail, .category-grid');
if (!main) return '';
// Supprimer les éléments de navigation imbriqués
const clone = main.cloneNode(true) as Element;
clone.querySelectorAll('nav, header, footer, .breadcrumb').forEach(el => el.remove());
return clone.textContent?.trim() ?? '';
});
const wordCount = mainContent.split(/\s+/).filter(Boolean).length;
await browser.close();
return {
url,
template: template as ContentCheck['template'],
wordCount,
hasMainContent: wordCount >= (THRESHOLDS[template] ?? 50),
statusCode,
};
}
// Utilisation : vérifier un échantillon de pages après chaque déploiement
async function auditSample(urls: Array<{ url: string; template: string }>) {
const results = await Promise.all(urls.map(u => checkPage(u.url, u.template)));
const softFourOhFourCandidates = results.filter(r =>
r.statusCode === 200 && !r.hasMainContent
);
if (softFourOhFourCandidates.length > 0) {
console.error(`🚨 ${softFourOhFourCandidates.length} pages suspectes (soft 404 potentiel) :`);
softFourOhFourCandidates.forEach(r =>
console.error(` ${r.url} — ${r.wordCount} mots (seuil : ${THRESHOLDS[r.template]})`)
);
process.exit(1); // Fail le pipeline CI/CD
}
}
Ce script, intégré dans votre pipeline CI/CD, bloque un déploiement si des pages critiques tombent en dessous du seuil de contenu. C'est une détection jour 0, pas jour 14.
Analyse des logs de crawl Googlebot
Croisez vos logs serveur avec les rapports Search Console. Si Googlebot crawle une URL 5 fois en 10 jours alors qu'elle recevait un crawl par mois auparavant, c'est un signal : Google doute du contenu et re-vérifie. Ce pattern précède souvent une classification soft 404.
# Extraire les URLs les plus crawlées par Googlebot ces 7 derniers jours
grep "Googlebot" /var/log/nginx/access.log \
| awk '{print $7}' \
| sort \
| uniq -c \
| sort -rn \
| head -50
Comparez cette liste avec la baseline du mois précédent. Un spike de crawl sur des URLs produit spécifiques sans changement de contenu intentionnel est un red flag.
Erreurs de gestion courantes à éviter
Ne pas confondre soft 404 et thin content
Google fait la distinction. Une page thin content a du contenu, mais insuffisant pour être utile. Une soft 404 est une page que Google considère comme une erreur déguisée. En pratique, le traitement est différent : un thin content peut être enrichi et ré-évalué sans perte d'indexation ; un soft 404 est retiré de l'index et nécessite une correction plus profonde avant d'être ré-inclus.
Ne pas forcer un 200 "pour garder le jus SEO"
Un réflexe courant : "si je renvoie un 404, je perds le PageRank de mes backlinks". C'est techniquement vrai, mais renvoyer un 200 sur une page vide est pire. Google classifie en soft 404, la page est désindexée de toute façon, ET Googlebot continue de gaspiller du crawl budget dessus. Vous perdez le PageRank ET le crawl budget.
La bonne approche : 301 vers une page pertinente pour conserver le jus, ou 410 pour couper proprement.
Ne pas ignorer les soft 404 sur les pages facettées
Sur un site e-commerce avec navigation facettée (/chaussures?couleur=rose&taille=47), certaines combinaisons de filtres ne renvoient aucun résultat. Si ces URLs sont crawlables (pas bloquées par robots.txt, pas canonicalisées correctement), chaque combinaison vide est un soft 404 potentiel. Avec 8 filtres et 10 valeurs chacun, vous pouvez générer des dizaines de milliers de soft 404 sans en avoir conscience.
Canonicalisez les pages facettées vers la catégorie parente, ou bloquez-les avec un meta robots noindex, et vérifiez que vos données structurées ne référencent pas ces URLs dans le schema BreadcrumbList ou Product.
Le status 410 Gone : un outil sous-utilisé
Le 410 Gone est le status code le plus propre pour signaler une suppression définitive. La documentation Google confirme que Google traite un 410 de la même manière qu'un 404, mais avec un signal d'intentionnalité plus fort. En pratique, Google arrête de re-crawler une URL en 410 plus rapidement qu'une URL en 404.
Utilisez 410 quand :
- Un produit est définitivement retiré du catalogue (pas de successeur)
- Une page éditoriale est volontairement supprimée
- Une URL issue d'une ancienne architecture n'a pas d'équivalent dans la nouvelle
Utilisez 404 quand :
- Vous ne savez pas si la page devrait exister (erreur de lien, typo)
- La situation est temporaire et la page pourrait revenir
La distinction semble subtile, mais à grande échelle (des milliers d'URLs), elle impacte la vitesse à laquelle Google libère du crawl budget pour vos pages actives.
Wrap-up
La gestion des soft 404 est un problème d'observabilité, pas de compétence technique. Les équipes savent corriger un status code — le défi est de détecter le problème avant que Google ne le signale dans un rapport datant de trois semaines. L'approche gagnante combine un contrôle strict des status codes côté serveur, un monitoring du contenu rendu par type de page, et une analyse régulière des patterns de crawl dans les logs. Un outil de monitoring continu comme Seogard automatise cette détection en comparant le rendu de vos pages à leurs baselines historiques, et alerte dès qu'une régression de contenu se produit — avant que Google ne la classifie en soft 404.