Auditool audits a website's health — performance, accessibility, SEO, security, real-user experience and carbon footprint — from a single URL, showing a per-axis score alongside an aggregated global score.
- Performance, accessibility and SEO audits via the Google PageSpeed Insights API (hosted Lighthouse engine)
- Security audit via the Mozilla HTTP Observatory API (HTTP headers, HSTS, CSP…)
- Real-user experience card from Chrome UX Report field data
- Carbon footprint estimate (Sustainable Web Design model) with a green-hosting check
- Aggregated global score across all axes
- Streamed results: each audit source renders as soon as it's ready, independently of the others
- Per-axis detail panel: click a card to open a drawer with the full checks list and an explanation for each one, instead of just the 3 metrics shown on the card
- Light / dark theme with persistence and no flash on load
- Per-axis sub-metrics (LCP, CLS, contrast, indexability, etc.)
- Next.js 16 (App Router, Server Components, streaming)
- TypeScript
- Tailwind CSS v4
- APIs: Google PageSpeed Insights, Mozilla HTTP Observatory, The Green Web Foundation
- Deployed on Vercel
A few deliberate architectural choices, beyond just "making it work":
Hosted APIs instead of running Lighthouse locally. Running Lighthouse yourself requires a headless Chromium, which fits poorly with serverless constraints (function size, cold starts, timeouts). Delegating to the PageSpeed and Observatory APIs allows a clean Vercel deployment with no browser to manage.
Streaming per data source. Each audit source (PageSpeed, Observatory) has its own <Suspense> boundary. Both requests start in parallel, and each block of results appears as soon as its source responds, without waiting for the slowest one. Streaming granularity follows the data sources, not the visual components.
Strict server / client boundary. API calls and keys live exclusively on the server (Server Components). Only genuinely interactive leaves (form, theme toggle, buttons) run on the client, kept to the smallest possible surface.
API knowledge kept contained. The shape of external responses (Lighthouse's internal paths, Observatory's format) is isolated in src/lib/: components never handle an API's raw data, only a normalized internal format. A provider-side change touches a single file.
Request deduplication. Data getters are memoized (React.cache) so that the same audit requested by several components within one render triggers only a single network call.
flowchart TB
subgraph ext["External APIs"]
direction LR
PSI["Google PageSpeed\nInsights"]
OBS["Mozilla HTTP\nObservatory"]
GWF["Green Web\nFoundation"]
end
subgraph lib["src/lib — API knowledge confined here"]
direction LR
psi["psi.ts\nextractors + checks"]
obs["obs.ts\nextractors + checks"]
gwf["gwf.ts"]
carbon["carbon.ts\nSustainable Web\nDesign model"]
score["score.ts\nBand / LABELS / COLORS"]
end
PSI --> psi
OBS --> obs
GWF --> gwf
gwf -.-> carbon
psi -.-> carbon
subgraph server["Server Components — one per data source, own Suspense boundary"]
direction LR
PsiAudit["PsiAudit.tsx\nPerf / SEO / A11y\n+ Complementary signals"]
ObsAudit["ObservatoryAudit.tsx\nSecurity"]
HeaderGlobal["HeaderGlobal.tsx\nglobal score"]
DetailPanel["DetailPanel.tsx\nper-axis drawer"]
end
psi --> PsiAudit
obs --> ObsAudit
carbon --> PsiAudit
psi --> HeaderGlobal
obs --> HeaderGlobal
psi --> DetailPanel
obs --> DetailPanel
carbon --> DetailPanel
subgraph page["page.tsx — Suspense orchestration"]
direction LR
Page["HomeContent\nreads ?url and ?panel"]
end
PsiAudit --> Page
ObsAudit --> Page
HeaderGlobal --> Page
DetailPanel --> Page
subgraph client["Client leaves — smallest possible surface"]
direction LR
UrlForm["UrlForm"]
ThemeToggle["ThemeToggle"]
EscClose["EscClose"]
end
Page -.->|"form submit → ?url="| UrlForm
Page -.->|"panel trigger → Link ?panel="| DetailPanel
DetailPanel -.-> EscClose
src/lib/is the only place that knows an external API's raw shape. Every fetcher there normalizes into the app's own vocabulary (Band,Check, 0–1 scores) before anything else sees it — a provider-side change touches one file.- Server Components fetch and render — no client-side data fetching anywhere.
React.cache()deduplicates a source's fetch across the components that need it in the same render (e.g.PsiAudit.tsxandDetailPanel.tsxboth callgetPsi()without doubling the request). - The detail panel opens via a URL search param (
?panel=performance), not client state — it's its own Server Component readingsearchParams, so opening it costs one more deduplicated fetch, not a client round-trip. - Client components are leaves, not containers: the form input, the theme toggle, and the panel's Escape-key listener are the only three — everything else, including every score card and the panel itself, stays server-rendered.
# 1. Clone the repository
git clone <!-- your repo url --> && cd auditool
# 2. Install dependencies
npm install
# 3. Set up environment variables
cp .env.example .env.local
# then fill in PAGESPEED_API_KEY in .env.local
# 4. Start the development server
npm run devOpen http://localhost:3000.
A free PageSpeed API key is available from the Google Cloud Console (enable "PageSpeed Insights API", then create a key). The Observatory API requires no key.
- "Real-user experience" card (CrUX field data)
- Carbon footprint (Sustainable Web Design Model v4 + green hosting check)
- Cross-request result caching, with on-demand invalidation
- Per-source error boundaries
- "Signaux complémentaires" aggregate — real-user experience and carbon footprint, grouped apart from the per-axis score cards with no ring and no /100, since neither belongs in the global average
- "Social preview" card — Open Graph and X card tags, rendered as the link preview a visitor would actually see
- "AI visibility" card — AI crawler rules in
robots.txt,llms.txt, structured data - AI-generated explanations of results (global score and per-signal)
- Privacy detection (trackers, cookies) — requires a headless worker
- Per-axis detail panel — a right-side drawer, driven by a
panelURL search param, showing a larger score hero and the full checks list for the clicked axis instead of the 3 metrics shown on its card - Per-check detail for the Security panel — Mozilla Observatory v2 only
returns an aggregate grade (verified: no per-check JSON endpoint,
details_urlpoints to a web UI, not an API). securityheaders.com's API is being retired (April 2026). Guardr's free tier caps at 3 findings. The only independent path found is self-hosting the@mdn/mdn-http-observatorynpm package (the scan engine itself) to compute per-header results locally instead of calling a hosted API — untried, needs evaluation before committing to it - Internationalization (English UI)
- Test coverage — currently none. Unit tests for
src/lib/extractors (psi.ts,obs.ts,carbon.ts,score.ts) since they hold all the normalization/scoring logic; E2E (Playwright) for the audit flow (submit a URL, streamed cards resolve, detail panel opens/closes); visual regression if feasible, given the score rings and color-band styling are easy to break silently
The two upcoming cards share a source Auditool does not use yet: the audited
page itself. Both need plain fetch and parsing, no browser — unlike privacy
detection.
Built as part of an in-depth study of modern Next.js (App Router) and a French RNCP level-7 software engineering certification.
