404 Pages and SEO in Nuxt · Nuxt SEO

[NuxtSEO](https://nuxtseo.com/ "Home")

- [Modules](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction)
- [Tools](https://nuxtseo.com/tools)
- [Pro](https://nuxtseo.com/pro)
- [Learn SEO](https://nuxtseo.com/learn-seo/nuxt) [Releases](https://nuxtseo.com/releases)

[1.4K](https://github.com/harlan-zw/nuxt-seo)

[Nuxt SEO on GitHub](https://github.com/harlan-zw/nuxt-seo)

Learn SEO

Master search optimization

Nuxt

 Vue

[SEO Checklist](https://nuxtseo.com/learn-seo/checklist) [Pre-Launch Warmup](https://nuxtseo.com/learn-seo/pre-launch-warmup) [Backlinks & Authority](https://nuxtseo.com/learn-seo/backlinks)

[Mastering Meta](https://nuxtseo.com/learn-seo/nuxt/mastering-meta)

- [Titles](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/titles)
- [Meta Description](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/descriptions)
- [Image Alt Text](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/alt-text)
- [Social Sharing](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/open-graph)
- [Rich Results](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/rich-results)
- [Schema.org](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/schema-org)
- [Twitter Cards](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/twitter-cards)

[ Controlling Crawlers](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers)

- [Robots Txt](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/robots-txt)
- [Sitemaps](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/sitemaps)
- [Robot Meta Tag](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/meta-tags)
- [Canonical Link Tag](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/canonical-urls)
- [HTTP Redirects](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/redirects)
- [Duplicate Content](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/duplicate-content)
- [llms.txt](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/llms-txt)

[ Routes & Rendering](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering)

- [URL Structure](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/url-structure)
- [Pagination](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/pagination)
- [Trailing Slashes](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/trailing-slashes)
- [Query Parameters](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/query-parameters)
- [Hreflang & i18n](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/i18n)
- [404 Pages](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/404-pages)
- [Dynamic Routes](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/dynamic-routes)
- [Internal Linking](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/internal-linking)
- [Programmatic SEO](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/programmatic-seo)
- [Rendering Modes](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/rendering)
- [Security](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/security)

[ Launch & Listen](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen)

- [Getting Indexed](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/going-live)
- [Google Search Console](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/search-console)
- [Core Web Vitals](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/core-web-vitals)
- [Indexing Issues](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/indexing-issues)
- [SEO Monitoring](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/seo-monitoring)
- [Site Migration](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/site-migration)
- [IndexNow](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/indexnow)
- [Debugging](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/debugging)
- [AI Search Optimization](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/ai-optimized-content)

1. [Learn SEO for Nuxt](https://nuxtseo.com/learn-seo)
2.
3. [Routes And Rendering](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering)
4.
5. [404 Pages](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/404-pages)

# 404 Pages and SEO in Nuxt

404 errors don't hurt SEO, but soft 404s do. Learn proper HTTP status codes, custom 404 design, and crawl budget optimization for Nuxt applications.

[![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan-zw)8 mins read Published Dec 17, 2025

404 errors don't hurt SEO. They're expected: deleted products, outdated links, user typos all create legitimate 404s. Google ignores them.

Soft 404s hurt SEO. A soft 404 returns `200 OK` status but shows "page not found" content. Google excludes these from search results and wastes your [crawl budget](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers#crawler-budget) recrawling pages it thinks exist.

Nuxt handles this automatically. Using `error.vue` and `createError()` ensures proper status codes for both SSR and SSG.

## [Quick Setup](#quick-setup)

Create `error.vue` in your project root:

error.vue

```
<script setup lang="ts">
const props = defineProps({
  error: Object
})

useHead({
  title: props.error?.statusCode === 404 ? '404 - Page Not Found' : 'Error',
  meta: [
    { name: 'robots', content: 'noindex' }
  ]
})
</script>

<template>
  <div class="error-page">
    <template v-if="error?.statusCode === 404">
      <h1>Page Not Found</h1>
      <p>The page you're looking for doesn't exist.</p>
      <NuxtLink to="/">
        Go home
      </NuxtLink>
    </template>
    <template v-else>
      <h1>An error occurred</h1>
      <p>{{ error?.message }}</p>
    </template>
  </div>
</template>
```

Throw 404 errors in pages when content doesn't exist:

pages/products/[id].vue

```
<script setup lang="ts">
const route = useRoute()
const { data: product } = await useFetch(\`/api/products/${route.params.id}\`)

if (!product.value) {
  throw createError({
    statusCode: 404,
    statusMessage: 'Product not found'
  })
}

useSeoMeta({
  title: product.value.name,
  description: product.value.description
})
</script>
```

Nuxt returns proper `404` status code for SSR and static generation automatically.

## [Soft 404 Errors Explained](#soft-404-errors-explained)

Soft 404 detection happens when Google sees content that looks like an error page but receives `200 OK` status ([Google Search Central](https://developers.google.com/search/docs/crawling-indexing/http-network-errors#soft-404-errors)).

Common triggers:

- "Page not found" in title or heading
- Minimal content (under ~200 words)
- Redirecting all 404s to homepage
- Empty page body with "coming soon" message
- Generic error messages without meaningful content

Google Search Console flags soft 404s in the "Page Indexing" report. Fix by returning proper `404` status code.

### [Why Soft 404s Hurt SEO](#why-soft-404s-hurt-seo)

1. **Wasted crawl budget** - Google recrawls pages thinking they exist, leaving less budget for real pages
2. **Index bloat** - Search Console shows thousands of indexed URLs that don't exist
3. **Ranking signals confusion** - Google doesn't know if content moved or disappeared
4. **No link equity transfer** - Can't redirect or canonicalize non-existent pages properly

## [Checking Routes and Throwing Errors](#checking-routes-and-throwing-errors)

Nuxt's file-based routing handles most route validation automatically. For dynamic routes, check data existence:

pages/blog/[slug].vue

```
<script setup lang="ts">
const route = useRoute()
const { data: post } = await useAsyncData(\`post-${route.params.slug}\`, () =>
  queryContent(\`/blog/${route.params.slug}\`).findOne())

if (!post.value) {
  throw createError({
    statusCode: 404,
    statusMessage: 'Post not found'
  })
}

useSeoMeta({
  title: post.value.title,
  description: post.value.description
})
</script>

<template>
  <article>
    <h1>{{ post.title }}</h1>
    <ContentRenderer :value="post" />
  </article>
</template>
```

The `createError()` function triggers Nuxt's error handling, renders `error.vue`, and returns proper HTTP status code.

## [Dynamic Routes Considerations](#dynamic-routes-considerations)

Check data existence before rendering:

pages/products/[id].vue

```
<script setup lang="ts">
const route = useRoute()

const { data: product } = await useFetch(\`/api/products/${route.params.id}\`)

if (!product.value) {
  throw createError({
    statusCode: 404,
    statusMessage: 'Product not found'
  })
}

// Product exists, continue with meta tags
useSeoMeta({
  title: product.value.name,
  description: product.value.description,
  ogImage: product.value.image
})
</script>

<template>
  <div class="product">
    <h1>{{ product.name }}</h1>
    <p>{{ product.description }}</p>
  </div>
</template>
```

For server routes, use `setResponseStatus`:

server/api/products/[id].get.ts

```
export default defineEventHandler(async (event) => {
  const id = getRouterParam(event, 'id')
  const product = await findProduct(id)

  if (!product) {
    setResponseStatus(event, 404)
    return { error: 'Product not found' }
  }

  return product
})
```

## [404 vs 410 Status Codes](#_404-vs-410-status-codes)

**404 Not Found** - Resource doesn't exist, might never have existed, might come back:

- User typos
- Outdated external links
- Deleted products that might return to inventory
- Seasonal content (holiday pages)

**410 Gone** - Resource existed, now permanently removed:

- Discontinued products
- Deleted blog posts (no redirect target)
- Expired promotions
- Intentionally removed content

Google treats both similarly for indexing. removes from search results. 410 signals faster removal but rarely needed. Use 404 for most cases.

To return 410:

pages/discontinued/[id].vue

```
<script setup lang="ts">
throw createError({
  statusCode: 410,
  statusMessage: 'This product has been discontinued'
})
</script>
```

## [Custom 404 Page Design](#custom-404-page-design)

Good 404 pages keep users on your site:

error.vue

```
<script setup lang="ts">
const props = defineProps({
  error: Object
})

const is404 = computed(() => props.error?.statusCode === 404)

useHead({
  title: is404.value ? '404 - Page Not Found' : 'Error',
  meta: [
    { name: 'robots', content: 'noindex' }
  ]
})
</script>

<template>
  <div class="not-found">
    <template v-if="is404">
      <h1>Page Not Found</h1>
      <p>The page you're looking for doesn't exist or has moved.</p>

      <SearchBox />

      <nav>
        <h2>Popular Pages:</h2>
        <ul>
          <li>
            <NuxtLink to="/products">
              Products
            </NuxtLink>
          </li>
          <li>
            <NuxtLink to="/blog">
              Blog
            </NuxtLink>
          </li>
          <li>
            <NuxtLink to="/support">
              Support
            </NuxtLink>
          </li>
        </ul>
      </nav>

      <NuxtLink to="/">
        Go to Homepage
      </NuxtLink>
    </template>
    <template v-else>
      <h1>An error occurred</h1>
      <p>{{ error?.message }}</p>
      <NuxtLink to="/">
        Go to Homepage
      </NuxtLink>
    </template>
  </div>
</template>
```

**Don't:**

- Redirect all 404s to homepage (soft 404 risk)
- Auto-redirect after countdown (bad UX)
- Show only "404" with no explanation
- Display technical error messages

**Do:**

- Explain what happened clearly
- Provide search functionality
- Link to popular/relevant pages
- Match site design (keeps users oriented)
- Include contact option for reporting broken links

## [Crawl Budget Impact](#crawl-budget-impact)

404 errors have minimal crawl budget impact ([Google Search Central](https://developers.google.com/search/docs/crawling-indexing/http-network-errors)). Google expects them. Soft 404s waste crawl budget because Google recrawls pages thinking content exists.

Large sites (10,000+ pages) should:

- Monitor 404 rates in Search Console
- Fix internal links pointing to 404s
- Remove 404 URLs from [sitemap](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/sitemaps)
- Use 301 redirects for high-value deleted pages with relevant replacements

Don't worry about occasional 404s from external links or user typos.

## [Handling 404s for Deleted Content](#handling-404s-for-deleted-content)

### [Content Moved](#content-moved)

Use [301 redirect](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/redirects) in `nuxt.config.ts`:

nuxt.config.ts

```
export default defineNuxtConfig({
  routeRules: {
    '/old-product': { redirect: { to: '/new-product', statusCode: 301 } },
    '/old-category/**': { redirect: { to: '/new-category/**', statusCode: 301 } }
  }
})
```

Or in server middleware:

server/middleware/redirects.ts

```
export default defineEventHandler((event) => {
  if (event.path === '/old-product') {
    return sendRedirect(event, '/new-product', 301)
  }
})
```

### [Content Permanently Removed](#content-permanently-removed)

Return 404 or 410. If similar content exists, redirect to relevant category:

nuxt.config.ts

```
export default defineNuxtConfig({
  routeRules: {
    // ✅ Good - redirect to relevant category
    '/discontinued-product': { redirect: { to: '/products/similar-items', statusCode: 301 } },

    // ✅ Also good - let Nuxt return 404 naturally for non-existent routes
  }
})
```

## [Testing 404 Responses](#testing-404-responses)

Verify proper status codes before deploying:

**Browser DevTools:**

1. Open Network tab
2. Navigate to non-existent URL
3. Check status code in response headers
4. Should show `404` not `200`

**Command line:**

```
curl -I https://example.com/fake-page

# Output should show:
# HTTP/1.1 404 Not Found
```

**Google Search Console:**

1. Use URL Inspection tool
2. Enter 404 URL
3. "Request indexing"
4. Check if Google recognizes 404 status
5. Monitor "Page Indexing" report for soft 404 flags

**Lighthouse:** Run Lighthouse audit, check "Crawling and Indexing" section for status code issues.

## [Common Mistakes](#common-mistakes)

### [Redirecting All 404s to Homepage](#redirecting-all-404s-to-homepage)

Creates soft 404 risk. Google may ignore [redirects](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/redirects) to irrelevant pages.

```
// ❌ Bad - mass redirect to homepage
export default defineNuxtConfig({
  routeRules: {
    '/**': { redirect: '/' } // Don't do this
  }
})
```

Only redirect if replacement content is relevant. Otherwise let Nuxt return proper 404.

### [Not Using createError()](#not-using-createerror)

Rendering error content without throwing error returns `200 OK`:

```
<!-- ❌ Bad - returns 200 status -->
<template>
  <div v-if="!product">
    <h1>404 Not Found</h1>
  </div>
</template>
```

```
<!-- ✅ Good - returns 404 status -->
<script setup lang="ts">
const { data: product } = await useFetch('/api/product')

if (!product.value) {
  throw createError({ statusCode: 404 })
}
</script>
```

### [Forgetting noindex Meta Tag](#forgetting-noindex-meta-tag)

If 404 page accidentally returns `200 OK`, [`noindex`](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/meta-tags) prevents indexing:

error.vue

```
<script setup lang="ts">
useHead({
  meta: [
    { name: 'robots', content: 'noindex' }
  ]
})
</script>
```

Safety net, not primary solution. Use `createError()` for proper status codes.

### [Not Monitoring 404 Patterns](#not-monitoring-404-patterns)

Repeated 404s to same path indicate broken internal links or outdated external links. Check Search Console "Not Found" report monthly, fix internal links immediately.

## [Catch-All Routes](#catch-all-routes)

For custom 404 handling beyond `error.vue`, use catch-all routes:

pages/[...slug].vue

```
<script setup lang="ts">
// This catches all unmatched routes
const route = useRoute()

// Try to find content
const { data: page } = await useAsyncData(\`page-${route.path}\`, () =>
  queryContent(route.path).findOne())

if (!page.value) {
  throw createError({
    statusCode: 404,
    statusMessage: 'Page not found'
  })
}

useSeoMeta({
  title: page.value.title,
  description: page.value.description
})
</script>

<template>
  <ContentRenderer :value="page" />
</template>
```

This pattern works for CMS-driven sites where routes come from database/content.

[The 2026 SEO Checklist for Nuxt & Vue Pre-launch setup, post-launch verification, and ongoing monitoring. Interactive checklist with links to every guide.](https://nuxtseo.com/learn-seo/checklist) [Haven't launched yet? Start with the Pre-Launch Warmup](https://nuxtseo.com/learn-seo/pre-launch-warmup)

---

[Hreflang & i18n Set hreflang tags in Nuxt to tell search engines which language version to show users. Avoid duplicate content penalties across multilingual sites.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/i18n) [Dynamic Routes How to use Nuxt's file-based dynamic routes, set per-route meta tags, and avoid duplicate content issues with URL parameters.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/dynamic-routes)

On this page

- [Quick Setup](#quick-setup)
- [Soft 404 Errors Explained](#soft-404-errors-explained)
- [Checking Routes and Throwing Errors](#checking-routes-and-throwing-errors)
- [Dynamic Routes Considerations](#dynamic-routes-considerations)
- [404 vs 410 Status Codes](#_404-vs-410-status-codes)
- [Custom 404 Page Design](#custom-404-page-design)
- [Crawl Budget Impact](#crawl-budget-impact)
- [Handling 404s for Deleted Content](#handling-404s-for-deleted-content)
- [Testing 404 Responses](#testing-404-responses)
- [Common Mistakes](#common-mistakes)
- [Catch-All Routes](#catch-all-routes)

[GitHub](https://github.com/harlan-zw/nuxt-seo) [ Discord](https://discord.com/invite/275MBUBvgP)

### [NuxtSEO](https://nuxtseo.com/ "Home")

- [Getting Started](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction)
- [MCP](https://nuxtseo.com/docs/nuxt-seo/guides/mcp)

Modules

- [Robots](https://nuxtseo.com/docs/robots/getting-started/introduction)
- [Sitemap](https://nuxtseo.com/docs/sitemap/getting-started/introduction)
- [OG Image](https://nuxtseo.com/docs/og-image/getting-started/introduction)
- [Schema.org](https://nuxtseo.com/docs/schema-org/getting-started/introduction)
- [Link Checker](https://nuxtseo.com/docs/link-checker/getting-started/introduction)
- [SEO Utils](https://nuxtseo.com/docs/seo-utils/getting-started/introduction)
- [Site Config](https://nuxtseo.com/docs/site-config/getting-started/introduction)
- [Skew Protection](https://nuxtseo.com/docs/skew-protection/getting-started/introduction)
- [AI Ready](https://nuxtseo.com/docs/ai-ready/getting-started/introduction)

### [NuxtSEO Pro](https://nuxtseo.com/pro "Nuxt SEO Pro")

- [Getting Started](https://nuxtseo.com/pro)
- [Dashboard](https://nuxtseo.com/pro/dashboard)
- [Pro MCP](https://nuxtseo.com/pro/docs/getting-started/mcp-setup)

### [Learn SEO](https://nuxtseo.com/learn-seo "Learn SEO")

Nuxt

- [Mastering Meta](https://nuxtseo.com/learn-seo/nuxt/mastering-meta)
- [Controlling Crawlers](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers)
- [Launch & Listen](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen)
- [Routes & Rendering](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering)
- [Staying Secure](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/security)

Vue

- [Vue SEO Guide](https://nuxtseo.com/learn-seo/vue)
- [Mastering Meta](https://nuxtseo.com/learn-seo/vue/mastering-meta)
- [Controlling Crawlers](https://nuxtseo.com/learn-seo/vue/controlling-crawlers)
- [SPA SEO](https://nuxtseo.com/learn-seo/vue/spa)
- [SSR Frameworks](https://nuxtseo.com/learn-seo/vue/ssr-frameworks)
- [SEO Checklist](https://nuxtseo.com/learn-seo/checklist)
- [Pre-Launch Warmup](https://nuxtseo.com/learn-seo/pre-launch-warmup)
- [Backlinks & Authority](https://nuxtseo.com/learn-seo/backlinks)

### [Tools](https://nuxtseo.com/tools "SEO Tools")

- [Social Share Debugger](https://nuxtseo.com/tools/social-share-debugger)
- [Robots.txt Generator](https://nuxtseo.com/tools/robots-txt-generator)
- [Meta Tag Checker](https://nuxtseo.com/tools/meta-tag-checker)
- [HTML to Markdown](https://nuxtseo.com/tools/html-to-markdown)
- [XML Sitemap Validator](https://nuxtseo.com/tools/xml-sitemap-validator)
- [Schema.org Validator](https://nuxtseo.com/tools/schema-validator)
- [Keyword Idea Generator](https://nuxtseo.com/tools/keyword-generator)
- [Keyword Research](https://nuxtseo.com/tools/keyword-research)
- [SERP Analyzer](https://nuxtseo.com/tools/serp-analyzer)
- [Domain Rankings](https://nuxtseo.com/tools/domain-rankings)

Copyright © 2023-2026 Harlan Wilton - [MIT License](https://github.com/harlan-zw/nuxt-seo/blob/main/license) · [mdream](https://mdream.dev)