Create a custom 404 error page
A custom 404 error page is designed with helpful navigation options for lost users.
- Custom 404 pages retain users who encounter broken or moved links
- Include search, navigation, and links to popular content
- Maintain consistent branding and site design
- Track 404 errors to identify and fix broken links
- Return a real HTTP 404 so the page is not treated as a soft 404
Rule Details
Custom 404 pages transform dead ends into opportunities to guide users back to relevant content.
Code Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Page Not Found - Site Name</title>
<meta name="robots" content="noindex">
</head>
<body>
<main class="error-page">
<h1>Page Not Found</h1>
<p>Sorry, the page you're looking for doesn't exist or has been moved.</p>
<!-- Search -->
<form action="/search" method="GET" role="search">
<label for="search">Search our site:</label>
<input type="search" id="search" name="q" placeholder="What are you looking for?">
<button type="submit">Search</button>
</form>
<!-- Helpful links -->
<nav aria-label="Helpful links">
<h2>Try these instead:</h2>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/blog">Blog</a></li>
<li><a href="/contact">Contact Us</a></li>
</ul>
</nav>
</main>
</body>
</html>Why It Matters
A generic browser 404 page causes users to leave immediately—a well-designed custom page provides recovery paths and keeps users engaged with your site.
Essential Elements
| Element | Purpose |
|---|---|
| Clear message | Explain the page wasn't found |
| Search box | Help users find what they need |
| Navigation links | Quick access to main sections |
| Popular content | Suggest relevant pages |
| Home link | Easy return to start |
| Contact option | Report broken links |
Next.js Implementation
// app/not-found.tsx
import Link from 'next/link'
export default function NotFound() {
return (
<main className="min-h-screen flex items-center justify-center px-4">
<div className="text-center max-w-md">
<h1 className="text-6xl font-bold text-gray-900 mb-4">404</h1>
<h2 className="text-2xl font-semibold mb-4">Page Not Found</h2>
<p className="text-gray-600 mb-8">
The page you're looking for doesn't exist or has been moved.
</p>
<div className="space-y-4">
<Link
href="/"
className="block w-full bg-blue-600 text-white py-2 px-4 rounded hover:bg-blue-700"
>
Go Home
</Link>
<Link
href="/search"
className="block w-full border border-gray-300 py-2 px-4 rounded hover:bg-gray-50"
>
Search Site
</Link>
</div>
<nav className="mt-8" aria-label="Popular pages">
<h3 className="text-sm font-semibold text-gray-500 mb-3">
Popular Pages
</h3>
<ul className="space-y-2">
<li><Link href="/products" className="text-blue-600 hover:underline">Products</Link></li>
<li><Link href="/docs" className="text-blue-600 hover:underline">Documentation</Link></li>
<li><Link href="/blog" className="text-blue-600 hover:underline">Blog</Link></li>
</ul>
</nav>
</div>
</main>
)
}React Component with Error Tracking
interface NotFoundPageProps {
searchEnabled?: boolean
popularLinks?: Array<{ href: string; label: string }>
}
export function NotFoundPage({
searchEnabled = true,
popularLinks = []
}: NotFoundPageProps) {
// Track 404 for analytics
useEffect(() => {
// Report to analytics
if (typeof window !== 'undefined') {
const path = window.location.pathname
console.error(`404 Error: ${path}`)
// Send to analytics service
analytics?.track('404_error', { path, referrer: document.referrer })
}
}, [])
return (
<main role="main" className="error-page">
<div className="error-content">
<h1>Oops! Page not found</h1>
<p>
We couldn't find the page you're looking for.
It may have been moved or deleted.
</p>
{searchEnabled && (
<form action="/search" method="GET" role="search" className="search-form">
<label htmlFor="error-search" className="sr-only">
Search
</label>
<input
type="search"
id="error-search"
name="q"
placeholder="Search..."
aria-label="Search site"
/>
<button type="submit">Search</button>
</form>
)}
{popularLinks.length > 0 && (
<nav aria-label="Suggested pages">
<h2>You might be looking for:</h2>
<ul>
{popularLinks.map(link => (
<li key={link.href}>
<a href={link.href}>{link.label}</a>
</li>
))}
</ul>
</nav>
)}
<a href="/" className="home-link">
← Back to Home
</a>
</div>
</main>
)
}Server Configuration
# Nginx
server {
error_page 404 /404.html;
location = /404.html {
root /var/www/html;
internal;
}
}# Apache .htaccess
ErrorDocument 404 /404.html404 Error Tracking
// middleware.ts (Next.js)
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Log 404s for monitoring
const response = NextResponse.next()
// You can check response status and log accordingly
// This runs before the 404 page renders
return response
}
// Alternative: Track in the 404 page itself
// app/not-found.tsx
'use client'
import { useEffect } from 'react'
import { usePathname } from 'next/navigation'
export default function NotFound() {
const pathname = usePathname()
useEffect(() => {
// Send to error tracking service
fetch('/api/track-404', {
method: 'POST',
body: JSON.stringify({
path: pathname,
referrer: document.referrer,
timestamp: new Date().toISOString()
})
})
}, [pathname])
return (/* ... */)
}SEO Considerations
<head>
<!-- Prevent 404 pages from being indexed -->
<meta name="robots" content="noindex, nofollow">
<!-- Return proper 404 status code (server-side) -->
<!-- Next.js handles this automatically for not-found.tsx -->
</head>Avoid Soft 404s
A branded "page not found" template is still a search bug if the server responds with 200 OK. Search engines can treat that as a soft 404, which wastes crawl budget and may surface broken pages in reports.
Styling Best Practices
.error-page {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
text-align: center;
}
.error-content {
max-width: 500px;
}
.error-page h1 {
font-size: 3rem;
margin-bottom: 1rem;
}
/* Keep consistent with site branding */
.error-page {
font-family: inherit;
background: var(--background);
color: var(--foreground);
}
/* Ensure search is prominent */
.search-form {
margin: 2rem 0;
}
.search-form input {
padding: 0.75rem 1rem;
font-size: 1rem;
width: 100%;
max-width: 300px;
}Verification
- Navigate to a non-existent URL
- Verify HTTP 404 status code is returned
- Check page includes navigation options
- Test search functionality works
- Verify page is not indexed (check robots meta)
- Test on mobile devices
- Verify screen reader announces content properly
- Confirm the missing URL appears as a real 404 in DevTools Network, not a 200 soft-404 response
A custom 404 page must return HTTP status code 404, not 200. Search engines treat 200 responses as valid pages, potentially indexing your error page content.
Use with AI
Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.
Check
Verify implementation
Verify that this website has a custom 404 error page with helpful navigation options. Confirm the route returns HTTP 404 and not a branded page with a 200 response.
Fix
Auto-fix issues
Create a user-friendly 404 page with search functionality and links to important pages. Ensure the server or framework returns status code 404 so the page does not become a soft 404.
Explain
Learn more
Explain how custom 404 pages help retain users who encounter broken links and why returning 200 for missing pages creates soft-404 indexing problems.
Review
Code review
Review templates, server-rendered HTML, and shared components that output markup related to Create a custom 404 error page. Flag exact elements, attributes, and routes where the rendered HTML violates the rule.