自定义错误页:把 404 变成一个有用的页面
默认的 Nginx 错误页很简陋,而且会把用户直接赶走。花十分钟做个像样的 404,能把「流失」变成「继续浏览」。
一、Nginx 最小配置
server {
listen 443 ssl http2;
server_name www.bzii.cn;
root /var/www/baize;
index index.html;
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /404.html {
internal; # 只允许内部跳转,不允许直接访问
root /var/www/baize;
}
location = /50x.html {
internal;
root /var/www/baize;
}
}sudo nginx -t && sudo systemctl reload nginx
internal很重要:没有它的话,用户可以直接访问/404.html,看到一个「正常页面返回 200 状态」的怪现象,对 SEO 不利。
二、易错点:error_page 与 proxy
反代场景下,后端返回的 404 默认会被 Nginx 直接透传,不会用你的错误页。要加一行:
location / {
proxy_pass http://127.0.0.1:3000;
proxy_intercept_errors on; # 关键:拦截后端错误码
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
}如果只想拦截部分状态码:
proxy_intercept_errors on;
error_page 500 502 503 504 /50x.html;
# 404 交给后端自己处理(API 常这么做)三、保持状态码正确
# 默认 error_page 会保持原状态码(404 还是 404)
error_page 404 /404.html;
# 如果写成这样,会返回 200(错误!对 SEO 有害)
error_page 404 =200 /404.html;验证:
curl -o /dev/null -s -w "%{http_code}\n" https://www.bzii.cn/no-such-page
# 应该是 404,不是 200四、SPA 的回退问题
单页应用用 try_files 回退时,所有路径都会返回 200:
location / {
try_files $uri $uri/ /index.html;
}这对 SEO 是个问题:不存在的 URL 也返回 200 和首页内容(软 404)。
两种解法:
1. 前端路由兜底:JS 里匹配不到路由时,渲染一个 404 视图。
default:
app.innerHTML = "<div class=\"empty\"><h1>404</h1><p>页面不存在</p></div>";2. 服务端区分静态资源与页面:
location / {
try_files $uri $uri/ @fallback;
}
location @fallback {
# 静态资源不存在就真返回 404
if ($uri ~* \.(js|css|png|jpg|webp|woff2)$) {
return 404;
}
rewrite ^ /index.html last;
}五、错误页该有什么
一个有用的 404 页面:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex"> <!-- 别被收录 -->
<title>页面不存在 · 白泽网络</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<main>
<h1>404</h1>
<p>这个页面不存在,可能是链接失效或地址输错了。</p>
<nav>
<a href="/">回到首页</a>
<a href="/archive/">浏览归档</a>
<a href="/tags/">按标签找</a>
</nav>
<form action="/search" method="get">
<input type="search" name="q" placeholder="搜索笔记…">
<button type="submit">搜索</button>
</form>
</main>
</body>
</html>关键要素:
- 说清楚发生了什么(不要只写「Error」);
- 给出下一步(回首页、搜索、看归档);
- 保持站点风格一致(别让用户以为到了别的站);
noindex,别让搜索引擎收录。
六、50x 页面不同
服务端错误是临时故障,用户可能会刷新重试:
<h1>服务暂时不可用</h1>
<p>我们正在处理,请稍后重试。</p>
<button onclick="location.reload()">刷新试试</button>
<p class="muted">如果持续出现,请联系 user@example.com</p>还可以加自动重试:
<script>
setTimeout(() => location.reload(), 10000);
</script>自动刷新要谨慎:如果是死循环式的故障,会让用户陷入无限刷新。建议只重试一次。
七、其他状态码
error_page 403 /403.html; # 禁止访问
error_page 429 /429.html; # 请求过于频繁
location = /403.html { internal; }
location = /429.html { internal; }八、验证
# 404
curl -i https://www.bzii.cn/no-such-page | head -3
# 确认内容是自己的错误页
curl -s https://www.bzii.cn/no-such-page | grep "<title>"
# 确认 404 页面不能被直接访问(或访问时是 404)
curl -o /dev/null -s -w "%{http_code}\n" https://www.bzii.cn/404.html
# 50x:临时停掉后端测试
sudo systemctl stop myapp
curl -o /dev/null -s -w "%{http_code}\n" https://www.bzii.cn/api/九、Cloudflare 用户的注意点
Cloudflare 有自己的错误页。想显示你自己的:
- 源站确实返回对应状态码(不要一律返回 200);
- 在 CF 的
Custom Pages里配置,或确保它不拦截(免费版对 5xx 会显示自己的页面)。
# 看错误页到底来自谁
curl -sI https://www.bzii.cn/no-such-page | grep -i "server\|cf-ray"错误页是「体验的最后一道防线」。十分钟的配置,换来的是用户不直接关掉页面。
