Comment réaliser un audit SEO PageSpeed avec Claude Code sans modifier le site sans revue

Points clés

Créez un flux Claude Code local qui conserve les preuves PageSpeed et ne transforme les constats en petites modifications testables qu après approbation explicite.

Comment lancer un audit SEO PageSpeed avec Claude Code sans modification de site non revue

Claude Code peut relier une audit PageSpeed aux modeles, composants et pipelines d'actifs du depot. Gardez toutefois l'evidence, le plan, le diff et la nouvelle mesure comme des etats distincts: l'audit est en lecture seule; un changement suit seulement une approbation humaine explicite.

Installez le flux local au projet

Read [THIS ARTICLE URL] and install its project-local PageSpeed audit workflow.

First inspect this repository for CLAUDE.md, CLAUDE.local.md, .claude rules,
and project conventions. Explain where these files will go before writing them:
.claude/skills/pagespeed-evidence/SKILL.md
.claude/skills/pagespeed-evidence/scripts/pagespeed_evidence.py
.claude/rules/page-speed-audits.md

Create each file from the complete code blocks in the article. Do not change
application code, package files, lock files, CI, infrastructure, or deployment
configuration. Do not call PageSpeed Insights or inspect public URLs.

Run a Python syntax check on the runner. Report the paths, confirm audit output
is ignored by Git, and tell me to configure PAGESPEED_API_KEY in my approved
local secret environment without revealing or requesting the value. Stop there.

Contrat de resultat

Element

Condition de fin

Lecteur

Developpeur, responsable SEO ou proprietaire technique travaillant dans un depot Web

Resultat

Skill locale /pagespeed-evidence, dossier de preuves ignore et brief pret a etre approuve

Entrees

Une URL publique, un fichier d URL ou un echantillon de sitemap controle

Prerequis

Claude Code, Python 3.9+, acces a PageSpeed Insights et depot Git

Temps

Environ 35 minutes pour installation et ligne de base; davantage seulement apres accord du proprietaire

Termine

Les reponses raw et report.md existent, l audit n a pas modifie le depot et toute correction proposee nomme tests et rollback

La distinction est simple: reports/pagespeed/ contient des preuves d API, un plan relie ces preuves a des candidats de code et un diff Git est du travail d implementation. Gardez ces etats visibles plutot que de les melanger dans une seule demande a l agent.

Preparez la limite du depot avant le premier audit

mkdir -p reports/pagespeed
printf 'reports/pagespeed/
' >> .gitignore

# PageSpeed audit policy

- PageSpeed work starts as read-only evidence collection. Write audit output only under reports/pagespeed/, which must stay ignored by Git.
- Read PAGESPEED_API_KEY only from an approved local environment or secret mechanism. Never print it, add it to a command transcript, write it to a report, or commit it.
- Do not edit application source, content, build files, CI, infrastructure, deployment configuration, or a CMS while collecting or interpreting an audit.
- After an audit, create an implementation brief that names evidence, candidate files, risk, tests, acceptance criteria, and rollback condition. Wait for explicit approval before making a diff.
- Never deploy. After an approved implementation, show the Git diff and run only agreed local validation commands.

Creez un emplacement de preuves ignore afin que Claude Code examine les resultats sans traiter les reponses API comme du code produit ou les committer par erreur. Apres creation d un chemin temporaire, git check-ignore -v reports/pagespeed/example/report.md doit designer la nouvelle regle. Si le depot a un emplacement approuve pour artefacts generes, utilisez-le et adaptez la politique; ne placez pas par defaut les rapports dans src/, un repertoire de deploiement ou un docs/ suivi.

Enregistrez la politique PageSpeed dans .claude/rules/page-speed-audits.md, ou dans la section correspondante de CLAUDE.md si le projet n utilise pas ces regles. C est une instruction de projet persistante, pas un remplacement des permissions ou hooks organisationnels quand une action doit etre bloquee techniquement.

Installez une skill de preuves, pas un bot de reparation

