You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js v12.1 chunks等脚本偶发阻塞渲染原因及解决方法

Next.js v12.1 自托管场景下框架内置脚本偶发渲染阻塞问题解答

一、脚本时而阻塞渲染、时而不阻塞的根本原因

  • Next.js v12.1 版本默认的脚本注入逻辑存在设计局限:框架自动生成的核心运行时chunk、三类manifest清单脚本默认以无defer/async属性的普通<script>标签注入页面,浏览器遇到这类标签时,默认会暂停HTML解析流程,等待脚本下载、执行完成后才继续渲染,这是产生阻塞的基础前提。
  • 性能检测工具对渲染阻塞脚本的通用判定规则是:首屏绘制(FCP)前发起网络请求加载、且未标记异步/延迟加载属性的脚本,或执行时阻塞主线程时长超过阈值的脚本,才会被标记为阻塞资源,这是偶发判定差异的核心判断标准。
  • 桌面端偶发不触发阻塞告警的原因:测试场景下的缓存状态、网络状态存在波动。如果脚本已经被浏览器强缓存命中,脚本直接从本地内存/磁盘读取,没有网络下载耗时,执行耗时也极短,达不到工具的阻塞判定阈值,就不会被标记;冷加载无缓存时,如果桌面端网络带宽足够高、延迟足够低,脚本下载+执行的总耗时偶尔也会低于判定阈值,就会出现同环境下时而告警、时而正常的现象。
  • 移动端几乎每次都触发阻塞告警的原因:移动端普遍网络带宽更低、往返延迟更高,且设备CPU性能弱于桌面端,无缓存场景下脚本下载耗时久,有缓存场景下脚本执行的主线程阻塞时长也更容易超过判定阈值,因此几乎每次测试都会被识别为渲染阻塞资源。
  • 额外触发因素:_buildManifest.js、_ssgManifest.js、_middlewareManifest.js三个清单脚本在v12.1版本中是硬编码注入到文档头部的,完全没有做加载策略优化,是阻塞问题的高发来源。

二、实现这类脚本始终非阻塞加载的配置调整方案

所有调整均基于Next.js v12.1版本的原生能力实现,不需要额外升级大版本:

  • 第一步:自定义pages/_document.js,给所有框架自动注入的核心chunk添加defer属性,禁止使用async(会打乱脚本执行依赖顺序,引发运行时报错)。参考实现代码:
import Document, { Html, Head, Main, NextScript } from 'next/document'

class MyDocument extends Document {
  render() {
    return (
      <Html lang="zh-CN">
        <Head />
        <body>
          <Main />
          <NextScript
            scriptLoader={{
              ...this.props.scriptLoader,
              scripts: (this.props.scriptLoader?.scripts || []).map(script => ({
                ...script,
                props: {
                  ...script.props,
                  defer: true,
                  async: false
                }
              }))
            }}
          />
        </body>
      </Html>
    )
  }
}

export default MyDocument
  • 第二步:通过自托管服务的响应拦截逻辑,处理三个硬编码注入的manifest脚本:移除Next.js默认注入在头部的无属性manifest标签,改为在body末尾注入带defer属性的对应脚本。Node.js自托管场景的参考拦截逻辑:
// 托管服务响应拦截中间件示例
const interceptRenderResponse = (app, buildId) => {
  const originalSend = app.response.send
  app.response.send = function (htmlContent) {
    if (typeof htmlContent === 'string' && htmlContent.includes('_next/static')) {
      // 移除默认注入的无属性manifest脚本
      htmlContent = htmlContent.replace(
        /<script src="\/_next\/static\/[^"]+\/_(build|ssg|middleware)Manifest\.js"><\/script>/g,
        ''
      )
      // 插入带defer属性的manifest脚本,保证执行顺序
      const manifestTags = `
        <script defer src="/_next/static/${buildId}/_buildManifest.js"></script>
        <script defer src="/_next/static/${buildId}/_ssgManifest.js"></script>
        <script defer src="/_next/static/${buildId}/_middlewareManifest.js"></script>
      `
      htmlContent = htmlContent.replace('</body>', `${manifestTags}</body>`)
    }
    return originalSend.call(this, htmlContent)
  }
}
  • 第三步:开启v12.1版本内置的生产优化项,降低脚本本身的体积和执行耗时:
    • 在next.config.js中开启swcMinify: true,使用SWC替代Terser做代码压缩,平均可减小chunk体积15%-20%
    • 开启experimental.optimizeCss: true,避免脚本等待CSS加载产生额外阻塞
    • 自托管场景可配置output: 'standalone',剔除不必要的运行时代码注入
  • 第四步:给/_next/static/路径下的所有静态资源配置强缓存响应头,设置Cache-Control: public, max-age=31536000, immutable,用户二次访问时直接从本地缓存读取脚本,完全消除网络下载阶段的耗时,进一步降低阻塞概率。

注意:defer属性会保证脚本在整个HTML文档解析完成后、DOMContentLoaded事件触发前按标签顺序执行,完全适配Next.js的chunk依赖顺序,不会引发运行时错误,是这类框架脚本非阻塞加载的最优选择。


内容的提问来源于stack exchange,提问作者Noitidart

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 12:48:15