# Link, anchor, and form action hrefs resolve to pages

- **Pattern:** `ab-000029` (`ai-slop-hallucinations.route-references.link-hrefs-resolve`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000029
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000029)

## Why it matters

Broken internal hrefs turn into visible 404s the moment a user clicks a nav item — they are the most common user-facing symptom of AI hallucination because the model names a page from its training intuition rather than your routing tree. `<Link href='/dashbord'>` will silently deploy, get indexed by crawlers, and then 404 every visitor who lands on it. Reference integrity at the navigation layer is load-bearing for crawlability and for bounce rate on marketing pages.

## Severity rationale

High because every broken internal href is a 404 a user can trigger by clicking, and search engines penalize the domain for it.

## Remediation

For each unresolved href, either fix the path to match an existing page file or create the missing page. In the Next.js App Router, the href must map to a `page.tsx` at the corresponding directory:

```tsx
// Matches app/dashboard/page.tsx
<Link href="/dashboard">Dashboard</Link>
```

For dynamic routes, ensure the segment file exists — for example `app/posts/[id]/page.tsx` for the href `/posts/123`.

## Detection

- **ID:** `link-hrefs-resolve`
- **Severity:** `high`
- **What to look for:** Walk all `.tsx`/`.jsx` files for these elements: `<Link href="...">`, `<a href="...">`, `<NavLink to="...">`, `<form action="...">`. Extract the literal href/to/action value. SKIP scheme-prefixed (`http://`, `https://`, `mailto:`, `tel:`, `javascript:`), anchor-only (`#section`), and protocol-relative (`//cdn.example.com`). For remaining absolute paths starting with `/`, normalize dynamic segments and match against the project's page route tree (`KNOWN_ROUTES` from the analysis step). For Next.js, also accept catch-all routes (`[...slug]`) as matching any deeper path. Count all internal hrefs inspected, total resolved, total unresolved. Sample at most 100 unique hrefs.
- **Pass criteria:** 100% of internal hrefs resolve to an existing page route. Report: "X unique internal hrefs inspected, Y resolved, 0 unresolved. Sampled X of N unique hrefs."
- **Fail criteria:** At least 1 internal href does not resolve to a page route after normalization.
- **Do NOT pass when:** An href like `/dashboard` resolves only because of a catch-all route at the project root (`app/[...slug]/page.tsx`) — catch-alls are valid but should be reported in detail so the user knows the resolution is loose.
- **Skip (N/A) when:** Project has 0 page route files (`app/`, `pages/`, `routes/`, `src/routes/` all empty or missing).
- **Detail on fail:** `"3 unresolved hrefs: <Link href='/dashbord'> in src/app/page.tsx (typo of '/dashboard'), <a href='/about/team'> in src/components/Footer.tsx (no app/about/team/page.tsx). Sampled 38 of 38 unique hrefs."`
- **Remediation:** Broken navigation hrefs cause visible 404s when users click them. Fix each one:

  ```tsx
  // Bad: typo in href
  <Link href="/dashbord">Dashboard</Link>

  // Good: matches app/dashboard/page.tsx
  <Link href="/dashboard">Dashboard</Link>
  ```

  For dynamic routes, ensure the page file exists at the expected path (e.g., `app/posts/[id]/page.tsx` for `/posts/123`).

Taxons: reference-integrity

HTML version: https://auditbuffet.com/patterns/ab-000029
