Fast, explainable multi‑pillar website quality snapshots
Performance • Accessibility • SEO • Security • UX
- Overview
- Why Another Auditor?
- Feature Matrix
- Quick Start
- Scoring Model
- Architecture Flow
- Data & Report Schema
- API Reference
- Testing & Coverage
- Deployment Notes
- Roadmap
- Troubleshooting
- Contributing
- Credits
- License
SitePulse produces a concise, weighted multi‑pillar score profile and only lists issues that actually impacted the numbers—every deduction is explainable. Logic executes server‑side directly on raw HTML (fast mode). No DB; ephemeral in‑memory cache.
Many tools are (a) heavyweight & slow (multi‑pass headless) or (b) shallow lists without weighting context. SitePulse optimizes for:
- Near‑instant feedback (<~1s typical)
- Transparent scoring inputs & weights
- Diff‑friendly JSON for CI / regression tracking
- Minimal accessible UI (dark by default, respects reduced‑motion)
Core:
- URL normalization & safety guardrails (blocks local/private/invalid schemes)
- HTML fetch with TTFB capture, size & content‑type enforcement (2 MB cap)
- Structured metric extraction (headings, meta, links, alt %, font-display, security headers, canonical, robots, language, viewport, mixed content)
- Weighted pillar scoring + composite overall
- Rule engine with estimated remediation gain per issue
- Diff view (added / resolved / unchanged issues + per‑pillar deltas)
- Ephemeral cached reports (~10 min TTL)
- Token bucket rate limiting (in‑memory)
- Soft optimization tier (low severity signals so every score delta has a label)
- Vector brand icon
app/icon.svg
UX / Visual:
- Heartbeat ambient theme (bg, gauge, glow, border, card, text breathing)
- Progressive issue reveal & severity accents
- Focus-visible + reduced-motion accessibility compliance
Integration / Ecosystem:
- Companion CSP generator (AutoCSP) for security hardening (separate package)
git clone <repo-url>
cd sitepulse
npm install
npm run devVisit: http://localhost:3000
Run an audit from the landing form or call the API directly:
curl -X POST http://localhost:3000/api/audit -H "Content-Type: application/json" -d '{"url":"https://example.com"}'Each pillar starts at 100 and deductions are applied per rule. Overall score is a weighted mean (defaults):
performance : 0.22
accessibility: 0.20
seo : 0.18
security : 0.25
ux : 0.15
Weights live alongside constants in lib/compute-scores.ts and can be tuned. Rules carry a max deduction; optimization (soft) issues cap at small values so they never dominate real defects. Security has a baseline floor uplift when HTTPS & core headers are present.
interface Issue {
id: string; // stable identifier
pillar: 'performance'|'accessibility'|'seo'|'security'|'ux';
severity: 'low'|'medium'|'high';
message: string; // human readable summary
impact: number; // deduction applied (or potential gain if resolved)
hints?: string[]; // optional remediation suggestions
}Client → /api/audit
1. normalize url (lib/normalize.ts or inline logic)
2. rate limit (lib/rate-limit.ts)
3. fetch HTML (lib/fetch-html.ts)
4. extract raw metrics (lib/extract-metrics.ts)
5. aggregate/normalize (lib/aggregate-metrics.ts)
6. compute scores (lib/compute-scores.ts)
7. derive issues (lib/derive-issues.ts)
8. diff (optional) (lib/diff.ts)
9. cache + respond (in-memory store)
All stateless; swap cache + rate limit layers for Redis/KV to scale horizontally.
interface Report {
id: string;
url: string;
pageTitle: string;
fetchedAt: string;
overall: number;
scores: Record<'performance'|'accessibility'|'seo'|'security'|'ux', number>;
issues: Issue[];
previousId?: string;
diff?: {
scores: Record<string, { previous: number; current: number; delta: number }>;
issues: { added: Issue[]; resolved: Issue[]; unchanged: Issue[] };
}
}Request:
{ "url": "https://example.com", "previousId": "optional-report-id" }Success:
{ "id": "...", "overall": 78, "scores": { "performance": 64, "accessibility": 60, "seo": 70, "security": 80, "ux": 75 }, "issues": [ { "id": "ttl-short", "pillar": "seo", "severity": "low", "impact": 2, "message": "Title length could be improved" } ] }Errors:
{ "error": { "code": "TIMEOUT", "message": "Fetch exceeded limit", "hint": "Try a smaller page" } }Response (404 if expired): cached Report JSON.
Lightweight harness (no Jest/Vitest) for speed:
tests/*.test.tsuse Nodeasserttests/run-all.tsauto-discoversnpm testruns viatsx
Coverage:
npm run coverage # HTML + text + lcov in coverage/
npm run coverage:cleanAdd a test:
import assert from 'node:assert';
import { computePillarDeltas } from '../lib/diff';
assert.deepStrictEqual(/* ... */);- Replace user agent token in
fetch-html.tsif branding. - Single-instance cache & rate limiting; externalize for multi-region.
- Tune weighting & baseline constants in
compute-scores.ts. - CSP & security headers set in
middleware.ts– adjust if adding external assets. - Mixed content detection is static (HTML only, no JS exec).
- Memory pressure: set
NODE_OPTIONS=--max_old_space_size=512as needed. - Replace
app/icon.svgfor custom branding.
- External resource weight HEAD probes
- Optional headless render phase
- Historical trend storage + auth
- Expanded security heuristics (HSTS, SRI, cookie flags)
- Smarter remediation grouping & hints
- Optional Lighthouse hybrid mode
| Symptom | Likely Cause | Suggested Fix |
|---|---|---|
| Security score low | Missing headers | Add CSP, X-Frame-Options, Referrer-Policy, Permissions-Policy |
| NON_HTML error | Non-HTML response | Provide a standard HTML URL |
| TIMEOUT | Slow origin / blocked | Retry, verify via curl, reduce payload |
| HTTP_ERROR 404 | Page missing | Use a public, accessible URL |
| Mixed content flagged | HTTP assets on HTTPS page | Upgrade resource URLs |
Contributions welcome – keep PRs focused and add/adjust a test when changing logic.
Guidelines:
- One concern per PR
- Prefer pure, deterministic functions in
lib/ - Maintain or improve coverage when feasible
Created by the project author. Design collaboration & inspiration: Habbi Web Design – minimal dark aesthetic & interaction refinements.
MIT – see LICENSE.
Planned OG image: pillar score gauges left + issues delta diff right. Provide /public/og.png or dynamic route.
Questions or ideas? Open an issue or start a discussion. 🚀