真正有效的Error State页面必须明确告知用户发生了什么、说明非用户责任、提供可操作的下一步。需用语义化HTML(如<main role="alert">)、避免技术术语、禁用自动跳转、确保HTTP状态码准确、预埋JS失效的HTML兜底提示。

为什么直接写 <h1>404</h1> 用户会关掉页面
用户看到纯数字错误码(比如 404、500)第一反应是“网页坏了”,而不是“我该点哪里”。真正有效的 Error State 页面必须同时满足三件事:明确告诉用户「发生了什么」、说明「这不是你的错」、给出「下一步能做什么」。
常见错误是堆砌设计感但忽略信息层级——比如把 404 做成动画粒子效果,却没写清楚“你要找的页面不存在”;或者放个返回按钮但没加 history.back() 逻辑,点了没反应。
- 错误页不是展示 CSS 技能的地方,优先保证文字可读、按钮可点击、路径可回退
- 所有文案避免技术术语:
ERR_CONNECTION_REFUSED改成“网络暂时连不上,请检查 Wi-Fi 或稍后重试” - 不要自动跳转(
meta http-equiv="refresh"),打断用户操作流,尤其对屏幕阅读器不友好
用 <main> 包住错误内容,别让辅助工具读到导航栏
很多错误页保留完整 header 和 footer,导致屏幕阅读器先读一遍 logo、菜单、版权信息,最后才听到“抱歉,页面未找到”——信息噪音太大。语义化结构能直接提升可访问性。
正确做法是把错误主体放进 <main>,并用 role="alert" 标记关键状态:
立即学习“前端免费学习笔记(深入)”;
<main role="alert"> <h1>页面找不到了</h1> <p>你访问的地址可能已修改或删除</p> <a href="/">回到首页</a> <button onclick="history.back()">返回上一页</button> </main>
-
<main>是 ARIA 推荐的错误内容容器,比<div>更明确 -
role="alert"会让支持的读屏软件立刻播报内容,无需用户手动聚焦 - 按钮必须带
onclick="history.back()"或监听popstate,否则纯<a href="javascript:history.back()">在部分 iOS Safari 下失效
HTTP 状态码要和服务端一致,别用 200 OK 返回 404 页面
前端单独渲染一个“404 页面”但服务端仍返回 200 OK,会导致搜索引擎收录错误页、监控系统漏报故障、甚至影响 CDN 缓存策略。真实错误状态必须由服务端透出。
- 静态站点(如 Vite/Hugo)需配置服务器:Nginx 加
error_page 404 /404.html,并确保响应头含Status: 404 Not Found - Next.js 用
app/not-found.tsx,Remix 用routes/_error.tsx,它们默认处理状态码 - 手写 Node.js/Express 时,务必调用
res.status(404).sendFile(...),而非res.sendFile(...)后默认 200
加载失败时的兜底提示不能只靠 JS,得有 HTML 原生 fallback
当页面因 JS 报错(如 Uncaught ReferenceError: React is not defined)而白屏,用户看到的是一片空白——此时任何依赖 JS 渲染的错误提示都无效。必须在 HTML 源码里预埋最小可用提示。
例如在 <body> 开头插入不可移除的提示:
<body> <noscript><div class="error-state">请开启 JavaScript</div></noscript> <!-- 其他内容 --> </body>
-
<noscript>是唯一可靠检测 JS 关闭的方式,CSS +@supports都不可信 - 网络中断时,CDN 可能返回缓存的 HTML,所以错误提示文本必须静态写死,不能靠 JS fetch 动态加载
- 如果用微前端,主应用挂了,子应用的错误页根本不会加载——得在主应用最外层 HTML 里写兜底
实际项目中最容易被忽略的,是服务端状态码和前端路由错误页的职责边界:路由级 404(如 /user/999)可以前端捕获,但资源加载失败(GET /api/profile 502)必须由接口响应决定,前端只负责展示,不该擅自改成 200。


















