---
name: seo-audit
description: Run a full SEO/performance/accessibility audit against any deployed URL using Lighthouse, surface the failures with actionable fixes, apply them directly in source, then re-audit to verify improvement. INVOKE THIS SKILL whenever the user mentions SEO, Lighthouse, Core Web Vitals, page speed, accessibility audit, robots.txt, render-blocking resources, heading hierarchy, meta tags, ARIA labels, structured data, or wants to improve a site's search ranking or audit score.
---

# SEO Audit & Fix Loop

This skill drives the full Lighthouse audit → fix → verify cycle for any site deployed on Treehouse or accessible via a public URL.

## The Loop

```
audit → parse failures → fix source → redeploy → re-audit → repeat
```

Keep iterating until all target categories score ≥ 90, or the user is satisfied.

## Step 1: Run the Audit

```bash
npx --yes lighthouse <URL> \
  --only-categories=seo,performance,accessibility,best-practices \
  --output=json \
  --output-path=/tmp/lighthouse-report.json \
  --chrome-flags="--headless --no-sandbox --disable-gpu" \
  --quiet
```

Then extract the scores and failures:

```bash
node -e "
const r = JSON.parse(require('fs').readFileSync('/tmp/lighthouse-report.json'));
const cats = r.categories;
console.log('SCORES:');
Object.entries(cats).forEach(([k,v]) => console.log(' ', k, ':', Math.round(v.score*100)));
console.log('\nFAILURES:');
Object.values(r.audits)
  .filter(a => a.score !== null && a.score < 1 && a.details)
  .sort((a,b) => (a.score??1)-(b.score??1))
  .slice(0,20)
  .forEach(a => console.log(' [' + Math.round((a.score??0)*100) + '] ' + a.id + ': ' + a.title));
"
```

## Step 2: Understand What You're Fixing

Before touching files, confirm the app's source directory:

```bash
tc hosting info         # lists deployed apps and their source paths
ls /workspace/apps/     # find the app directory
```

For static sites the fix goes directly in `/workspace/apps/<app>/`. For SSR/Node apps, find the template or build output that generates the HTML.

## Step 3: Apply Fixes

Work through failures from lowest score to highest. Common patterns:

### SEO

| Audit ID | Fix |
|----------|-----|
| `document-title` | Add `<title>Descriptive Page Title</title>` in `<head>` |
| `meta-description` | Add `<meta name="description" content="...">` (150–160 chars) |
| `crawlable-anchors` | Replace `<a onclick=...>` with real `href` links |
| `robots-txt` | Create/fix `/public/robots.txt`: `User-agent: *\nAllow: /` |
| `canonical` | Add `<link rel="canonical" href="https://your-domain.com/page">` |
| `hreflang` | Add `<link rel="alternate" hreflang="en" href="...">` if multilingual |
| `structured-data` | Add JSON-LD `<script type="application/ld+json">` block |
| `font-size` | Set `font-size: 16px` base; avoid text under 12px |
| `tap-targets` | Ensure tappable elements ≥ 48×48px with 8px spacing |
| `link-text` | Replace "click here" / "read more" with descriptive anchor text |

### Performance

| Audit ID | Fix |
|----------|-----|
| `render-blocking-resources` | Move `<script>` to bottom of `<body>` or add `defer`/`async`; inline critical CSS, `<link rel="preload">` for fonts |
| `unused-css-rules` | Remove or tree-shake unused CSS; split per-page stylesheets |
| `unused-javascript` | Add `defer` to non-critical scripts; lazy-load routes |
| `uses-optimized-images` | Convert to WebP; add `loading="lazy"` to below-fold images |
| `uses-responsive-images` | Use `srcset` and `sizes` attributes |
| `uses-text-compression` | Enable gzip/brotli in your server config (Caddy does this automatically) |
| `total-byte-weight` | Minify CSS/JS; check for unnecessary dependencies |
| `dom-size` | Reduce DOM nodes (target < 1500); virtualise long lists |

**Render-blocking pattern:**
```html
<!-- Before -->
<link rel="stylesheet" href="/styles.css">
<script src="/app.js"></script>

<!-- After -->
<link rel="preload" href="/styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<script src="/app.js" defer></script>
```

### Accessibility

| Audit ID | Fix |
|----------|-----|
| `image-alt` | Add `alt="description"` to all `<img>`; decorative images: `alt=""` |
| `heading-order` | Ensure `h1 → h2 → h3` hierarchy; one `<h1>` per page |
| `color-contrast` | Ensure text/background contrast ratio ≥ 4.5:1 (3:1 for large text) |
| `label` | Wrap `<input>` in `<label>` or use `aria-label="..."` |
| `button-name` | Add text content or `aria-label` to all `<button>` elements |
| `aria-roles` | Use valid ARIA roles; don't override implicit semantics unnecessarily |
| `frame-title` | Add `title="..."` to all `<iframe>` elements |
| `list` | Use `<ul>`/`<ol>` + `<li>` for lists, not styled `<div>` elements |
| `focus-traps` | Ensure keyboard focus can move freely; avoid `tabindex > 0` |
| `skip-link` | Add `<a href="#main" class="skip-link">Skip to content</a>` |

**ARIA pattern for interactive elements:**
```html
<!-- Button without text -->
<button aria-label="Close dialog">✕</button>

<!-- Custom widget -->
<div role="button" tabindex="0" aria-pressed="false" onclick="...">Toggle</div>
```

### Best Practices

| Audit ID | Fix |
|----------|-----|
| `uses-https` | Caddy handles TLS automatically for `*.devitltd.uk` — no action needed |
| `doctype` | Add `<!DOCTYPE html>` as first line |
| `charset` | Add `<meta charset="UTF-8">` in `<head>` |
| `js-libraries` | Update vulnerable deps: `npm audit fix` |
| `errors-in-console` | Fix JS errors; remove `console.error` in production |
| `paste-preventing-inputs` | Remove `onpaste="return false"` from inputs |

## Step 4: Redeploy

For Treehouse hosted apps:

```bash
cd /workspace/apps/<app-name>
tc app deploy <app-name>
```

Then wait for the container to start before re-auditing:

```bash
tc app wait <app-name>
```

## Step 5: Re-audit and Report

Re-run the Lighthouse command from Step 1 and compare scores. Report:

```
Before → After
  SEO:           72 → 95  ✓
  Performance:   54 → 81
  Accessibility: 88 → 96  ✓
  Best Practices: 92 → 100 ✓
```

Note which failures were fixed and which still need work. If a score is below 90, continue the loop with the remaining failures.

## Tips

- **Run from the host** — Lighthouse needs Chromium. Run `npx lighthouse` directly in the container (Chromium is available as `chromium`). If it times out, add `--max-wait-for-load=45000`.
- **Ignore Caddy-handled items** — TLS, compression, and HTTP/2 are handled by the platform. Don't add server config for these.
- **Structural vs content fixes** — Heading hierarchy and semantic HTML require editing template files. For React/Vue apps, find the component that renders the `<head>` (often a `Helmet` or `Head` component).
- **One category at a time** — If all categories need work, fix SEO and accessibility first (they're usually quick structural fixes), then tackle performance (requires more testing).
- **Score instability** — Performance scores can vary ±5 points between runs due to timing. Run twice if a score looks unexpectedly low.