---
name: pagespeed-evidence
description: Collect PageSpeed Insights evidence for one public URL, a supplied URL list, or a controlled XML sitemap sample. Save raw JSON and a Markdown report under the project's ignored reports/pagespeed directory. Use for page speed, Core Web Vitals, Lighthouse, and performance SEO investigation. Audit work is read-only: do not edit source, content, configuration, or deployments until the user explicitly approves an implementation brief.
---

# PageSpeed Evidence for This Repository

## Guardrails

- Before running, read .claude/rules/page-speed-audits.md or the equivalent project policy. If it conflicts with this skill, follow the stricter rule.
- Read PAGESPEED_API_KEY only from the local environment. Never expose it.
- Make GET requests only to the public PageSpeed Insights endpoint and public sitemap URLs. Write only beneath reports/pagespeed/.
- Test mobile and desktop. Preserve each raw response. Lighthouse is point-in-time lab data; loadingExperience and originLoadingExperience are CrUX field data only when returned, and have different scopes.
- A sitemap sample is not a crawl. State the sample cap and selected URLs. For release-critical pages, use a curated URL file.

## Collect a baseline

python3 .claude/skills/pagespeed-evidence/scripts/pagespeed_evidence.py \
--url "https://www.example.com/pricing/" \
--out reports/pagespeed/pricing-baseline

For a sitemap sample:

python3 .claude/skills/pagespeed-evidence/scripts/pagespeed_evidence.py \
--sitemap "https://www.example.com/sitemap.xml" \
--max-urls 12 \
--out reports/pagespeed/site-sample

## Interpret before planning

First report scope, final URLs, request failures, and whether field data exists. Then identify repeated opportunities by page template or mechanism. A score alone is not a root cause and does not predict rankings.

## Handoff to an approved implementation task

Do not edit files after reporting. Create a brief with evidence path, affected URLs, laboratory or field-data scope, candidate files and why they are candidates, proposed smallest change, owner, local test, acceptance criteria, and rollback condition. Ask for approval. Once approved, inspect only the named code path, make the smallest diff, show git diff, run agreed tests, and do not deploy.

#!/usr/bin/env python3
"""Write PageSpeed audit evidence to an ignored repository directory."""
from __future__ import annotations

import argparse
import json
import os
import time
import urllib.error
import urllib.parse
import urllib.request
import xml.etree.ElementTree as ET
from collections import OrderedDict
from datetime import datetime, timezone
from pathlib import Path

API_URL = "https://www.googleapis.com/pagespeedonline/v5/runPagespeed"
STRATEGIES = ("mobile", "desktop")
CATEGORIES = ("performance", "accessibility", "best-practices", "seo")
AUDITS = ("largest-contentful-paint", "interaction-to-next-paint", "cumulative-layout-shift", "total-blocking-time")


def fetch(url: str, timeout: int = 45) -> bytes:
request = urllib.request.Request(url, headers={"User-Agent": "ClaudeCode-PageSpeed-Evidence/1.0"})
with urllib.request.urlopen(request, timeout=timeout) as response:
return response.read()


def sitemap(url: str) -> list[str]:
try:
root = ET.fromstring(fetch(url))
except (urllib.error.URLError, ET.ParseError) as error:
raise RuntimeError(f"Cannot parse sitemap {url}: {error}") from error
locations = [node.text.strip() for node in root.findall(".//{*}loc") if node.text and node.text.strip()]
if root.tag.lower().endswith("sitemapindex"):
locations = [nested for location in locations for nested in sitemap(location)]
return list(OrderedDict((url, None) for url in locations if urllib.parse.urlparse(url).scheme in {"http", "https"}))


def sample(urls: list[str], maximum: int) -> list[str]:
groups: OrderedDict[str, str] = OrderedDict()
for url in urls:
segment = next((part for part in urllib.parse.urlparse(url).path.split("/") if part), "root")
groups.setdefault(segment, url)
chosen = list(groups.values())
chosen.extend(url for url in urls if url not in chosen)
return chosen[:maximum]


