Gary Illyes vient de publier un billet qui change la compréhension qu'on avait du fonctionnement interne de Googlebot. Le point central : Googlebot n'est pas un crawler autonome. C'est un client d'une plateforme de crawl centralisée chez Google, et cette plateforme applique des limites strictes en bytes — des limites que la plupart des SEO technique n'avaient jamais pu quantifier avec certitude.
Ces révélations ont des conséquences directes sur la façon dont vous structurez vos pages, servez vos ressources et priorisez votre crawl budget. Voici l'analyse technique complète.
L'architecture réelle de Googlebot : un client parmi d'autres
La révélation la plus structurante du billet de Gary Illyes concerne l'architecture même du système. Googlebot n'est pas un programme monolithique qui crawle le web de manière autonome. Il s'agit d'un client connecté à une plateforme de crawl centralisée — une infrastructure partagée avec d'autres services Google (Google Shopping, Google News, etc.).
La plateforme de crawl centralisée
Cette architecture implique plusieurs choses. D'abord, les limites de ressources ne sont pas propres à Googlebot : elles sont imposées par la plateforme sous-jacente. Quand la plateforme décide qu'un fetch a consommé assez de bytes, elle coupe — que ce soit Googlebot, le crawler de Google News ou un autre client.
Concrètement, la plateforme gère :
- La file d'attente des URLs à crawler
- L'allocation de bande passante par domaine (le fameux crawl rate)
- Les limites de taille par réponse HTTP
- Le scheduling entre les différents clients
Cette centralisation explique pourquoi des comportements de crawl qui semblaient incohérents prennent soudain du sens. Un pic de crawl Google News sur votre domaine peut indirectement affecter la fréquence de crawl de Googlebot pour l'indexation organique — ils partagent la même infrastructure et les mêmes politiques de politeness.
Ce que ça change pour le diagnostic
Quand vous observez des anomalies dans les logs serveur — des fetches incomplets, des ressources non chargées, des pages partiellement crawlées — vous savez maintenant que le problème peut venir de la plateforme centralisée, pas de Googlebot lui-même. La distinction est importante pour le diagnostic.
Pour analyser ce comportement dans vos logs, filtrez les user-agents de manière granulaire :
# Extraire les requêtes Googlebot avec le status code et la taille de la réponse
awk '$0 ~ /Googlebot/ {
match($0, /"[A-Z]+ ([^ ]+)/, url);
match($0, /\" ([0-9]{3}) ([0-9]+)/, resp);
print url[1], resp[1], resp[2]
}' /var/log/nginx/access.log | sort -t' ' -k3 -n -r | head -50
Cette commande vous donne les 50 plus grosses réponses servies à Googlebot. Si vous voyez des réponses qui s'arrêtent systématiquement autour de la même taille, vous touchez probablement une limite de la plateforme de crawl.
Les limites en bytes : les chiffres enfin documentés
Le billet confirme et précise des limites que la communauté SEO avait partiellement devinées à travers l'expérimentation.
HTML brut : 15 MB
La limite pour le document HTML principal est de 15 mégaoctets. Au-delà, Googlebot tronque. Le contenu après la coupure n'existe tout simplement pas pour Google.
15 MB semble confortable pour une page standard. Mais considérez un catalogue e-commerce qui injecte 8 000 produits dans une page catégorie avec du JSON-LD par produit, des attributs data-*, du markup microdata, et un DOM lourd généré par un framework frontend. On atteint ces limites plus vite qu'on ne le pense.
Ressources individuelles : la limite par fichier
Chaque ressource chargée (CSS, JS, images pour le rendering) est soumise à sa propre limite. Les fichiers JavaScript, en particulier, sont limités eux aussi. Quand un bundle JS dépasse la limite, le Web Rendering Service (WRS) ne peut pas exécuter le code complet — et le contenu rendu côté client disparaît de l'index.
C'est ici que l'impact est le plus vicieux. Un fichier vendor.js de 3 MB qui passe un jour et se fait tronquer le lendemain (parce que la plateforme est sous pression) produit un rendering partiel. Le contenu critique — titres, descriptions produit, prix — peut disparaître de la version rendue que Google indexe. Ce type de régression est exactement ce que décrit notre article sur pourquoi Google voit une page blanche sur votre SPA.
Impact sur le rendering JavaScript
Le Web Rendering Service (WRS) de Google, basé sur une version headless de Chrome, doit charger et exécuter le JavaScript dans les mêmes contraintes de bytes imposées par la plateforme. Voici la chaîne complète :
- La plateforme fetch le HTML (limite 15 MB)
- Le WRS parse le HTML, identifie les ressources
- Chaque ressource JS/CSS est fetchée (limite par fichier)
- Le WRS exécute le JS dans un Chrome headless avec timeout
- Le DOM rendu est renvoyé au pipeline d'indexation
Si le bundle JS est tronqué à l'étape 3, l'étape 4 échoue silencieusement. Pas d'erreur dans Search Console. Pas de signal dans les logs. Juste du contenu manquant dans l'index.
Pour vérifier si vos bundles sont à risque :
# Lister tous les fichiers JS servis avec leur taille, triés par poids
find /var/www/votre-site/public -name "*.js" -exec du -h {} + | sort -rh | head -20
# Ou via curl pour voir ce que le serveur envoie réellement (avec compression)
curl -sI -H "Accept-Encoding: gzip" https://shop.votredomaine.fr/static/js/main.abc123.js | grep -i content-length
Attention : la limite s'applique au contenu décompressé. Un fichier de 800 KB gzippé qui fait 2.8 MB une fois décompressé est évalué à 2.8 MB par le WRS.
Scénario concret : un e-commerce de 22 000 pages face aux byte limits
Prenons un cas réaliste. MaisonDuMeuble.fr est un e-commerce Next.js avec 22 000 pages : 18 000 fiches produit, 350 pages catégorie, le reste en pages CMS, blog, et landing pages.
Le problème initial
L'équipe SEO constate une chute de 35% du trafic organique sur les pages catégorie en 3 semaines. Search Console montre une baisse d'impressions mais aucune erreur de couverture. Le site a déployé une refonte frontend 4 semaines avant.
Le diagnostic
L'analyse des logs révèle que Googlebot crawle bien les pages catégorie. Mais l'inspection URL dans Search Console montre un écart entre le HTML brut et le HTML rendu : sur les pages catégorie, le listing produits (généré côté client) n'apparaît pas dans la version rendue.
La cause : la refonte a introduit un nouveau design system. Le bundle principal est passé de 1.2 MB à 4.1 MB (décompressé). Les pages catégorie, qui dépendent entièrement du JS pour afficher les produits, se retrouvent avec un rendering incomplet.
La correction
L'équipe applique trois mesures :
1. Code splitting agressif
// next.config.js - Configuration du code splitting
/** @type {import('next').NextConfig} */
const nextConfig = {
webpack: (config, { isServer }) => {
if (!isServer) {
config.optimization.splitChunks = {
chunks: 'all',
maxSize: 500000, // 500 KB max par chunk (décompressé)
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name(module) {
// Séparer chaque gros package en son propre chunk
const packageName = module.context.match(
/[\\/]node_modules[\\/](.*?)([\\/]|$)/
)[1];
return `vendor.${packageName.replace('@', '')}`;
},
priority: 10,
},
// Chunk séparé pour le design system
designSystem: {
test: /[\\/]components[\\/]ui[\\/]/,
name: 'design-system',
priority: 20,
reuseExistingChunk: true,
},
},
};
}
return config;
},
};
module.exports = nextConfig;
2. Migration des pages catégorie vers SSR
Les pages catégorie passent de CSR à SSR avec getServerSideProps. Le listing produit est désormais dans le HTML initial, indépendant du JavaScript. Ce choix de rendering est détaillé dans notre comparatif ISR, SSR, SSG : quel mode de rendering pour le SEO.
3. Monitoring du poids des ressources
L'équipe ajoute un check dans leur CI/CD :
#!/bin/bash
# check-bundle-size.sh — Script CI pour bloquer les déploiements
# si un chunk JS dépasse la limite de sécurité
MAX_SIZE_KB=600 # Seuil de sécurité : 600 KB décompressé
BUILD_DIR=".next/static/chunks"
FAILED=0
echo "Vérification des tailles de bundles JS..."
for file in "$BUILD_DIR"/*.js; do
SIZE_KB=$(du -k "$file" | cut -f1)
if [ "$SIZE_KB" -gt "$MAX_SIZE_KB" ]; then
echo "❌ FAIL: $(basename $file) = ${SIZE_KB}KB (limite: ${MAX_SIZE_KB}KB)"
FAILED=1
fi
done
if [ "$FAILED" -eq 1 ]; then
echo ""
echo "Des chunks JS dépassent la limite de sécurité."
echo "Googlebot peut tronquer ces fichiers lors du rendering."
echo "Appliquez du code splitting ou migrez le contenu critique vers SSR."
exit 1
fi
echo "✅ Tous les chunks JS sont sous ${MAX_SIZE_KB}KB"
exit 0
Le résultat
Trois semaines après le déploiement des correctifs, les pages catégorie récupèrent leur visibilité. Le trafic organique revient au niveau d'avant la refonte, puis dépasse de 8% grâce à l'amélioration du HTML initial (SSR) sur les pages catégorie.
Le point clé : sans l'information sur les byte limits, l'équipe aurait cherché un problème de contenu ou d'algorithme. C'était un problème d'infrastructure pure — un bundle trop gros qui empêchait le rendering.
Optimiser la taille de vos pages pour la plateforme de crawl
Connaître les limites ne suffit pas. Il faut les intégrer dans votre workflow de développement.
Auditer le poids réel de vos pages
Screaming Frog permet de mesurer la taille du HTML brut de chaque page, mais pas la taille des ressources chargées pour le rendering. Pour une vision complète, combinez deux approches :
Screaming Frog pour le HTML :
- Configuration > Spider > cochez "Store HTML" et "Store Rendered HTML"
- Bulk Export > Response Codes > exportez avec la colonne "HTML Size"
- Filtrez les pages > 5 MB en HTML brut (zone de danger)
Chrome DevTools pour les ressources :
- Network tab > filtrez par "JS" > triez par "Size" (colonne décompressée)
- Identifiez les fichiers > 1 MB décompressé
- Vérifiez le Coverage tab pour le taux de JS inutilisé
Pour automatiser cette vérification sur un grand nombre de pages, un script Puppeteer est plus adapté :
// audit-page-weight.mjs
import puppeteer from 'puppeteer';
const urls = [
'https://shop.votredomaine.fr/categorie/canapes',
'https://shop.votredomaine.fr/categorie/tables',
'https://shop.votredomaine.fr/produit/canape-stockholm-velours',
];
const BYTE_LIMIT_HTML = 15 * 1024 * 1024; // 15 MB
const BYTE_LIMIT_JS = 2 * 1024 * 1024; // 2 MB seuil d'alerte
(async () => {
const browser = await puppeteer.launch({ headless: 'new' });
for (const url of urls) {
const page = await browser.newPage();
const resources = [];
page.on('response', async (response) => {
const resourceUrl = response.url();
const headers = response.headers();
const contentLength = parseInt(headers['content-length'] || '0', 10);
const contentType = headers['content-type'] || '';
if (contentType.includes('javascript')) {
try {
const buffer = await response.buffer();
resources.push({
url: resourceUrl.substring(0, 80),
compressed: contentLength,
decompressed: buffer.length,
});
} catch (e) { /* response already disposed */ }
}
});
const response = await page.goto(url, { waitUntil: 'networkidle0', timeout: 30000 });
const htmlContent = await page.content();
const htmlSize = Buffer.byteLength(htmlContent, 'utf8');
console.log(`\n--- ${url} ---`);
console.log(`HTML rendu : ${(htmlSize / 1024).toFixed(0)} KB ${htmlSize > BYTE_LIMIT_HTML ? '⚠️ DÉPASSE 15MB' : '✓'}`);
const dangerousJS = resources.filter(r => r.decompressed > BYTE_LIMIT_JS);
if (dangerousJS.length > 0) {
console.log(`⚠️ ${dangerousJS.length} fichier(s) JS > 2MB décompressé :`);
dangerousJS.forEach(r => {
console.log(` ${r.url}... → ${(r.decompressed / 1024 / 1024).toFixed(2)} MB`);
});
} else {
console.log(`JS : ${resources.length} fichiers, tous < 2MB ✓`);
}
await page.close();
}
await browser.close();
})();
Le piège du HTML gonflé artificiellement
Certains frameworks (surtout en SSR avec hydration) produisent un HTML qui contient deux fois le contenu : une fois dans le DOM HTML, une fois sérialisé dans un <script> pour l'hydration côté client. C'est le cas de Next.js avec __NEXT_DATA__, de Nuxt avec __NUXT__, et de tout framework qui injecte le state initial en JSON.
Sur un catalogue de 200 produits par page catégorie, le JSON sérialisé dans __NEXT_DATA__ peut facilement atteindre 2-3 MB. Ajoutez le HTML du DOM, les scripts inline, le CSS critique — et vous flirtez avec les limites.
La solution : paginer agressivement (50 produits max par page), utiliser getServerSideProps avec des projections strictes (ne sérialisez que les champs nécessaires à l'affichage), et implémentez la pagination côté serveur, pas côté client. C'est un cas typique de mismatch entre SSR et hydration qui peut avoir des conséquences sur l'indexation.
Configuration serveur : servir des réponses optimisées à Googlebot
Vous ne voulez pas servir une version différente à Googlebot (c'est du cloaking). Mais vous pouvez optimiser la compression et les headers pour que la plateforme de crawl travaille efficacement :
# nginx.conf — Optimisations pour le crawl
server {
# Compression Brotli pour réduire le transfert
# (la limite s'applique au décompressé, mais un transfert plus rapide
# réduit les risques de timeout de la plateforme)
brotli on;
brotli_types text/html text/css application/javascript application/json;
brotli_comp_level 6;
# Headers de taille pour permettre à la plateforme de planifier le fetch
location ~* \.(js|css)$ {
add_header Content-Length $content_length always;
# Cache long pour que la plateforme ne re-fetche pas les assets stables
add_header Cache-Control "public, max-age=31536000, immutable";
}
# Limiter la taille des pages HTML dynamiques côté applicatif
# Si une page dépasse 10 MB, c'est un bug — renvoyez une 500
location / {
proxy_pass http://backend;
proxy_buffer_size 16k;
proxy_buffers 8 1m;
proxy_max_temp_file_size 10m; # Sécurité : coupe à 10 MB
}
}
Implications pour le crawl budget et la priorisation
Les byte limits interagissent directement avec le crawl budget et les limites de crawl documentées par Google. La plateforme de crawl centralisée alloue un budget en bytes, pas seulement en requêtes.
Chaque byte compte dans l'allocation
Si votre page catégorie pèse 8 MB en HTML et que Googlebot doit crawl 350 pages catégorie, c'est 2.8 GB de données rien que pour les pages catégorie. Ajoutez les ressources JS et CSS nécessaires au rendering, et vous pouvez facilement multiplier par 3. Comparez avec un site concurrent dont les pages catégorie pèsent 200 KB en HTML avec du contenu SSR : il consomme 14 fois moins de bande passante pour le même nombre de pages.
La plateforme de crawl va naturellement favoriser le site le moins coûteux à crawler. Ce n'est pas une pénalité — c'est de la gestion de ressources.
Prioriser les pages légères pour un crawl complet
Pour un site volumineux, la stratégie est claire :
- Les pages à haute valeur SEO (catégories, fiches produit principales) doivent être les plus légères possible en HTML initial. SSR avec des payloads minimaux.
- Les pages secondaires (CGV, FAQ longues, documentation technique) peuvent être plus lourdes — elles sont crawlées moins fréquemment.
- Les sitemaps doivent être segmentés par priorité et type de contenu, comme expliqué dans notre article sur le découpage des sitemaps en fichiers multiples.
Surveiller les signaux de truncation
Quand Googlebot tronque une page, il n'y a pas de signal explicite dans Search Console. Les seuls indices :
- Inspection URL : comparez le HTML brut et le HTML rendu. Si le rendu s'arrête au milieu d'une section, c'est un signe de truncation JS.
- Logs serveur : des requêtes avec des réponses partielles (connexion fermée avant la fin du transfert).
- Couverture d'index : des pages qui oscillent entre "indexée" et "découverte, pas encore indexée" sans raison apparente.
- Données structurées : des rich results qui apparaissent et disparaissent de manière cyclique. Si votre JSON-LD est en fin de page et que le HTML est tronqué, le markup disparaît.
Un outil de monitoring continu comme Seogard détecte automatiquement ce type de régression — quand des meta tags ou des éléments structurels disparaissent soudainement du rendu, c'est souvent le signe d'un problème de byte limit ou de rendering incomplet.
Les edge cases que cette architecture révèle
Les soft 404 géantes
Certains sites génèrent des pages d'erreur personnalisées extrêmement lourdes (navigation complète, recommandations produit, recherche intégrée). Si votre page 404 pèse 3 MB et que Googlebot crawle des milliers d'URLs en 404, vous gaspillez du budget de crawl en bytes sur des pages que vous ne voulez même pas indexer.
Les pages d'API consommées par le WRS
Quand le WRS exécute votre JavaScript et que celui-ci fait des appels API pour récupérer du contenu, chaque réponse API consomme du budget bytes. Un appel qui retourne 500 KB de JSON pour afficher 10 lignes de texte est un gaspillage que la plateforme n'a aucune raison de tolérer indéfiniment.
Si votre architecture repose sur du client-side rendering avec des appels API, considérez sérieusement le passage à du SSR ou du prerendering pour éliminer cette couche de fetches supplémentaires. Le dynamic rendering peut aussi être une solution transitoire, avec les réserves habituelles sur sa maintenance.
Le cas des single-page applications
Les SPA sont les plus exposées. Le HTML initial est quasi vide (quelques KB), mais le rendering dépend entièrement de bundles JS lourds. Si un seul de ces bundles est tronqué, l'intégralité du contenu disparaît. C'est la raison pour laquelle le choix entre SSR et CSR n'est pas qu'une question de performance — c'est une question de résilience face aux contraintes de la plateforme de crawl.
Pour vérifier ce que Google voit réellement sur vos pages critiques, l'inspection URL de Search Console reste le premier réflexe. Mais elle ne teste qu'un snapshot à un instant T. Les variations de comportement de la plateforme de crawl (charge, priorisation) font que le résultat peut différer du crawl réel en production.
Ce que Google ne dit pas (encore)
Le billet de Gary Illyes est une avancée significative en termes de transparence. Mais plusieurs zones d'ombre persistent.
La limite exacte par fichier JS/CSS n'est pas documentée publiquement. Les 15 MB pour le HTML sont confirmés, mais le seuil pour les ressources individuelles reste flou. L'expérience de la communauté SEO suggère un seuil autour de 15 MB par ressource également, selon la documentation Google sur les tailles maximales, mais le comportement réel peut varier selon la charge de la plateforme.
La question de la priorisation interne entre les clients de la plateforme (Googlebot Search vs. Googlebot News vs. autres) n'est pas abordée. Si votre site est intensivement crawlé par Google Shopping, cela affecte-t-il le budget disponible pour l'indexation organique ? Le billet ne le dit pas.
Enfin, l'impact du rendering JavaScript sur le budget bytes global reste opaque. Quand le WRS fetch 20 fichiers JS pour rendre une page, est-ce que le coût bytes total (HTML + JS + CSS + API calls) est agrégé et limité ? Ou chaque ressource est-elle indépendante ? La réponse a des implications majeures pour les architectures frontend modernes.
Les révélations de Gary Illyes sur l'architecture de crawl de Google transforment des intuitions de la communauté SEO en faits documentés. Le takeaway principal : votre site est en compétition pour un budget en bytes autant qu'un budget en requêtes, et chaque mégaoctet superflu dans vos pages réduit la capacité de Google à crawler et indexer l'ensemble de votre site. Intégrez les checks de poids dans votre CI/CD, migrez le contenu critique vers du SSR, et surveillez en continu les régressions de rendering — un outil comme Seogard automatise cette détection pour que vous ne découvriez pas le problème trois semaines après la chute de trafic.