---
title: "Fix Uncrawlable Links for Better SEO"
description: "Learn how to fix anchor elements that search engines can't follow due to missing or invalid href attributes."
canonical_url: "https://unlighthouse.dev/learn-lighthouse/seo/crawlable-anchors"
last_updated: "2025-01-18"
---

Search engines discover your pages by following links. When your anchor elements don't have proper `href` attributes, Googlebot can't crawl them - and those pages won't get indexed.

## What's the Problem?

Modern JavaScript frameworks make it easy to handle clicks without real links. But if there's no valid `href`, search engines see a dead end.

Lighthouse flags these patterns as uncrawlable:

**Empty or missing href:**

```html
<a onclick="navigate('/about')">About</a>
<a href="">About</a>
<a href="#">About</a>
```

**JavaScript pseudo-URLs:**

```html
<a href="javascript:void(0)">About</a>
<a href="javascript:">About</a>
```

**Invalid URLs:**

```html
<a href="file:///path/to/file">Download</a>
```

**Click handlers without href:**

```html
<a @click="goToAbout">About</a>
```

Googlebot does execute JavaScript, but it doesn't click on elements. It follows `href` attributes. No href, no crawl.

## How to Identify This Issue

### Chrome DevTools

1. Open DevTools (F12) → Elements tab
2. Search for `<a` and manually inspect href attributes
3. Or run this in the Console:

```javascript
document.querySelectorAll('a').forEach((a) => {
  const href = a.getAttribute('href')
  if (!href || href === '#' || href === '' || href.startsWith('javascript:')) {
    console.log('Uncrawlable:', a.outerHTML)
  }
})
```

### Lighthouse

Run a Lighthouse SEO audit. Look for "Links are not crawlable" which lists each failing anchor element with its HTML.

## The Fix

### 1. Always Include a Valid href

Every navigation link needs a real URL in the `href` attribute.

```html
<!-- Before -->
<a onclick="navigate('/about')">About</a>

<!-- After -->
<a href="/about">About</a>
```

If you need JavaScript behavior, use the href as the fallback:

```html
<a href="/about" onclick="handleClick(event)">About</a>
```

### 2. Replace JavaScript:void(0)

This pattern was common before we had better alternatives. Replace it with a real URL.

```html
<!-- Before -->
<a href="javascript:void(0)" onclick="showModal()">Open Modal</a>

<!-- After: if it's a true navigation -->
<a href="/modal-content">Open Modal</a>

<!-- Or: if it's an action, use a button -->
<button type="button" onclick="showModal()">Open Modal</button>
```

**Rule of thumb:** If clicking takes you to a new page, use `<a>` with an href. If it triggers an action on the current page, use `<button>`.

### 3. Fix Framework Router Links

Framework routers should handle this automatically, but verify your implementation:

```html
<!-- Vue Router -->
<router-link to="/about">About</router-link>
<!-- Renders: <a href="/about">About</a> ✓ -->

<!-- React Router -->
<Link to="/about">About</Link>
<!-- Renders: <a href="/about">About</a> ✓ -->
```

If you're using custom click handlers instead of router components, switch to the proper router link component.

### 4. Handle Dynamic Navigation

When you generate URLs dynamically, make sure the href is populated at render time - not just on click.

```html
<!-- Before: href is empty until clicked -->
<a href="" @click="href = getProductUrl()">View Product</a>

<!-- After: href is set during render -->
<a :href="productUrl">View Product</a>
```

```javascript
// Compute the URL upfront
const productUrl = computed(() => `/products/${product.id}`)
```

### 5. Fix Anchor Placeholders

If you're using `<a>` without href as a placeholder or for styling, reconsider.

```html
<!-- Before: anchor as placeholder -->
<a class="nav-item disabled">Coming Soon</a>

<!-- After: use span for non-links -->
<span class="nav-item disabled">Coming Soon</span>
```

The only valid case for `<a>` without href is as a named anchor: `<a id="section-1"></a>`. Even then, prefer `id` on any element.

### 6. Escape Hatch: ARIA Role

If you must use an anchor without href for technical reasons, add `role` to exclude it from the audit:

```html
<a role="button" onclick="showMenu()">Menu</a>
```

But this is a band-aid. Prefer a real `<button>` element.

## Framework-Specific Solutions

<callout icon="i-logos-nextjs-icon">

**Next.js**: Always use the `Link` component from `next/link`, never raw anchors with click handlers. The Link component renders a proper `<a>` with href for crawlers.

</callout>

<callout icon="i-logos-nuxt-icon">

**Nuxt**: Use `NuxtLink` for all internal navigation. It handles both client-side routing and proper href rendering. Avoid `@click` navigation on raw anchors.

</callout>

## Verify the Fix

1. Run Lighthouse SEO audit again
2. Confirm "Links are crawlable" shows as passing
3. Use "View Page Source" (not DevTools) to see what Googlebot sees before JavaScript runs
4. Check Google Search Console for crawl errors on pages that were previously unreachable

## Common Mistakes

- **Using anchors for buttons**: If it doesn't navigate to a URL, it shouldn't be an `<a>`. Use `<button>` for actions like modals, dropdowns, and form submissions.
- **Empty href with hash routing**: `<a href="" @click="$router.push('/about')">` still fails the audit. Use `<router-link>` or include the href.
- **Forgetting SSR rendering**: In SSR apps, check that hrefs are populated during server render, not just after hydration.
- **File protocol links**: `file:///` URLs are flagged as uncrawlable because they're not accessible to search engines. Upload files to your server and use http/https URLs.

## Related Issues

Crawlable anchor issues often appear alongside:

- [Link Text](/learn-lighthouse/seo/link-text) - Links need both valid hrefs and descriptive text
- [Link Name](/learn-lighthouse/accessibility/link-name) - Accessibility also requires links to have names
- [Canonical](/learn-lighthouse/seo/canonical) - Links should point to canonical URLs

## Test Your Entire Site

Uncrawlable links often hide in navigation components, footers, or dynamically loaded sections. Unlighthouse scans your entire site and identifies every page with uncrawlable anchors, so you can fix them all at once.
