稍候片刻,月出文自明。
outputFileTracingExcludes 对 sharp 无效、以及旧的 .edgeone 产物被重复打包。通过构建后清理、删除多余依赖和残留文件,函数包从 141 MiB 降到约 112 MiB,部署通过。随后又修正了 EdgeOne 与 Vercel 的平台差异,包括 rewrites 迁移到 edgeone.json、edge runtime 改为 nodejs、以及从请求头读取地理位置和 IP。最后用 GitHub Actions 实现了从 main 同步到部署分支的自动发布。
前言
之前我一直用 Docker 自托管 Yohaku(mx-space 的 Next.js 前端)。这次想试试腾讯云 EdgeOne Makers(原 EdgeOne Pages)能不能把它跑起来 。结论是能,现在 yohaku-eo.zwh.moe 就跑在 EdgeOne 海外区。不过中间让 AI 替我踩了不少坑:函数包体积超限、平台和 Vercel 的差异,还有后端 CORS。这篇让 AI 自己按时间顺序记录下来分别踩了哪些坑,做了哪些改动。
先看限制
Yohaku 本身的情况:Next.js 16.3.7(Turbopack 构建),pnpm 12 monorepo,还依赖 yohaku-oss 子模块。
EdgeOne Makers 跟这次相关的几条:
项目
说明
Next.js 支持
13.5 到 16,App Router、SSR、ISR、RSC、`proxy.ts` 中间件都支持
Cloud Functions
免费版代码包上限 128 MiB,运行时 Node 20
next.config
`rewrites` / `redirects` 文档写着不支持,建议改用 `edgeone.json`
我用的部署方式是 CLI:edgeone makers deploy 在本地安装依赖、构建,再由 @edgeone/opennextjs-pages 适配器把产物整理成 .edgeone/ 目录上传。目录里有三部分:assets/ 是静态资源,cloud-functions/ssr-node/ 是 SSR 函数,edge-functions/ 是编译后的 proxy.ts。
第一个小坑:新项目直接用 -e preview 部署会报 Please create a production deployment first,必须先有一次 production 部署。
141 MiB:函数包超限
monorepo、pnpm 12、子模块、Next 16 这些我原本担心的地方都没出问题,构建和上传一次就通过了。卡住的是平台检查这一步:
拆开 ssr-node 看,大头是 .next/server(约 72 MB,这部分不好动)。能砍的有这几块:
内容
大小
来源
sharp + libvips
约 19 MiB
Next 的图片优化依赖,但 EdgeOne 有自己的图片处理
`src/`、`public/`
约 12.6 MB
本不该打进服务端包
sourcemap
约 3 MB
运行时用不到
坑一:动态读文件,整个项目被追踪进包
构建日志里其实给过提示:
问题出在生成 OG 图的 og-image.server.ts。它用动态路径去读 resvg 的 wasm 和兜底字体,Turbopack 推断不出会读哪个文件,就把整个项目都追踪了进去。这两个资源本来已经通过 outputFileTracingIncludes 显式包含了,所以加一行 ignore 注释就行:
这一处修改对 Docker 部署同样有用。
坑二:outputFileTracingExcludes 排除不掉 sharp
我第一反应是在 next.config.mjs 里用 outputFileTracingExcludes 排除 sharp,结果试了两种写法都没生效。翻 Next 源码才找到原因:collectBuildTraces 只在非 Turbopack 构建时运行,Turbopack 下 next-server 的依赖追踪由原生代码生成,根本不读这个配置。
最后改成构建完再删。postbuild 跑一个脚本,只在 EdgeOne 构建时生效,从 .next/standalone 里删掉 sharp、@img/sharp-* 和 *.map。适配器是在 postbuild 之后才从 standalone 目录复制文件的,所以删掉的东西不会进函数包。
坑三:158 MiB,上一次的产物被打进来了
改完再部署,体积反而涨到 158 MiB。查下来,多出来的 26 MiB 是上一次本地构建留下的 .edgeone/ 目录。pnpm 有一个 workspace 链接 @yohaku/web -> apps/web,Next 顺着这个链接把旧产物当成依赖追踪了进去,新包里又套了一份旧包。
修法是在 prebuild 里先清掉旧的 .edgeone/cloud-functions 等目录,清理脚本里也顺手删掉 standalone 中残留的 .edgeone。.edgeone/project.json 记录了项目绑定,要保留。
坑四:环境变量传不进构建
为了不影响 Docker 构建,清理脚本一开始靠环境变量 EDGEONE_BUILD 判断要不要执行。后来发现 edgeone makers deploy 执行构建时,不会把终端里设置的环境变量传下去,所以脚本一直什么都没删。
最终改成检测适配器在构建期间写入的标记文件 .edgeone/.image-loader-injected。这样不需要任何配置,Docker 构建也不受影响。
做完这四步,函数包从 141 MiB 降到约 112 MiB,部署终于通过了。
404:"The site does not exist"
中间有一段时间,访问站点所有路径都返回 EdgeOne 网关的 404,连 favicon.ico 这种静态文件也一样,自定义域名和默认域名都是这样。我先后怀疑过 DNS 和同步延迟,最后确认原因很简单:项目还没有一次成功的生产部署(前面的体积问题),域名后面没有任何内容。所以看到网关 404 先去查部署状态,DNS 不用急着怀疑。
另外,CLI 给出的预览链接带 eo_token 参数,大约一小时后会过期,不适合反复测试。绑一个自定义域名会方便得多,海外区也不需要备案。
平台差异的适配
体积问题解决后,站点基本可用。我逐个测了路由,又改了三处:
用 GitHub Actions 自动部署
我希望 EdgeOne 相关的改动单独放在 feat/edgeone-makers 分支,不污染 main,但又要跟上 main 的更新。所以 workflow 这样设计:main 每次有 push,就把 main 合并进部署分支,推回远端,再从合并结果构建部署。
几个要注意的地方:
第一次运行就成功了:依赖安装 32 秒,整个流程大约 6 分钟,SSR 包 101.4 MiB。
小结