| name | audit |
| description | SEO audit checklist and automated testing for Next.js sites. This skill should be used when the user asks to "audit SEO", "check SEO", "SEO checklist", "Lighthouse SEO", "SEO test", "automated SEO testing", "SEO report", or wants to systematically evaluate their site's SEO health and identify issues. |
SEO Audit
Systematic checklist and automated testing patterns for evaluating a Next.js site's SEO health.
Quick Audit Commands
Run these to get an instant snapshot of SEO status:
grep -r "export const metadata\|export async function generateMetadata" src/app --include="*.tsx" --include="*.ts" -l
find src/app -name "page.tsx" | while read f; do
grep -qE "metadata|generateMetadata" "$f" || echo "MISSING metadata: $f"
done
ls src/app/sitemap.ts src/app/robots.ts 2>/dev/null || echo "Missing sitemap.ts or robots.ts"
grep -r "metadataBase" src/app/layout.tsx || echo "NO metadataBase set"
grep -rn "<img\|<Image" src/ --include="*.tsx" | grep -v "alt=" | head -20
find src/app -name "page.tsx" | while read f; do
grep -qE "<h1|<H1" "$f" || echo "NO H1: $f"
done
grep -r "application/ld+json" src/ --include="*.tsx" -l
Full SEO Audit Checklist
1. Crawlability & Indexability
| Check | How to Verify | Priority |
|---|
robots.ts exists and is correct | Visit /robots.txt | Critical |
sitemap.ts exists with all pages | Visit /sitemap.xml, count URLs | Critical |
| Sitemap submitted to Google Search Console | GSC → Sitemaps | Critical |
| No broken internal links (404s) | Run crawler or Lighthouse | High |
| No redirect chains (A→B→C) | Check next.config.ts redirects | High |
| No orphan pages | Verify every page has inbound links | Medium |
| Clean URL structure (lowercase, hyphens) | Review route structure | Medium |
| Staging blocked from indexing | Check robots.ts non-production rules | Critical |
2. Metadata & Social
| Check | How to Verify | Priority |
|---|
metadataBase set in root layout | Grep for metadataBase | Critical |
| Title template configured | Check root layout title.template | Critical |
Every page has unique title | Grep for metadata exports | Critical |
Every page has unique description | Check metadata exports | Critical |
description length 150-160 chars | Measure each page | High |
title length 50-60 chars | Measure each page | High |
| Canonical URL on every page | Check alternates.canonical | Critical |
| Open Graph tags present | View source, check og: tags | High |
| OG image configured (1200x630) | Test with Facebook Debugger | High |
| Twitter Card configured | Test with Twitter Card Validator | Medium |
3. Structured Data
| Check | How to Verify | Priority |
|---|
| Organization schema on root layout | Check for JSON-LD in layout | High |
| BreadcrumbList on inner pages | Check for breadcrumb JSON-LD | Medium |
| Article schema on blog posts | Check blog page JSON-LD | High (if blog) |
| Product schema on product pages | Check product page JSON-LD | High (if e-commerce) |
| No FAQPage on non-gov/health site | Grep for FAQPage schema | High |
| Schema validates in Rich Results Test | Test live URLs | Critical |
schema-dts installed for type safety | Check package.json | Medium |
4. Performance
| Check | How to Verify | Priority |
|---|
| LCP < 2.5s | PageSpeed Insights | Critical |
| INP < 200ms | PageSpeed Insights (field data) | Critical |
| CLS < 0.1 | PageSpeed Insights | Critical |
LCP image has priority prop | Check hero/banner Image component | High |
All images have sizes prop | Grep for <Image without sizes | High |
All images have width/height or fill | Grep for missing dimensions | High |
next/font used (no external font requests) | Check network tab, layout.tsx | High |
| AVIF/WebP enabled | Check next.config.ts images.formats | Medium |
| No render-blocking third-party scripts | Check <Script> strategy props | Medium |
5. Content
| Check | How to Verify | Priority |
|---|
| Single H1 per page | Grep for <h1 in pages | Critical |
| Logical heading hierarchy (no skips) | Audit heading levels per page | High |
| All images have alt text | Grep for <Image without alt | Critical |
| Alt text is descriptive (not "image") | Manual review | High |
| Descriptive internal link text | No "click here" anchors | Medium |
| Breadcrumb navigation present | Check inner pages | Medium |
6. Security & Mobile
| Check | How to Verify | Priority |
|---|
| HTTPS everywhere | Check production URL | Critical |
| Security headers configured | Check response headers | Medium |
| Mobile responsive | Test with Chrome DevTools | Critical |
| Touch targets >= 48px | Lighthouse accessibility audit | High |
| No horizontal scroll on mobile | Manual check | High |
| Viewport meta tag present | View source | Critical |
Automated Testing with Playwright
Create an SEO test file that validates critical SEO elements:
import { test, expect } from '@playwright/test'
const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'
test.describe('SEO Fundamentals', () => {
test('homepage has correct meta tags', async ({ page }) => {
await page.goto(BASE_URL)
const title = await page.title()
expect(title).toBeTruthy()
expect(title.length).toBeLessThanOrEqual(60)
const description = page.locator('meta[name="description"]')
await expect(description).toHaveAttribute('content', /.{50,160}/)
const canonical = page.locator('link[rel="canonical"]')
await expect(canonical).toHaveAttribute('href', /https?:\/\//)
await (page.()).(, )
(page.()).(, )
(page.()).(, )
})
(, ({ page }) => {
pages = [, , ]
( path pages) {
page.()
h1Count = page.().()
(h1Count, ).()
}
})
(, ({ page }) => {
page.()
images = page.()
count = images.()
( i = ; i < count; i++) {
alt = images.(i).()
(alt, )..()
}
})
(, ({ request }) => {
response = request.()
(response.()).()
body = response.()
(body).()
(body).()
(body).()
})
(, ({ request }) => {
response = request.()
(response.()).()
body = response.()
(body).()
(body).()
})
(, ({ page }) => {
page.()
scripts = page.()
count = scripts.()
(count).()
( i = ; i < count; i++) {
content = scripts.(i).()
( .(content!), )..()
parsed = .(content!)
(parsed[]).()
}
})
})
Lighthouse CI Integration
Add Lighthouse CI to your GitHub Actions:
name: Lighthouse CI
on: [pull_request]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci && npm run build
- uses: treosh/lighthouse-ci-action@v12
with:
urls: |
http://localhost:3000/
http://localhost:3000/blog
budgetPath: ./lighthouse-budget.json
uploadArtifacts: true
Lighthouse budget file:
[{
"path": "/*",
"timings": [
{ "metric": "largest-contentful-paint", "budget": 2500 },
{ "metric": "cumulative-layout-shift", "budget": 0.1 },
{ "metric": "total-blocking-time", "budget": 200 }
],
"resourceCounts": [
{ "resourceType": "script", "budget": 10 },
{ "resourceType": "total",
Full-Site Scan with Unlighthouse
For scanning every page on your site at once:
npx unlighthouse --site https://yourdomain.com
Unlighthouse crawls your entire site and runs Lighthouse on each page, producing a dashboard with aggregated results.
Audit Report Template
After running the audit, present results in this format:
## SEO Audit Report — [Site Name] — [Date]
### Critical Issues (fix immediately)
- [ ] Issue description — affected pages — recommended fix
### High Priority (fix this sprint)
- [ ] Issue description — affected pages — recommended fix
### Medium Priority (plan for next sprint)
- [ ] Issue description — affected pages — recommended fix
### Passing
- [x] Item that passed verification