首页
/ @remotion/vercel:在 Vercel Sandbox 中渲染 Remotion 视频的完整技术解析

@remotion/vercel:在 Vercel Sandbox 中渲染 Remotion 视频的完整技术解析

2026-09-07 16:42:44作者:裘旻烁

本文围绕 Remotion 仓库中的 @remotion/vercel 包(packages/vercel/README.md)展开,讲解如何在 Vercel Sandbox 中创建渲染环境、上传项目 Bundle、执行视频/静帧渲染、跟踪进度并把产物上传到 Vercel Blob 的完整链路,所有结论均基于仓库内 packages/vercel/src/index.ts 等源码。

包定位与公开 API

@remotion/vercel 的官方定位是“Render Remotion videos on Vercel Sandbox”(在 Vercel Sandbox 上渲染 Remotion 视频),当前仓库内版本为 4.0.521,License 为 Remotion License(见 packages/vercel/package.json)。它依赖 @remotion/rendererremotion,并以 @vercel/sandbox >= 1.0.0 作为 peer dependency(开发中固定使用 1.6.0,配套 @vercel/blob 2.3.0)。

src/index.ts 的导出清单看,该包对外暴露 6 个运行时 API 和一批类型:

导出 类型 作用
createSandbox 函数 创建一个安装好系统依赖、JS 依赖、headless 浏览器与渲染脚本的沙箱
addBundleToSandbox 函数 把本地 remotion bundle 产物递归上传进沙箱
renderMediaOnVercel 函数 在沙箱内渲染视频(支持常规与 detached 两种模式)
renderStillOnVercel 函数 在沙箱内渲染单帧静图
getRenderProgress 函数 轮询 detached 渲染任务的文件式进度
uploadToVercelBlob 函数 把沙箱内产物上传到 Vercel Blob,返回 URL
类型导出 type VercelSandboxRenderProgressVercelBlobUploadOptionsChromiumOptionsCodec 等(大部分自 types.ts@remotion/renderer 再导出)

安装与版本约束

README 给出的安装方式:

npm install @remotion/vercel --save-exact

