构建产物体积超平台限制:3 个原因 + 修复路径

Vercel / Cloudflare Pages 等都有体积上限——一个大依赖足以爆掉。

部署日志里出现 Error: The Serverless Function exceeds the maximum size limit of 50 MB(Vercel)、Functions size exceeds maximum allowedPages output exceeds 25 MiB per file / 20,000 files total(Cloudflare Pages)——这是宿主硬限制,重试再多次也不会过。每家平台都有不同的体积上限:Vercel Serverless 单 function 50 MB、Cloudflare Workers 1 MB(免费 / 10 MB Paid)、Cloudflare Pages 25 MiB 单文件、Netlify Functions 50 MB。本文给一条按”找到大头 → 砍掉 → CI 拦截”的修复路径。

常见原因

按命中率从高到低排。

1. 一个依赖打包后体积惊人

puppeteer(带 Chromium ~170 MB)、canvassharp@ffmpeg/coretensorflow.jsplaywright 这一类原生 / 浏览器引擎依赖是常见元凶。看似只 import 了一个函数,实际把整个二进制都打进了产物。

典型报错:

Error: The Serverless Function "api/screenshot" is 187 MB which exceeds the maximum size limit of 50 MB.

如何判断du -sh node_modules/* | sort -h | tail -20 找出最大的 20 个依赖,对照部署产物。

2. 静态资源未压缩或被重复打入产物

原图未压缩(4 MB 的 JPG)、字体文件全 weight 全字符集都打进去(每个 woff2 几百 KB)、或者 public/src/assets/ 同时引用了同一份图片,导致最终 bundle 出现两份。

如何判断ls -lhS dist/ | head 列出最大的静态文件,目测哪些超 500 KB 但其实可以小很多。

3. tsconfig 没排除测试 / 示例 / dev 工具

tsconfig.jsonexclude,build 把 *.test.ts__fixtures__/storybook/examples/ 全编译进 server bundle。表现:明明业务代码不多,产物却几百 MB。

如何判断:build 后 find dist -name "*.test.*" -o -name "*fixture*",能找到就是没排干净。

4. Server / client bundle 边界没分清

把 server-only 的依赖(如 pdf-parse@aws-sdk/client-s3)错误地 import 到了 client component,bundler 把它打到了 browser bundle。或者反过来:本应留在 server 的 markdown / 模板字符串被 inline 到了每个 page。

如何判断:跑 bundle analyzer,看 client chunk 里出现了哪些理应只在服务端的包名。

5. Source map 进了生产产物

vite.config.ts / next.config.js 默认在 production 也输出 source map,每个 chunk 旁边都跟一份同体积的 .map 文件,直接翻倍产物大小。

如何判断ls dist/assets/*.map | wc -l 大于 0 且 .map 文件总大小接近代码总大小。

最短修复路径

Step 1:定位”谁最大”

先看产物物理体积:

npm run build
du -sh dist/
du -sh dist/* | sort -h | tail -10

再跑 bundle visualizer 找出每个 chunk 里的依赖构成:

# Vite / Astro
npx vite-bundle-visualizer

# Next.js
ANALYZE=true npm run build
# 需要先装 @next/bundle-analyzer 并在 next.config.js 里配置

# Webpack 直接用
npx webpack-bundle-analyzer dist/stats.json

打开生成的 HTML,按面积找出占比最大的 1-3 个依赖,那就是要砍的目标。

Step 2:把大依赖换轻量版本或推到 server-only

moment (~290 KB)date-fns (~13 KB tree-shaken) 或 dayjs (~7 KB)
lodash (~70 KB)lodash-es 按需 import,或原生 ES 方法
axios (~30 KB)原生 fetch
puppeteer@sparticuz/chromium + puppeteer-core(serverless 专用)
完整 chart.js按需 import chart.js/auto 子模块

如果依赖必须用且必须重(比如 sharppuppeteer),把它从 serverless function 拆出来:迁到独立 worker、用外部服务(Browserless、Cloudinary),或者改 build-time 处理。

Step 3:收紧 tsconfig 和 build include / exclude

// tsconfig.json
{
  "compilerOptions": { "...": "..." },
  "exclude": [
    "node_modules",
    "dist",
    "**/*.test.ts",
    "**/*.spec.ts",
    "**/__tests__/**",
    "**/__fixtures__/**",
    "examples/**",
    "storybook/**"
  ]
}

Vercel 还可以在 vercel.json 里精细排除:

{
  "functions": {
    "api/**/*.ts": {
      "includeFiles": "lib/**",
      "excludeFiles": "{tests,fixtures}/**"
    }
  }
}

Step 4:压缩静态资源 + 关 source map

图片:

# 一次性把 public/ 下所有 PNG / JPG 压缩
npx @squoosh/cli --mozjpeg auto public/**/*.{jpg,jpeg}
npx @squoosh/cli --oxipng auto public/**/*.png

# 或者全部转 WebP / AVIF
npx @squoosh/cli --webp auto public/**/*.{jpg,png}

关掉 production source map:

// vite.config.ts
export default defineConfig({
  build: {
    sourcemap: false, // 或 'hidden' 上传到 Sentry 后不发布
  },
});

Step 5:在 CI 里加 bundle-size 守门

# .github/workflows/bundle-size.yml
name: Bundle Size
on: [pull_request]
jobs:
  size:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci && npm run build
      - name: Check size
        run: |
          SIZE=$(du -sm dist | cut -f1)
          echo "dist size: ${SIZE} MB"
          if [ $SIZE -gt 40 ]; then
            echo "Bundle exceeds 40 MB threshold"
            exit 1
          fi

设的阈值比平台硬限制低 20%,给未来增长留 buffer。

预防建议

  • 每加一个新依赖前先去 bundlephobia.com 查 minified + gzipped 体积,超 50 KB 的要找替代
  • CI 里加 bundle-size 检查,阈值设在平台硬限制的 70-80%
  • 区分 server-only 和 client-only 依赖,server 包别 import 到客户端组件
  • 大型二进制(PDF / 视频 / 模型权重)放对象存储,运行时拉取,不打进 bundle
  • 每季度跑一次 npx depcheck 删未使用依赖;跑 bundle visualizer 复盘 top 5 大依赖是否还都需要

相关阅读

标签: #构建报错 #部署 / 托管 #排查