必须顶格写在第一行且前面不能有任何字符(包括空格、bom、注释),否则浏览器触发怪异模式,导致box-sizing、height百分比、flex等渲染异常。

DOCTYPE 必须顶格首行,且不能带 BOM 或空格
浏览器一读到非 的内容(哪怕一个空格、BOM 字节或注释),就会直接进入怪异模式(Quirks Mode)。这时候 <code>box-sizing、height 百分比、inline-block 垂直对齐全会失常,调试时根本找不到源头。
实操建议:
必须是文件第一字符,前面零容忍——连 <code><!-- 注释 -->都不行- 用编辑器检查 BOM:VS Code 右下角显示“UTF-8 with BOM”就点它转成“UTF-8”
- 服务端模板(如 EJS、Nunjucks)若在
前拼接了空字符串或换行,也会触发怪异模式——得查模板逻辑,不是改前端就能修
lang 属性必须用 IANA 标准值,且不可为空
或 看似无害,实际会让 Safari 的 <input type="date"> 直接不渲染,语音合成工具把中文读成日语,SEO 也收不到语言信号。
实操建议:
- 中文站点只认
zh-CN(不是zh、cn、zh-cn小写也不行) - 多语言项目别静态写死——得靠 JS 动态设
document.documentElement.lang,否则切换语言后 screen reader 仍读旧值 - Next.js / Nuxt 的
html.lang配置项若没生效,先检查 _document.tsx 或 app.vue 是否覆盖了根标签
charset 必须写成 UTF-8,且位置必须在 <title></title> 之前
</meta charset="utf8"> 或 <meta charset="UTF8"> 在部分 Android WebView 和邮件客户端里会解析失败,中文变方块;更隐蔽的是,如果它写在 <title>网页标题</title> 后面,浏览器已按默认编码(如 ISO-8859-1)解码了标题里的中文,再读 charset 就晚了。
实操建议:
- 严格按 IETF BCP 47 写成
<meta charset="UTF-8">:大写 UTF、短横线、大写 E - 该标签必须是
中第一个子元素,比<title></title>、<link>、<script></script>都要靠前 - Vite 插件(如
vite-plugin-html)若自动注入<meta>到<title></title>后,得手动配置injectOptions.viteHtmlPluginOptions调整插入顺序
语义化标签不是“加分项”,而是 DOM 操作和可访问性的硬依赖
用五个 <div class="item"> 套着 <code><p></p>,三个月后你用 document.querySelector('.list .item') 找不到目标,因为没人记得哪个 .item 是列表项、哪个是侧边栏卡片。屏幕阅读器也压根不知道哪块是主内容。
实操建议:
-
<main></main>必须有且仅有一个——它是 AT 工具跳过导航的锚点,也是 SEO 提取核心内容的依据 -
<nav></nav>只包真正导航链路(顶部菜单、面包屑、页脚链接组),筛选栏、分页器塞进去会导致 AT 报告“发现 4 个导航区”,用户直接放弃 -
<section></section>不是<div> 替代品:它必须有 <code><h2></h2>~<h6></h6>作为标题,否则语义断裂;没标题的“区块”老实用<div><p>最常被忽略的其实是构建流程中的隐性破坏:Vite/webpack 的 HTML 插件可能错位插入 meta 标签,SSR 模板可能截断 <code>开头,CI 流水线又不校验这些——问题只在低版本安卓机或屏幕阅读器里爆发,等发现时已经线上翻车。