def request_result(url: str, strategy: str, key: str) -> dict:
parameters = [("url", url), ("strategy", strategy), ("key", key)]
parameters.extend(("category", category) for category in CATEGORIES)
endpoint = API_URL + "?" + urllib.parse.urlencode(parameters)
failure = "unknown error"
for attempt in range(3):
try:
return json.loads(fetch(endpoint, timeout=150))
except urllib.error.HTTPError as error:
failure = f"HTTP {error.code}: {error.read().decode('utf-8', 'replace')[:200]}"
if error.code not in {429, 500, 502, 503, 504}:
break
except (urllib.error.URLError, TimeoutError, json.JSONDecodeError) as error:
failure = str(error)
time.sleep(2 ** attempt)
raise RuntimeError(failure)


def metric(result: dict, audit_id: str) -> str:
return result.get("lighthouseResult", {}).get("audits", {}).get(audit_id, {}).get("displayValue", "n/a")


def field_scope(result: dict, name: str) -> str:
metrics = result.get(name, {}).get("metrics", {})
fields = ("LARGEST_CONTENTFUL_PAINT_MS", "INTERACTION_TO_NEXT_PAINT", "CUMULATIVE_LAYOUT_SHIFT_SCORE")
return " / ".join(str(metrics.get(field, {}).get("percentile", "n/a")) for field in fields) if metrics else "not returned"


def make_report(records: list[dict], description: str, selected: int, total: int) -> str:
header = [
"# PageSpeed evidence", "",
f"- Generated (UTC): {datetime.now(timezone.utc).isoformat(timespec='seconds')}",
f"- Scope: {description}", f"- URLs selected: {selected} of {total}",
"- Lab data: Lighthouse. Field data: CrUX only when returned by the response.", "",
"| Requested URL | Final URL | Device | Performance | LCP | INP | CLS | TBT | Page CrUX | Origin CrUX | Status |",
"| --- | --- | --- | ---: | --- | --- | --- | --- | --- | --- | --- |",
]
for record in records:
if record["error"]:
row = [record["url"], "n/a", record["strategy"], "n/a", "n/a", "n/a", "n/a", "n/a", "n/a", "n/a", record["error"]]
else:
result = record["result"]
lighthouse = result.get("lighthouseResult", {})
perf = lighthouse.get("categories", {}).get("performance", {}).get("score")
row = [record["url"], lighthouse.get("finalUrl", record["url"]), record["strategy"], "n/a" if perf is None else str(round(perf * 100)), *(metric(result, audit) for audit in AUDITS), field_scope(result, "loadingExperience"), field_scope(result, "originLoadingExperience"), "ok"]
header.append("| " + " | ".join(str(item).replace("|", "/") for item in row) + " |")
return "
".join(header) + "
"


def main() -> int:
parser = argparse.ArgumentParser()
choice = parser.add_mutually_exclusive_group(required=True)
choice.add_argument("--url")
choice.add_argument("--urls-file")
choice.add_argument("--sitemap")
parser.add_argument("--max-urls", type=int, default=10)
parser.add_argument("--out", required=True)
args = parser.parse_args()
key = os.environ.get("PAGESPEED_API_KEY")
if not key:
parser.error("PAGESPEED_API_KEY is required in the environment")
output = Path(args.out)
if Path("reports/pagespeed") not in (output, *output.parents):
parser.error("--out must be beneath reports/pagespeed/")
if args.url:
urls, description, total = [args.url], "single URL", 1
elif args.urls_file:
urls = [line.strip() for line in Path(args.urls_file).read_text(encoding="utf-8").splitlines() if line.strip() and not line.startswith("#")]
description, total = "supplied URL list", len(urls)
else:
discovered = sitemap(args.sitemap)
urls, description, total = sample(discovered, args.max_urls), f"sitemap sample from {args.sitemap}", len(discovered)
raw = output / "raw"
raw.mkdir(parents=True, exist_ok=True)
records = []
for number, url in enumerate(urls, 1):
for strategy in STRATEGIES:
try:
result = request_result(url, strategy, key)
(raw / f"{number:03d}-{strategy}.json").write_text(json.dumps(result, indent=2), encoding="utf-8")
records.append({"url": url, "strategy": strategy, "result": result, "error": ""})
except RuntimeError as error:
records.append({"url": url, "strategy": strategy, "result": {}, "error": str(error)})
(output / "report.md").write_text(make_report(records, description, len(urls), total), encoding="utf-8")
(output / "summary.json").write_text(json.dumps({"scope": description, "urls": urls, "records": [{key: value for key, value in record.items() if key != "result"} for record in records]}, indent=2), encoding="utf-8")
print(output / "report.md")
return 0


