404 Pages and SEO in Vue
- Missing pages should return HTTP 404. A success response with error content can produce a soft 404
- A client component cannot change an HTTP response already sent. Set the status through your host or server
- Google treats 404 and 410 alike for indexing, while the codes describe different resource states
A real 404 is appropriate for a missing page. Fix broken internal links, but do not turn every unknown URL into a homepage redirect.
A soft 404 can occur when a success response contains an error message or empty content. Google does not guarantee indexing for a 200 response. Return the correct status instead of relying on the error page's appearance.
SSR is one way to combine a Vue view with a server response. A static host, proxy, or application server can also return a real 404 without rendering Vue on every request.
Quick Setup
This complete illustrative HTTP app uses Express 5 and Vue's string renderer. Its in-memory catalog contains one product. It demonstrates the response boundary, without client routing, hydration, a database, or production asset hosting.
In a package with "type": "module", install the dependencies:
pnpm add express@5.2.1 vue@3.5.43Save this as catalog.ts:
import express from 'express'
import { createSSRApp, h } from 'vue'
import { renderToString } from 'vue/server-renderer'
export function createCatalogApp() {
const app = express()
const products = new Map([['widget', { title: 'Widget' }]])
async function page(heading: string) {
const app = createSSRApp({
render: () => h('main', [h('h1', heading), h('a', { href: '/' }, 'Home')])
})
const body = await renderToString(app)
return `<!doctype html><html><head><title>Mini catalog</title></head><body>${body}</body></html>`
}
app.get('/', async (_req, res) => {
res.type('html').send(await page('Mini catalog'))
})
app.get('/products/:id', async (req, res) => {
const product = products.get(req.params.id)
if (!product) {
res.status(404).type('html').send(await page('Page not found'))
return
}
res.type('html').send(await page(product.title))
})
app.get('/old-product', (req, res) => {
const query = new URL(req.originalUrl, 'http://localhost').search
res.redirect(301, `/products/widget${query}`)
})
app.get('/gone', async (_req, res) => {
res.status(410).type('html').send(await page('This page has been removed'))
})
app.use(async (_req, res) => {
res.status(404).type('html').send(await page('Page not found'))
})
return app
}Save this entry as server.ts and run node server.ts. Node 24.18.0 ran these TypeScript files in the reader check:
import { createCatalogApp } from './catalog.ts'
createCatalogApp().listen(3000, '127.0.0.1')Request /products/widget, /products/missing, and /missing. The first should return 200 with Widget content; the others should return 404 with a helpful error page. Express 5 requires named wildcard patterns. This example uses a final pathless middleware rather than the unsupported app.get('*', ...) pattern.
For a full Vite application, follow the Vite SSR guide and integrate your status decision before sending the rendered response.
Soft 404 Errors Explained
Google may classify a successful response as a soft 404 when its content suggests an error or an empty page. There is no documented 200-word threshold.
If the page should exist, restore its meaningful content and fix rendering or data failures. If it does not exist, return 404 or 410. The Page indexing report can surface soft 404 classifications; local HTTP checks do not reproduce Google's classification system.
Why Soft 404s Hurt SEO
Google excludes pages it classifies as soft 404s from indexing. Investigate whether the problem is a missing resource or a failed page. A soft 404 report does not prove index bloat, lost ranking signals, or a fixed amount of wasted crawl capacity.
Vue SPA Soft 404 Problem
A host may rewrite unknown paths to an app shell with status 200. Vue Router then displays a missing-page view. That view cannot change the response the host already sent.
Vue Router's history-mode guide describes host fallback configuration and its 404 caveat. Use a host policy or server-side route/data decision to distinguish missing URLs from valid app routes.
Checking Routes Server-Side
A router match identifies a route pattern, not necessarily an existing record. A catch-all missing-page route also matches unknown paths. Do not treat any nonempty resolved.matched array as proof that content exists.
In an installed router, mark the catch-all route and inspect the matched records. This factory is a router excerpt; routes includes your application components and a catch-all record with meta: { notFound: true }:
import type { RouteRecordRaw } from 'vue-router'
import { createMemoryHistory, createRouter } from 'vue-router'
export function createRouteCheck(routes: RouteRecordRaw[]) {
const router = createRouter({ history: createMemoryHistory(), routes })
return (url: string) => {
const resolved = router.resolve(url)
return resolved.matched.length > 0
&& !resolved.matched.some(record => record.meta.notFound === true)
}
}This check does not establish whether /products/:id has a record. Combine route matching with the resource lookup before choosing the response status.
Dynamic Routes Considerations
A matched /products/:id pattern can represent a nonexistent product. The quick setup looks up the requested ID before sending a response. A real app must distinguish missing data from a failed database or API request.
Return 404 for a confirmed missing resource. Do not turn timeouts or authentication failures into missing-page responses by swallowing errors. Keep access-control and infrastructure failures separate from content absence.
404 vs 410 Status Codes
404 means the server did not find a current representation. It does not state whether the resource once existed. 410 indicates that the resource is gone and that this condition is likely permanent.
Google treats both alike for indexing. It removes previously indexed 4xx URLs over time and does not process newly encountered 404 pages. Choose the status that describes your resource; do not promise a faster removal from using 410.
Custom 404 Page Design
Keep a short explanation and useful links on the error page. The quick setup includes a Home link. An application can also offer relevant sections or an installed search component.
If you render an error view in Vue, the server or host must still select the status. An Unhead title or robots tag controls document metadata, not HTTP status. Install the head plugin before calling its composables.
Crawl Budget Impact
Google's crawling frequency gradually decreases for URLs that continue returning a missing-resource status. A genuine 404 does not require a redirect simply because a crawler requested it.
Fix internal links to missing pages and remove obsolete URLs from sitemaps. Review repeated patterns to find incorrect links or routing failures. Do not infer a crawl-budget crisis from occasional typos or obsolete external links.
Handling 404s for Deleted Content
Content Moved
If an appropriate replacement exists, use a permanent 301 or 308 redirect. The quick setup redirects /old-product to the existing product and preserves the query string.
Content Permanently Removed
If no suitable replacement exists, return 404 or 410. A category page is not automatically a relevant replacement for every deleted product. Redirect only when the destination meets the original visitor's need.
Testing 404 Responses
Check the actual GET response and its content:
curl -i http://127.0.0.1:3000/products/widget
curl -i http://127.0.0.1:3000/products/missing
curl -i http://127.0.0.1:3000/missing
curl -i http://127.0.0.1:3000/goneExpect 200, 404, 404, and 410 respectively. Check that the response body matches each status. A HEAD request can also inspect headers, but verify GET because some hosts handle the methods differently.
In browser DevTools, inspect the document request on a direct load and reload. Client navigation may not request another document. In Search Console, review URL Inspection and the Page indexing report; requesting indexing is not a status-code test or a guarantee of inclusion.
Common Mistakes
Redirecting All 404s to Homepage
An irrelevant mass redirect can produce a soft 404. Use an appropriate replacement or return a missing-resource status.
Client-Side 404 Handling Only
An error component does not prove the server returned 404. Inspect a direct document request, including a missing record that matches a dynamic route pattern.
Forgetting noindex Meta Tag
A real 404 or 410 already excludes that response's content from Google's indexing. A robots noindex can be a fallback if an error view temporarily returns success, but Google must fetch it to read it. Fix the status rather than treating noindex as a mandatory 404 requirement.
Not Monitoring 404 Patterns
Repeated missing URLs can reveal broken links or a deployment problem. Review the Page indexing report and your server logs, then investigate the specific source. An isolated external typo does not justify inventing a redirect target.
Using Nuxt? Its error APIs can render error pages with a chosen status. Your app must detect missing data and supply the correct status. Learn more in Nuxt →
Checklist
- Direct requests to missing paths and missing records return 404 or 410
- The error page explains what happened and offers useful links
- Catch-all route matching does not treat the missing-page record as valid content
- Redirects point to appropriate replacements, rather than sending every missing URL home
- GET responses and bodies agree; investigate soft 404 classifications separately in Search Console
Pre-launch setup, post-launch verification, and ongoing monitoring. Interactive checklist with links to every guide.