404 Pages and SEO in Vue

404 errors don't hurt SEO, but soft 404s do. Learn proper HTTP status codes, custom 404 design, and crawl budget optimization for Vue applications.
Harlan WiltonHarlan Wilton8 mins read Published
What you'll learn
  • 404 errors don't hurt SEO, but soft 404s do (200 status with error content)
  • SSR required—SPAs return 200 for all routes and render errors client-side
  • Return proper 404 HTTP status code from the server, not just client-side error pages

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 recrawling pages it thinks exist.

SSR required. SPAs typically return 200 OK for all routes and render 404 content client-side—search engines see this as a soft 404.

Quick Setup

Return proper 404 status codes from your server:

import express from 'express'

const app = express()

app.get('*', (req, res) => {
  // Check if route exists in your Vue Router config
  const routeExists = checkRoute(req.path)

  if (!routeExists) {
    res.status(404).send(render404Page())
    return
  }

  // Normal SSR render
  res.send(renderVueApp(req.path))
})

function render404Page() {
  return `
    <!DOCTYPE html>
    <html>
      <head>
        <title>404 Not Found</title>
        <meta name="robots" content="noindex">
      </head>
      <body>
        <h1>Page Not Found</h1>
        <p>The page you're looking for doesn't exist.</p>
        <a href="/">Go home</a>
      </body>
    </html>
  `
}

Add noindex meta tag to prevent 404 pages from appearing in search results if accidentally crawled with wrong status code.

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).

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

  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

Vue SPA Soft 404 Problem

Vue Router handles routing client-side. Server returns 200 OK for all paths:

// ❌ Bad - SPA returns 200 for /fake-page
app.get('*', (req, res) => {
  res.send(indexHtml) // Always 200 OK
})

Vue Router then renders 404 component in browser after JavaScript executes. Google sees 200 OK response, might see error content, flags as soft 404.

Solution: Check route existence server-side before rendering.

Checking Routes Server-Side

Match incoming paths against your Vue Router configuration:

import { createMemoryHistory, createRouter } from 'vue-router'
import routes from './routes'

function checkRoute(path: string): boolean {
  const router = createRouter({
    history: createMemoryHistory(),
    routes
  })

  const resolved = router.resolve(path)
  return resolved.matched.length > 0
}

Integrate with server:

import express from 'express'
import { checkRoute } from './router-check'

const app = express()

app.get('*', (req, res) => {
  if (!checkRoute(req.path)) {
    res.status(404).send(render404Page())
    return
  }

  res.send(renderVueApp(req.path))
})

Dynamic Routes Considerations

Dynamic routes (/products/:id) need data fetching to determine existence:

async function checkDynamicRoute(path: string): Promise<boolean> {
  const match = path.match(/^\/products\/([^/]+)$/)
  if (!match)
    return false

  const productId = match[1]
  const exists = await productExists(productId)

  return exists
}

async function productExists(id: string): Promise<boolean> {
  // Query database/API
  const product = await db.products.findById(id)
  return !!product
}

Server-side route checking:

app.get('/products/:id', async (req, res) => {
  const exists = await productExists(req.params.id)

  if (!exists) {
    res.status(404).send(render404Page())
    return
  }

  res.send(await renderProductPage(req.params.id))
})

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.

Custom 404 Page Design

Good 404 pages keep users on your site:

<script setup>
import { useHead } from '@unhead/vue'

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

<template>
  <div class="not-found">
    <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><a href="/products">Products</a></li>
        <li><a href="/blog">Blog</a></li>
        <li><a href="/support">Support</a></li>
      </ul>
    </nav>

    <a href="/">Go to Homepage</a>
  </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

404 errors have minimal crawl budget impact (Google Search Central). 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
  • 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

Content Moved

Use 301 redirect to new location:

app.get('/old-product', (req, res) => {
  res.redirect(301, '/new-product')
})

Content Permanently Removed

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

// ✅ Good - redirect to relevant category
app.get('/discontinued-product', (req, res) => {
  res.redirect(301, '/products/similar-items')
})

// ✅ Also good - return 404 if no replacement
app.get('/old-blog-post', (req, res) => {
  res.status(404).send(render404Page())
})

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

Redirecting All 404s to Homepage

Creates soft 404 risk. Google may ignore redirects to irrelevant pages.

// ❌ Bad - mass redirect to homepage
app.get('*', (req, res) => {
  res.redirect(301, '/')
})

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

Client-Side 404 Handling Only

JavaScript-rendered error pages return 200 OK to search engines:

<!-- ❌ Bad - SPA 404 component -->
<template v-if="!pageExists">
  <h1>404 Not Found</h1>
</template>

Server sees 200 OK, Google sees error content, flags soft 404. Set status server-side.

Forgetting noindex Meta Tag

If 404 page accidentally returns 200 OK, noindex prevents indexing:

<script setup>
import { useHead } from '@unhead/vue'

useHead({
  meta: [
    { name: 'robots', content: 'noindex' }
  ]
})
</script>

Safety net, not primary solution. Fix the status code.

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.

Using Nuxt?

Nuxt handles 404 errors automatically with the error.vue component and proper status codes. Check out Nuxt SEO for built-in crawl budget optimization and error handling.

Learn more in Nuxt →