两条必须遵守的版本约束(来自 README 与 package.json):

  1. 所有 remotion@remotion/* 包必须对齐同一版本,需去掉版本号前的 ^ 使用精确版本;
  2. @vercel/sandbox 是 peer dependency,调用方需自行安装(>=1.0.0)。

另外从沙箱初始化逻辑看,包内渲染脚本由构建产物(generated/*-script)注入,包内部通过 remotion/version 读取版本号,在沙箱内以精确版本安装 @remotion/renderer@<VERSION>@remotion/compositor-linux-x64-gnu@<VERSION>(见 internals/install-js-dependencies.ts),也就是说沙箱内的渲染器版本永远与本地 @remotion/vercel 版本一致,这也是“版本必须对齐”这条约束的底层原因。

createSandbox:一步准备一个可渲染沙箱

createSandbox 是整个流程的入口,完整实现在 src/create-sandbox.ts。签名与默认值:

createSandbox({
  onProgress?,            // (update: {progress, message}) => void | Promise<void>
  resources = {vcpus: 4}, // Vercel Sandbox 的 resources 参数,默认 4 vCPU
  timeoutInMilliseconds = 5 * 60 * 1000, // 沙箱创建/初始化超时,默认 5 分钟
} = {})

它返回 VercelSandbox——即 Sandbox & AsyncDisposable(定义见 types.ts),意味着可以用 await using 语法自动停止沙箱:创建时通过 internals/disposable.ts 给沙箱挂了 [Symbol.asyncDispose],dispose 时调用 sandbox.stop()

沙箱的准备工作按两个加权阶段推进(onProgress 的进度权重:系统依赖 75%、下载浏览器 25%):

  1. 创建沙箱runtime: 'node24',即 Node 24 运行时;
  2. 安装系统依赖(75%):通过 sudo dnf install 安装 headless Chromium 在 Amazon Linux 2023 上运行所需的一组库:nssatkat-spi2-atkcups-libslibdrmlibXcompositelibXdamagelibXrandrmesa-libgbmalsa-libpangogtk3,以及补丁工具链 patchelfzstdbinutils(见 internals/install-system-dependencies.ts)。进度是通过统计命令 stdout 行数(源码注释说明经验值为 272 行)线性估算的;
  3. 安装 JS 依赖:在沙箱内执行 pnpm i @remotion/renderer @remotion/compositor-linux-x64-gnu @vercel/blob,版本锁定为当前包版本;
  4. 修补 compositor:Vercel Sandbox 的 Amazon Linux 2023 自带 glibc 2.34,而 Remotion 的 compositor 二进制要求 glibc 2.35。internals/patch-compositor.ts 会下载 Ubuntu 22.04 的 libc6 2.35 deb 包(主源为 Launchpad,备用源为 remotion.media),解压后用 patchelfremotion 二进制的动态链接指向捆绑的 glibc。源码注释明确指出:Remotion 并不官方支持 glibc 2.34,但可以通过这种方式打补丁;且只有 remotion 二进制需要修补,ffmpeg/ffprobe 在 glibc 2.34 下工作正常;
  5. 下载 headless 浏览器(25%):写入并执行 ensure-browser.mjs,以 JSON 日志形式回报 browser-progress 百分比(见 internals/install-browser.ts);
  6. 写入渲染脚本:向沙箱写入 package.json{"type": "module"})以及 render-video.mjsrender-still.mjsupload-blob.mjs 三个脚本,后续渲染命令直接调用它们。

addBundleToSandbox:上传项目 Bundle

渲染前需要把 npx remotion bundle 生成的静态产物传进沙箱。src/add-bundle-to-sandbox.tsaddBundleToSandbox({sandbox, bundleDir}) 行为如下:

  • 递归读取 bundleDir 下所有文件,统一转成 POSIX 分隔路径;
  • 先在沙箱内按祖先目录逐一 mkDir,再批量 writeFiles 上传;
  • 所有文件统一放在沙箱内的 remotion-bundle/ 目录下(常量 REMOTION_SANDBOX_BUNDLE_DIR,见 internals/add-bundle.ts)。渲染时浏览器加载的 URL 因此固定为 /vercel/sandbox/remotion-bundle
  • 目录创建或文件上传失败时,经由 internals/format-sandbox-error.ts 重新抛出带操作上下文(如“upload N bundle file(s)”)的错误,便于定位。

renderMediaOnVercel:渲染视频

完整实现在 src/render-media-on-vercel.ts。这是一个通过重载区分两种模式的函数:

  • 常规模式detached 缺省或 false):阻塞等待渲染结束,返回 {sandboxFilePath, contentType},产物留在沙箱文件系统中,等待后续 uploadToVercelBlob
  • detached 模式detached: true):必须同时提供 vercelBlob: {blobToken, access, blobPath?},立即返回 {sandboxId, cmdId, outputFile},由沙箱后台继续渲染,并用 getRenderProgress 轮询结果。

完整参数与默认值

以下参数表全部来自源码中解构默认值:

参数 默认值 说明
sandbox 必填 createSandbox 返回的沙箱实例
compositionId 必填 目标 Composition 的 id
inputProps 必填 传给 Composition 的 props
outputFile /tmp/video.mp4 沙箱内输出路径
codec h264 视频编码(类型 Codec@remotion/renderer 再导出)
crf null 恒定质量因子
imageFormat / pixelFormat null 帧图像格式与像素格式
envVariables {} 注入渲染进程的环境变量
frameRange null 只渲染指定帧区间
everyNthFrame 1 抽帧渲染步长
proResProfile null ProRes 档位
chromiumOptions {} 附加 Chromium 启动参数
scale 1 输出缩放比例
preferLossless false 偏好无损编码
enforceAudioTrack false 强制包含音轨
disallowParallelEncoding false 禁止并行编码
concurrency null 并发帧数
metadata null 写入容器的元数据
licenseKey null Remotion 企业授权密钥
videoBitrate / audioBitrate / encodingMaxRate / encodingBufferSize null 码率相关(类型 Bitrate
muted false 静音输出
numberOfGifLoops null GIF 循环次数
x264Preset / gopSize null H.264 预设与 GOP 大小
colorSpace default 色彩空间
jpegQuality 80 JPEG 帧质量
audioCodec null 音频编码
logLevel info 日志级别
timeoutInMilliseconds 30000 浏览器/Composition 打开超时
forSeamlessAacConcatenation false AAC 无缝拼接
separateAudioTo null 单独输出音频文件路径
hardwareAcceleration disable 硬件加速开关(沙箱环境默认关闭)
offthreadVideoCacheSizeInBytes / mediaCacheSizeInBytes / offthreadVideoThreads null 离屏视频缓存与线程
sampleRate 48000 音频采样率
detached false 是否后台渲染
detachedSandboxTimeoutInMilliseconds 30 * 60 * 1000 detached 模式下沙箱超时延长时长(30 分钟)

底层执行方式

函数把上述参数组装成 renderConfig,其中强制写死了几个与本地渲染不同的字段:chromeMode: 'headless-shell'browserExecutable: nullbinariesDirectory: nullrepro: false,以及 serveUrl: '/vercel/sandbox/remotion-bundle'。随后:

const renderCmd = await sandbox.runCommand({
  cmd: 'node',
  args: ['render-video.mjs', JSON.stringify(renderConfig)],
  detached: true,
  env: vercelBlob ? {BLOB_READ_WRITE_TOKEN: vercelBlob.blobToken} : undefined,
});

即:把整个渲染配置作为 JSON 传给沙箱内的 render-video.mjs 脚本,脚本内部再调用 @remotion/renderer 完成渲染,并以 JSON 行形式把进度打到 stdout。常规模式下,客户端逐行解析 stdout 日志(非 JSON 的行直接忽略),把 opening-browserselecting-compositionrender-progress 三个阶段透传给 onProgress,最后 wait() 等待命令结束;退出码非 0 时抛出 Render failed: <stderr> <stdout>。detached 模式则先 sandbox.extendTimeout(detachedSandboxTimeoutInMilliseconds) 延长沙箱寿命,然后立即返回 {sandboxId, cmdId, outputFile},供后续轮询。

renderStillOnVercel:渲染静帧

实现在 src/render-still-on-vercel.ts,参数更精简:

参数 默认值
outputFile /tmp/still.png
frame 0
imageFormat png(类型 StillImageFormat
jpegQuality 80
scale 1
logLevel info
timeoutInMilliseconds 30000
chromiumOptions / envVariables {} / {}
offthreadVideoCacheSizeInBytes / mediaCacheSizeInBytes / offthreadVideoThreads / licenseKey 均可选

执行方式与视频渲染一致:node render-still.mjs <jsonConfig>,同样以 JSON 行协议回报 opening-browserselecting-compositiondone(携带 sizecontentType),成功返回 {sandboxFilePath, contentType}

getRenderProgress:轮询 detached 任务

detached 模式下的进度追踪实现在 src/get-render-progress.ts。它不依赖命令句柄,而是按“文件 + 命令状态”双通道读取:

  1. Sandbox.get({sandboxId}) 重新附着沙箱,失败即返回 {stage: 'expired'}
  2. sandbox.getCommand(cmdId) 获取渲染命令对象,识别 sandbox_stopped 一类错误码同样归为 expired
  3. 读取沙箱内固定路径 /vercel/sandbox/progress.json(沙箱内渲染脚本把最新进度写在这里);文件不存在且命令尚未退出时返回 {stage: 'starting', overallProgress: 0}
  4. 文件存在但命令退出码非 0 时,收集 stderr/stdout 组装错误信息返回 error

返回值是联合类型 RenderProgresstypes.ts),覆盖完整生命周期:

starting → opening-browser → selecting-composition → render-progress
        → (detached 时沙箱内自动) uploading → done | error | expired

其中 done 携带 {url, size, contentType, overallProgress}——detached 模式下沙箱内的渲染脚本会使用 BLOB_READ_WRITE_TOKEN 直接把产物上传到 Vercel Blob,因此 done 里的 url 就是可直接下载的产物地址。

uploadToVercelBlob:上传产物到 Blob

常规模式渲染完产物只存在于沙箱文件系统中,需要显式上传。src/upload-to-vercel-blob.tsuploadToVercelBlob({sandbox, sandboxFilePath, blobPath?, contentType, blobToken, access})

  • blobPath 缺省时自动生成 renders/{uuid}{原文件扩展名}
  • 在沙箱内执行 node upload-blob.mjs <jsonConfig>(沙箱内已装好 @vercel/blob SDK),从 stdout 的 type: 'done' JSON 消息中取回 {url, size}
  • access'public' | 'private'(类型 VercelBlobAccess)。

典型端到端工作流

把上述 API 串起来,一个完整的服务端渲染流程大致如下(基于仓库内各函数的真实签名编写):

import {
  addBundleToSandbox,
  createSandbox,
  renderMediaOnVercel,
  uploadToVercelBlob,
} from '@remotion/vercel';

// 1. 创建并初始化沙箱(可 await using 自动清理)
await using sandbox = await createSandbox({
  onProgress: ({progress, message}) => console.log(progress, message),
  resources: {vcpus: 4},
});

// 2. 上传 `npx remotion bundle` 的产物(如 out/remotion)
await addBundleToSandbox({sandbox, bundleDir: 'out/remotion'});

// 3. 渲染视频(常规模式)
const {sandboxFilePath, contentType} = await renderMediaOnVercel({
  sandbox,
  compositionId: 'MyComp',
  inputProps: {title: 'Hello'},
  codec: 'h264',
  scale: 1,
  onProgress: ({stage, overallProgress}) =>
    console.log(stage, overallProgress),
});

// 4. 上传到 Vercel Blob 并拿到 URL
const {url, size} = await uploadToVercelBlob({
  sandbox,
  sandboxFilePath,
  contentType,
  blobToken: process.env.BLOB_READ_WRITE_TOKEN!,
  access: 'public',
});
console.log(url, size);

长任务或需要跨进程追踪时改用 detached 模式:

const {sandboxId, cmdId, outputFile} = await renderMediaOnVercel({
  sandbox,
  compositionId: 'MyComp',
  inputProps: {title: 'Hello'},
  detached: true,
  vercelBlob: {
    blobToken: process.env.BLOB_READ_WRITE_TOKEN!,
    access: 'public',
    blobPath: 'renders/hello.mp4',
  },
});

// 在任意时机(甚至另一个进程中)轮询
const progress = await getRenderProgress({sandboxId, cmdId});
// progress.stage: 'starting' | 'opening-browser' | ... | 'done' | 'expired'

适用前提与限制

综合源码可以归纳出该包的使用前提与限制,部署前需要确认:

  1. 平台假设:沙箱初始化脚本围绕 node24 运行时 + Amazon Linux 2023(dnf 包管理、glibc 2.34 补丁路径)编写,compositor 修补逻辑只处理 node_modules/@remotion/compositor-linux-x64-gnu,即当前实现面向 Linux x64 沙箱环境;
  2. 浏览器固定为 headless-shellrenderConfigchromeMode 被硬编码为 headless-shell,且 browserExecutablebinariesDirectory 恒为 null,无法指定自托管 Chromium;
  3. detached 模式强依赖 Vercel Blobdetached: true 时缺少 vercelBlob 会直接抛错(The vercelBlob option is required when detached is set to true.),且沙箱默认只自动延长 30 分钟超时(DEFAULT_DETACHED_SANDBOX_TIMEOUT),超长渲染需自行调大 detachedSandboxTimeoutInMilliseconds
  4. 版本一致性是硬约束:沙箱内渲染器版本取自本地 remotion/version,本地 remotion/@remotion/* 版本不一致会导致行为不确定,因此 README 要求所有包使用 --save-exact 的同一版本。

小结

@remotion/vercel 把“打包 → 沙箱环境准备 → 渲染 → 产物分发”拆成了 6 个职责单一、可组合的 API:createSandbox 负责一个开箱即用的 Node 24 渲染沙箱(含系统依赖、glibc 2.35 补丁与 headless-shell 下载),addBundleToSandbox 负责 Bundle 分发,renderMediaOnVercel/renderStillOnVercel 负责以 JSON 配置驱动的无头渲染,getRenderProgressuploadToVercelBlob 分别覆盖异步进度追踪与产物上传。对于需要在无状态云端按需生成视频的 Remotion 项目,这是一条不依赖长期 GPU 实例的轻量渲染路径;实现细节可直接在 packages/vercel/src/ 下按上述文件名查阅。

登录后查看全文
热门项目推荐
相关项目推荐