link标签必须放在script之前,因为浏览器解析HTML是单线程自上而下进行的,script会阻塞DOM构建,若CSS未就绪,JS调用getComputedStyle、offsetHeight等依赖CSSOM的API将出错,导致样式缺失、布局塌陷或UI不一致。

CSS必须在JS执行前就位,否则样式缺失或重绘会直接破坏UI一致性
为什么link标签必须出现在script之前?
浏览器解析HTML是单线程自上而下进行的。一旦遇到script标签(尤其是未加async或defer的),它会立即暂停DOM构建,去下载、解析、执行JS。如果此时CSS还没加载完,document.styleSheets可能为空或不完整,JS里调用getComputedStyle、offsetHeight或基于样式的条件逻辑就会出错。
常见错误现象包括:
-
getComputedStyle(el).display返回"none"或""(实际元素已渲染) - 组件首次挂载时高度计算为
0,导致布局塌陷 - React/Vue中依赖CSS类名做状态判断(如
el.classList.contains("loaded"))失败
style和link的加载时机差异
内联style标签内容在解析时同步注入CSSOM,而外部link需发起网络请求。但无论哪种,只要写在script之前,浏览器都会优先处理完CSSOM再继续——这是关键。
立即学习“前端免费学习笔记(深入)”;
以下写法是安全的:
<head> <link rel="stylesheet" href="base.css"> <link rel="stylesheet" href="theme.css"> <script src="app.js"></script> </head>
而下面这段会导致问题:
<head> <script src="utils.js"></script> <link rel="stylesheet" href="app.css"> </head>
因为utils.js执行时,app.css尚未参与CSSOM构建。
现代框架打包场景下的隐性风险
即使使用Webpack/Vite,若配置了mini-css-extract-plugin或css chunk异步加载,仍可能触发竞态:
- JS入口文件里
import './App.css',但CSS chunk被标记为async,加载时机不可控 - 服务端渲染(SSR)中,
renderToString生成的HTML不含CSS,客户端水合(hydration)时样式未就绪,导致FOUC或布局跳变 - React 18并发渲染下,
useLayoutEffect读取尺寸时CSSOM未合并完成,返回旧值
解决办法不是禁用CSS分割,而是确保关键CSS(如布局、字体、主题色)通过rel="preload"或内联方式提前注入:
<link rel="preload" href="critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
唯一可接受的例外:动态样式注入
只有当JS明确负责样式注入且不依赖已有CSSOM时,才可把script放前面,例如:
- 运行时主题切换器,在
DOMContentLoaded后动态创建<style></style>节点 - 微前端子应用独立样式隔离,由主应用JS控制加载顺序
- 实验性功能(如CSS容器查询polyfill)需要先加载JS才能识别并补全CSS能力
但这类场景必须主动检查document.styleSheets.length或监听load事件,不能假设样式已就绪。
真正容易被忽略的是:框架文档常默认你已理解CSSOM阻塞机制,而实际项目中,90%的“样式不生效”“高度算不准”问题,根源都在link与script的物理顺序或打包产物的chunk依赖关系上,而非代码逻辑本身。


















