放错位置会使tti增加300–800ms,因同步脚本阻塞dom构建;应禁用中无defer/async的脚本,主应用用defer,第三方用async,ssr水合脚本必须defer以确保dom就绪。

<script></script> 放错位置,TTI 直接多出 300–800ms。这不是理论值,是真实 Lighthouse 和 Web Vitals 采集的中位数差距。
为什么 <script></script> 在 里会让 TTI 延迟
浏览器解析 HTML 到 <script src="app.js"></script> 时,会立刻暂停 DOM 构建、等待 JS 下载 → 解析 → 执行完毕,才能继续。哪怕这个脚本只做 console.log,它也卡住整个渲染流水线。
常见错误现象:
- 首屏文字/按钮已渲染,但点击无响应(JS 还没执行完)
- Lighthouse 报 “Reduce JavaScript execution time”,主线程被占满
- FCP(首次内容绘制)尚可,LCP(最大内容绘制)达标,但 TTI 却远超 3.5s
实操建议:
- 禁止在
中放未加defer或async的同步脚本 - 第三方统计、埋点脚本必须加
async,否则它们常含阻塞逻辑 - 若必须在
初始化(如主题切换、字体预加载),改用内联小脚本 +type="module"(自动 defer)
defer 和 async 对 TTI 的实际差异
两者都让脚本不阻塞 HTML 解析,但执行时机不同:defer 等 DOM 解析完再按顺序执行;async 下载完就执行,顺序不确定。
影响 TTI 的关键点:
-
async脚本可能在 DOM 尚未就绪时执行,document.getElementById返回 null —— 导致报错或降级逻辑触发,延长水合时间 -
defer脚本能安全操作 DOM,适合主应用入口(如 React.render()、Vue.createApp().mount()) - 多个
defer脚本保持声明顺序,利于依赖管理;async无法保证执行顺序,慎用于有依赖的模块链
示例对比:
<!-- 拖慢 TTI:无属性,阻塞解析 --> <script src="vendor.js"></script><p><!-- 推荐:defer 主应用,async 第三方 --> <script defer src="main.js"></script><script async src="analytics.js"></script></p>
SSR/同构场景下 <script></script> 位置与增量水合的关系
服务端渲染(SSR)输出 HTML 后,客户端 JS 必须“水合”(hydrate)DOM。水合时机直接受 <script></script> 加载位置影响。
容易踩的坑:
- 把水合脚本放在
底部,但 DOM 中有大量未标记use client的组件 —— 客户端 JS 一执行就发现 mismatch,触发全量水合,TTI 回退到传统 SSR 模式 - 用
async加载水合脚本,导致ReactDOM.hydrateRoot在document.body还没完全解析完就调用,抛出 “Target container is not a DOM element” 错误 - 第三方 SDK(如客服浮窗)打包进主 chunk,即使用户没滚动到页脚,也强制水合,白白增加主线程负担
实操建议:
- 水合入口脚本必须用
defer,确保 DOM 已 ready - 非首屏区域组件(如页脚、评论区)用
React.lazy+Suspense,配合timeoutMs控制水合时机 - 第三方脚本通过动态
import()+ 事件委托加载,例如用户点击客服图标后再import('live-chat-sdk')
真正影响 TTI 的从来不是脚本大小,而是它何时能开始执行、执行时 DOM 是否可用、执行后是否立刻接管交互。一个 defer 属性,比压缩 100KB 代码对 TTI 的收益更直接——尤其当你的页面已经启用 SSR 且首屏 HTML 很快抵达时。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