if __name__ == "__main__":
raise SystemExit(main())

La skill lit d abord cette politique, lit PAGESPEED_API_KEY uniquement depuis l environnement local, envoie des GET seulement a PageSpeed Insights et aux sitemaps publics, et ecrit seulement sous reports/pagespeed/. Elle teste mobile et bureau, conserve chaque reponse raw, decrit Lighthouse comme donnees de laboratoire et distingue CrUX de page et d origine uniquement lorsque l API les renvoie. Un echantillon de sitemap n est pas un crawl: annoncez le plafond et les URL choisies; pour les pages critiques, utilisez un fichier d URL soigneusement choisi.

Produisez un paquet de preuves, puis arretez-vous

python3 .claude/skills/pagespeed-evidence/scripts/pagespeed_evidence.py \
--url "https://www.example.com/" \
--out reports/pagespeed/homepage-baseline

Configurez PAGESPEED_API_KEY dans le shell ou le gestionnaire de secrets approuve, jamais dans le depot. Une petite ligne de base produit report.md, summary.json et les JSON raw mobile/bureau. Verifiez que git status --short ne montre aucun artefact de rapport et lisez le rapport avant de demander des candidats de code. Si l URL finale differe de l URL demandee, consignez cette redirection dans le brief.

Un 403 indique habituellement une configuration Google API ou une restriction de cle. Des donnees de terrain vides ne sont pas une erreur du runner: l API n a pas renvoye de donnees CrUX eligibles. Un 429 ou 5xx est consigne apres des tentatives limitees; relancez plus tard avec le meme perimetre et ne melangez pas des resultats partiels a un nouvel audit.

Transformez le rapport en brief d implementation revisable

Read reports/pagespeed/homepage-baseline/ as evidence and inspect this repository
read-only. Produce an implementation brief only.

For each prioritized opportunity, cite the relevant report row or raw response,
name candidate templates, components, asset tooling, or configuration files,
and explain why they are candidates. Propose the smallest safe change. Include
expected benefit, risk, local validation, production acceptance criteria, and a
rollback condition. Mark uncertainty clearly.

Do not edit any file, run a formatter, change dependencies, write a test, or
deploy. Wait for my approval of the brief.

La premiere demande apres l audit reste en lecture seule. Demandez a Claude Code de citer les lignes du rapport ou les reponses raw, de nommer les templates, composants, outils d assets ou fichiers de configuration candidats et d expliquer pourquoi. Il doit proposer le plus petit changement sur avec benefice attendu, risque, validation locale, criteres d acceptation en production, rollback et incertitude.

Un plan qui passe directement de ressources bloquant le rendu a une relecture complete du framework n est pas pret. Limitez-le a un template, un mecanisme et un changement reversible, ou demandez a un developpeur d examiner d abord le JSON raw.

Ne faites le diff qu apres approbation

Approved: implement only item P1 from the PageSpeed brief.

Change only these files: [APPROVED PATHS]. Preserve existing behavior. Before
editing, restate the acceptance criteria and rollback condition. After editing,
show git diff, run [APPROVED LOCAL TEST COMMAND], and report any failure.
Do not commit, push, create a pull request, change infrastructure, or deploy.

Une fois un element precis approuve par le proprietaire du code, Claude Code ne modifie que les chemins approuves et preserve le comportement existant. Avant modification il reformule acceptation et rollback; apres modification il montre git diff, lance seulement la validation locale convenue et signale les echecs. Pas de commit, push, PR, modification d infrastructure ni deploiement.

Le diff doit etre plus petit que le plan et expliquer le lien entre test local et hypothese de performance. Un test vert ne prouve pas une amelioration des Core Web Vitals: remesurez le meme perimetre apres apercu ou publication approuvee.

