DOCTYPE必须放在第一行且无前置内容,否则触发怪异模式导致Flexbox/Grid等失效;lang属性须精确到地区码且不可动态修改;charset必须在前1024字节内且为head首个标签;语义化标签需严格遵循DOM层级与可访问性契约。

DOCTYPE 必须放在第一行,且不能有任何前置内容
浏览器解析 HTML 时,只要在 <!DOCTYPE html> 前出现任何字符(包括 BOM、空格、注释、换行),就会触发怪异模式(Quirks Mode)。这时候 Flexbox、Grid、甚至 box-sizing: border-box 都可能失效,样式错乱却查不出原因。
常见错误现象:
- 本地开发看着正常,部署到 Nginx 后布局崩塌
- VS Code 自动插入 UTF-8 BOM(尤其 Windows 环境),导致首行实际是
<!DOCTYPE html> -
<!-- 注释 --><!DOCTYPE html>被当成无效声明
实操建议:
- 用编辑器设置「保存时自动移除 BOM」,VS Code 中配置
"files.autoGuessEncoding": false+"files.encoding": "utf8" - CI 流程中加校验脚本:检查所有
.html文件首行是否严格匹配^<!DOCTYPE html>$ - Webpack/Vite 构建时,通过
html-webpack-plugin或vite-plugin-html注入模板,避免手写出错
lang 属性必须精确到地区码,且不可动态覆盖
<html lang="zh-CN"> 不是可有可无的装饰。它直接影响屏幕阅读器语调、搜索引擎语言判定、甚至某些 CSS 的 :lang() 选择器行为。但很多人写成 lang="zh" 或试图用 JS 动态改 document.documentElement.lang,这两者都无效。
立即学习“前端免费学习笔记(深入)”;
为什么这样做:
-
lang="zh"缺少地区信息,NVDA 可能默认用粤语发音;Google 搜索结果页会降权该页面的简体中文匹配度 -
document.documentElement.lang = 'en-US'对已加载的辅助技术无影响——读屏软件只读取初始 HTML 中的值
实操建议:
- 构建时根据 locale 配置生成不同
index.html,而不是靠 JS 切换 - Next.js/Nuxt 等框架需确认
htmlAttrs或app.html模板中lang是硬编码或服务端注入,而非客户端渲染 - 用 Lighthouse 或 axe CLI 扫描时,
html-has-lang规则会直接报错,别等上线后被审计打回
charset 必须在 前 1024 字节内,且禁用 http-equiv
<meta charset="UTF-8"> 如果出现在 <title> 之后,或用了旧式写法 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">,Safari 和部分 Android WebView 会先按 Latin-1 解析前面的内容,导致中文变乱码——而且这种乱码在 DevTools 里看不到,只在真实设备上暴露。
容易踩的坑:
- 把
<link rel="stylesheet">放在<meta charset>前,CSS 文件里的中文注释或字体名就可能解码失败 - Webpack 的
html-webpack-plugin默认把templateParameters插入<head>开头,但如果模板里手动写了<title>在前,就容易挤掉 charset 位置
实操建议:
- 所有 HTML 模板中,
<meta charset="UTF-8">必须是<head>内第一个标签 - 用 ESLint 插件
eslint-plugin-html配合规则html/valid-meta-http-equiv拦截旧写法 - CI 中用
grep -A 5 '<head>' *.html | head -n 20快速抽检前几行结构
语义化标签不是“换词游戏”,而是 DOM 层级与可访问性契约
把 <div class="header"> 改成 <header> 并不等于完成语义化。真正的约束在于:一个 <main> 页面只能有一个;<nav> 必须包裹导航链接,不能塞搜索框或广告;<section> 必须有 <h2> 或更高级标题作为主题标识。
否则后果很具体:
- NVDA 朗读时跳过
<main>,用户无法用快捷键直达主体内容 - SEO 工具(如 Screaming Frog)将多个
<main>视为结构错误,降低页面权重 - Chrome DevTools 的「Accessibility」面板显示「Landmark region missing label」警告
实操建议:
- 用
document.querySelectorAll('main').length !== 1写成 Jest 测试用例,CI 强制拦截 - React/Vue 组件中禁止直接 return
<div><header>...</header></div>,而应让<header>成为根节点 - 设计师交付的 Sketch/Figma 标注里,要求明确标注「此处为
<aside>,承载相关推荐」,而不是只写「右边栏」
真正卡住团队落地的,从来不是“要不要做”,而是“怎么确保每次提交都不退化”。lang 值写错一次,后续所有自动化测试都可能绕过这个缺陷;<main> 多套一层 <div>,可访问性审计工具就不再信任整个页面结构。工程化不是堆工具,是把关键约束变成不可绕过的检查点。



















