Building a Website Audit Platform with Next.js, Playwright, Lighthouse, and axe-core
Learn how to build a production website audit platform with Next.js, Playwright, Lighthouse, axe-core, SEO checks, and accessibility reports.
Building a Website Audit Platform with Next.js, Playwright, Lighthouse, and axe-core
Website audits become more useful when they are fast, repeatable, and written for people who need to make decisions. That was the idea behind AuditWave, a production-grade website audit platform I built with Next.js, React, TypeScript, Playwright, Lighthouse, axe-core, Tailwind CSS, and Vercel.
The goal was simple: enter a URL, run a real technical audit, and get a clear report covering performance, accessibility, SEO, responsive behavior, and detected technologies. No login wall. No vague score without context. Just practical diagnostics in seconds.
This guide breaks down the architecture I use for building a website audit platform in Next.js.
The Core Product Flow
A useful audit platform has four jobs:
- Validate and normalize the target URL.
- Run automated browser checks in a controlled environment.
- Convert raw tool output into understandable report sections.
- Return results quickly without blocking the UI.
For AuditWave, that meant combining three layers:
| Layer | Tooling | Responsibility |
|---|---|---|
| App layer | Next.js, React, TypeScript | UI, API routes, report rendering |
| Browser automation | Playwright | Multi-device page checks and screenshots |
| Audit engines | Lighthouse, axe-core | Performance, SEO, accessibility insights |
The important part is orchestration. Lighthouse and axe-core are powerful, but raw output is not a product experience. The value is in turning noisy data into a prioritized action list.
URL Validation Comes First
Never pass user input straight into a browser automation job. Normalize the URL and reject unsupported protocols.
export function normalizeAuditUrl(input: string): string {
const trimmed = input.trim();
const withProtocol = /^https?:\/\//i.test(trimmed)
? trimmed
: `https://${trimmed}`;
const url = new URL(withProtocol);
if (!["http:", "https:"].includes(url.protocol)) {
throw new Error("Only HTTP and HTTPS URLs are supported.");
}
url.hash = "";
return url.toString();
}
This protects the audit worker and improves user experience. If someone enters example.com, the app should understand that they probably mean https://example.com.
Design the API Around Jobs
Small audits can run inside a single API request, but production systems should still be modeled as jobs. That makes it easier to add queues, retries, cached reports, or background processing later.
type AuditStatus = "queued" | "running" | "complete" | "failed";
interface AuditJob {
id: string;
targetUrl: string;
status: AuditStatus;
createdAt: string;
completedAt?: string;
report?: AuditReport;
error?: string;
}
Even if your first implementation is serverless, the mental model matters. The UI can show progress states, the API can stay consistent, and the platform has room to grow.
Running Multi-Device Checks with Playwright
Playwright is excellent for checking how a site behaves across viewport sizes. For an audit product, I normally start with desktop and mobile profiles.
import { chromium, devices } from "playwright";
const profiles = [
{ name: "desktop", viewport: { width: 1440, height: 900 } },
{ name: "mobile", ...devices["iPhone 13"] },
];
export async function runDeviceChecks(url: string) {
const browser = await chromium.launch({ headless: true });
try {
const results = [];
for (const profile of profiles) {
const context = await browser.newContext(profile);
const page = await context.newPage();
const startedAt = performance.now();
const response = await page.goto(url, {
waitUntil: "networkidle",
timeout: 30000,
});
results.push({
profile: profile.name,
status: response?.status() ?? null,
title: await page.title(),
loadTimeMs: Math.round(performance.now() - startedAt),
hasHorizontalScroll: await page.evaluate(
() => document.documentElement.scrollWidth > window.innerWidth
),
});
await context.close();
}
return results;
} finally {
await browser.close();
}
}
This catches issues Lighthouse may not explain clearly: broken mobile layouts, slow page boot, missing titles, failed responses, and horizontal overflow.
Accessibility Audits with axe-core
Performance is only part of quality. Accessibility issues directly affect usability and often overlap with SEO and conversion problems.
import AxeBuilder from "@axe-core/playwright";
export async function runAccessibilityAudit(page: Page) {
const results = await new AxeBuilder({ page })
.withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"])
.analyze();
return results.violations.map((violation) => ({
id: violation.id,
impact: violation.impact,
description: violation.description,
helpUrl: violation.helpUrl,
nodes: violation.nodes.slice(0, 5).map((node) => ({
target: node.target,
summary: node.failureSummary,
})),
}));
}
The product decision here is to summarize. A raw accessibility report can overwhelm non-technical users. Show severity, explain the impact, and keep examples short.
Lighthouse for Performance and SEO Signals
Lighthouse is strongest when used as one input, not the entire product. I use it for Core Web Vitals signals, SEO diagnostics, best practices, and performance opportunities.
Useful report fields include:
- Performance score
- Accessibility score
- SEO score
- First Contentful Paint
- Largest Contentful Paint
- Cumulative Layout Shift
- Total Blocking Time
- Unused JavaScript
- Image optimization opportunities
For a user-facing report, the key is priority. A good audit does not say "you have 37 issues." It says "fix these 5 things first because they affect load speed, discoverability, or accessibility."
Report Shape That Works in the UI
The report data should be shaped for rendering, not just storage.
interface AuditReport {
url: string;
summary: {
score: number;
performance: number;
accessibility: number;
seo: number;
bestPractices: number;
};
checks: {
title: string;
status: "pass" | "warning" | "fail";
category: "performance" | "accessibility" | "seo" | "technical";
description: string;
recommendation: string;
}[];
devices: {
profile: string;
loadTimeMs: number;
hasHorizontalScroll: boolean;
}[];
}
This makes the frontend clean. Cards, filters, severity badges, and export actions can all work from one consistent structure.
Performance Lessons from Building AuditWave
Audit tools are resource-heavy, so the platform itself needs strict performance boundaries.
The patterns that helped most:
- Keep the landing page static and lightweight.
- Run audit work behind API boundaries.
- Limit concurrent browser jobs.
- Set hard timeouts for navigation and analysis.
- Cache repeated URL reports when possible.
- Return partial results instead of failing the whole audit when one check breaks.
Serverless hosting works well when jobs are short and bounded. Once audits become longer or require scheduled monitoring, move execution into a queue-backed worker.
SEO Strategy for an Audit Platform
The SEO opportunity is strong because the product naturally maps to search intent.
Target pages and keywords can include:
- Website audit tool
- SEO audit tool
- Accessibility audit tool
- Core Web Vitals checker
- Lighthouse report generator
- Next.js performance audit
But the product page should not stuff keywords. Use clear sections:
- What the tool checks
- Why each check matters
- Example report output
- Common fixes
- FAQ for long-tail questions
Search engines reward pages that satisfy intent. Users reward pages that help them act.
Common Mistakes to Avoid
The biggest mistake is treating audit scores as the product. Scores are useful, but users need context.
Avoid these traps:
- Showing raw Lighthouse JSON.
- Hiding the most important recommendations under dozens of minor warnings.
- Running browser jobs without timeouts.
- Ignoring mobile viewport checks.
- Treating accessibility as optional.
- Reporting issues without showing how to fix them.
The best audit tools are opinionated. They help users understand what matters now.
Related Reading
If you are building something similar, these posts connect well:
Final Thoughts
Building a website audit platform with Next.js is not just about wiring together Playwright, Lighthouse, and axe-core. The real product work is in reliability, prioritization, and presentation.
For AuditWave, the winning formula was a fast Next.js interface, bounded server-side audit jobs, multi-device checks, and reports that translate technical findings into practical next steps. That is what turns an audit from a score into a useful workflow.