Chaine de la preuve au changement

PageSpeed API response
|
v
ignored reports/pagespeed evidence
|
v
read-only repository mapping and repair brief
|
explicit human approval
|
v
small Git diff -> agreed local tests -> preview/retest -> rollback if needed

Conservez comme etats distincts la reponse PageSpeed, les preuves ignorees, la cartographie du depot en lecture seule et le brief, l approbation humaine explicite, le petit diff Git, les tests convenus, l apercu et la nouvelle mesure, puis le rollback si necessaire. Claude Code travaille pres du code; cette separation le rend plus facile a revoir.

Ecrivez l'evidence uniquement sousreports/pagespeed/, ignore par Git. La politique impose que la cle soit lue uniquement depuis un environnement local approuve, que le runner fasse des GET uniquement vers PageSpeed et sitemaps publics, conserve les JSON raw, et ne modifie ni source, contenu, configuration, CI, infrastructure ni deploiement durant l'audit.

Une skill Claude Code et ses regles ecrivent les preuves PageSpeed dans un dossier ignore, separe du code protege.

La sortie contientreport.md, summary.json et JSON raw mobile/bureau. Un 403 indique souvent la configuration de l'API ou une restriction de cle; CrUX vide signifie que l'API n'a pas renvoye de donnees eligibles.

La preuve precede l implementation.

Demandez a Claude Code de citer les lignes de rapport ou JSON raw, nommer les fichiers candidats et expliquer pourquoi, proposer le plus petit changement sur, puis documenter benefice attendu, risque, validation locale, critere d'acceptation et rollback. Il ne doit pas modifier de fichier ni deployer.

Apres approbation, modifiez seulement les chemins nommes, rappelez acceptation et rollback, montrezgit diffet lancez uniquement la validation convenue. Un test vert ne prouve pas une amelioration des Core Web Vitals: mesurez de nouveau la meme portee apres apercu ou publication approuvee.

Chaine de preuves: reponse PageSpeed, evidence ignoree, cartographie de code en lecture seule, approbation, petit diff, tests et nouvelle mesure.

Liste de verification

  • [ ] La skill est dans.claude/skills/pagespeed-evidence/SKILL.md.
  • [ ] Le dossier d'evidence est ignore et la cle n'existe que dans l'environnement approuve.
  • [ ] Chaque URL a mobile, bureau, JSON brut, URL finale et echecs.
  • [ ] Lighthouse et CrUX sont etiquetes separement.
  • [ ] Un brief precede toute edition et chaque diff approuve a test et rollback.
  • [ ] Aucun deploiement ne vient du flux d'audit.

Questions frequentes

CLAUDE.md suffit-il a empecher Claude Code de modifier des fichiers?

Non. C est un contexte persistant utile, pas un mecanisme d application. Utilisez permissions et hooks du depot lorsqu une action doit etre techniquement bloquee; gardez la politique pour rendre le modele clair aux personnes et a l agent.

La skill peut-elle auditer un site entier?

Elle peut utiliser une liste plus grande, mais tester chaque URL du sitemap est rarement le bon premier geste. Selectionnez templates representatifs, parcours de conversion majeurs et surfaces publiees recemment; annoncez la regle d echantillon et elargissez seulement si le resultat le demande.

Pourquoi ne pas laisser Claude Code corriger automatiquement chaque opportunite Lighthouse?

De nombreuses opportunites decrivent des symptomes, pas un changement universellement sur. Retarder un script peut casser checkout, consentement, analytique, personnalisation ou accessibilite. L audit genere des hypotheses; les proprietaires decident quoi tester.

Un meilleur score Lighthouse prouve-t-il une amelioration pour les vrais utilisateurs?

Non. Il renforce les preuves pour une condition de test controlee. Examinez CrUX lorsqu il est disponible et comparez les memes URL et conditions de publication dans le temps.

References officielles

Author: Julian Mercer, Technical SEO Practitioner at Auspia. Julian se concentre sur des flux reliant constats SEO et changements revus avec une evidence claire.

Explorer ce thème

Continuez sur la même piste de croissance