“渐进增强”常被当作装饰性口号:说了,但没人验收。它的可操作定义其实很简单——禁用 JavaScript 后,站点的核心任务是否仍可完成? 本文把它拆成一份能逐条勾选的检查清单,以内容站为例。
基线:无 JS 时,什么必须仍然成立
- 所有内容可读:正文由服务端渲染进 HTML,不靠前端 fetch 填充(用 fetch 自取数据再注入 DOM 的做法,JS 一关就是白页——这是“SSR 页面 fetch 自己静态文件”踩坑的孪生问题)。
- 导航可用:站内跳转是真实的
<a href>,不是onclick里写router.push。增强脚本可以在点击时拦截走 SPA,但href 本身必须是有效目标。 - 表单可提交:搜索、订阅、评论用原生
<form action method>提交到服务端端点,JS 只是把体验升级成“行内反馈”。服务端端点要能独立处理并返回结果页。 - 状态可见:收藏、进度这类增强若依赖 JS,无 JS 时应退化为“该功能不显示”,而不是“显示一个坏掉的交互”。
把增强做成“加法”而非“替换”
一个正确的订阅表单是这样分层的:
<form method="post" action="/api/subscribe">
<input name="email" type="email" required />
<button type="submit">发送确认邮件</button>
</form>
// 增强:拦截提交,改成 fetch + 行内反馈,失败则回退原生提交
form.addEventListener('submit', async (e) => {
e.preventDefault();
try {
const res = await fetch(form.action, { method: 'POST', headers: {...}, body: ... });
// 渲染成功/错误到 aria-live 区
} catch {
form.submit(); // 网络/payload 失败:退回原生整页流程
}
});
关键在最后那行 form.submit()——增强路径失败时,交回基线路径,而不是把用户困在一个“点了没反应”的按钮上。渐进增强的精髓不是“有 JS 更好”,而是“JS 挂了的瞬间自动降级回可用态”。
内容渲染的“服务端为准”
静态优先框架(如 Astro)默认把内容构建进 HTML,天然满足基线。风险来自“过度客户端化”:为了动效把整页数据塞进 fetch+useState,等于主动放弃无 JS 基线。判断标准——
- 内容属于信息(文章、列表、导航)→ 服务端渲染,客户端只加动效;
- 内容属于即时交互态(输入校验反馈、拖拽结果)→ 才交给客户端。
把第一条守住的站点,JS 全关仍能当文档站用;把第二条误当第一条的站点,JS 一挂就白屏。
验收:一份可勾选的清单
每个交互组件上线前,在 DevTools 禁用 JS 逐条验证:
- 页面主体内容显示完整,无空白占位区
- 每个导航项是可回车跳转的真实链接
- 每个表单能用原生提交得到一个正确结果页
- 报错路径不依赖 JS 才能看到提示(服务端渲染的错误页存在)
- 关键 SEO 文本(标题/描述/正文)在
view-source里可见,非 JS 注入
这份清单跑通,“渐进增强”就从信念变成了可回归的工程事实。
小结
真正的健壮不是“什么都能用”,而是“最坏情况下仍能用”。把无 JS 基线设成验收项,你同时赚到的还有:搜索引擎友好、无障碍友好、弱网友好、以及“某个脚本 404 了全站不崩”的韧性。渐进增强从来不是怀旧,是用最便宜的方式买最贵的保险。