@remotion/vercel:在 Vercel Sandbox 中渲染 Remotion 视频的完整技术解析
本文围绕 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/renderer 与 remotion,并以 @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 |
VercelSandbox、RenderProgress、VercelBlobUploadOptions、ChromiumOptions、Codec 等(大部分自 types.ts 与 @remotion/renderer 再导出) |
安装与版本约束
README 给出的安装方式:
npm install @remotion/vercel --save-exact
两条必须遵守的版本约束(来自 README 与 package.json):
- 所有
remotion与@remotion/*包必须对齐同一版本,需去掉版本号前的^使用精确版本; @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%):
- 创建沙箱:
runtime: 'node24',即 Node 24 运行时; - 安装系统依赖(75%):通过
sudo dnf install安装 headless Chromium 在 Amazon Linux 2023 上运行所需的一组库:nss、atk、at-spi2-atk、cups-libs、libdrm、libXcomposite、libXdamage、libXrandr、mesa-libgbm、alsa-lib、pango、gtk3,以及补丁工具链patchelf、zstd、binutils(见 internals/install-system-dependencies.ts)。进度是通过统计命令 stdout 行数(源码注释说明经验值为 272 行)线性估算的; - 安装 JS 依赖:在沙箱内执行
pnpm i @remotion/renderer @remotion/compositor-linux-x64-gnu @vercel/blob,版本锁定为当前包版本; - 修补 compositor:Vercel Sandbox 的 Amazon Linux 2023 自带 glibc 2.34,而 Remotion 的 compositor 二进制要求 glibc 2.35。internals/patch-compositor.ts 会下载 Ubuntu 22.04 的
libc6 2.35deb 包(主源为 Launchpad,备用源为 remotion.media),解压后用patchelf把remotion二进制的动态链接指向捆绑的 glibc。源码注释明确指出:Remotion 并不官方支持 glibc 2.34,但可以通过这种方式打补丁;且只有remotion二进制需要修补,ffmpeg/ffprobe在 glibc 2.34 下工作正常; - 下载 headless 浏览器(25%):写入并执行
ensure-browser.mjs,以 JSON 日志形式回报browser-progress百分比(见 internals/install-browser.ts); - 写入渲染脚本:向沙箱写入
package.json({"type": "module"})以及render-video.mjs、render-still.mjs、upload-blob.mjs三个脚本,后续渲染命令直接调用它们。
addBundleToSandbox:上传项目 Bundle
渲染前需要把 npx remotion bundle 生成的静态产物传进沙箱。src/add-bundle-to-sandbox.ts 的 addBundleToSandbox({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: null、binariesDirectory: null、repro: 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-browser、selecting-composition、render-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-browser、selecting-composition、done(携带 size 与 contentType),成功返回 {sandboxFilePath, contentType}。
getRenderProgress:轮询 detached 任务
detached 模式下的进度追踪实现在 src/get-render-progress.ts。它不依赖命令句柄,而是按“文件 + 命令状态”双通道读取:
Sandbox.get({sandboxId})重新附着沙箱,失败即返回{stage: 'expired'};sandbox.getCommand(cmdId)获取渲染命令对象,识别sandbox_stopped一类错误码同样归为expired;- 读取沙箱内固定路径
/vercel/sandbox/progress.json(沙箱内渲染脚本把最新进度写在这里);文件不存在且命令尚未退出时返回{stage: 'starting', overallProgress: 0}; - 文件存在但命令退出码非 0 时,收集
stderr/stdout组装错误信息返回error。
返回值是联合类型 RenderProgress(types.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.ts 的 uploadToVercelBlob({sandbox, sandboxFilePath, blobPath?, contentType, blobToken, access}):
blobPath缺省时自动生成renders/{uuid}{原文件扩展名};- 在沙箱内执行
node upload-blob.mjs <jsonConfig>(沙箱内已装好@vercel/blobSDK),从 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'
适用前提与限制
综合源码可以归纳出该包的使用前提与限制,部署前需要确认:
- 平台假设:沙箱初始化脚本围绕
node24运行时 + Amazon Linux 2023(dnf包管理、glibc 2.34 补丁路径)编写,compositor 修补逻辑只处理node_modules/@remotion/compositor-linux-x64-gnu,即当前实现面向 Linux x64 沙箱环境; - 浏览器固定为 headless-shell:
renderConfig中chromeMode被硬编码为headless-shell,且browserExecutable、binariesDirectory恒为null,无法指定自托管 Chromium; - detached 模式强依赖 Vercel Blob:
detached: true时缺少vercelBlob会直接抛错(The vercelBlob option is required when detached is set to true.),且沙箱默认只自动延长 30 分钟超时(DEFAULT_DETACHED_SANDBOX_TIMEOUT),超长渲染需自行调大detachedSandboxTimeoutInMilliseconds; - 版本一致性是硬约束:沙箱内渲染器版本取自本地
remotion/version,本地remotion/@remotion/*版本不一致会导致行为不确定,因此 README 要求所有包使用--save-exact的同一版本。
小结
@remotion/vercel 把“打包 → 沙箱环境准备 → 渲染 → 产物分发”拆成了 6 个职责单一、可组合的 API:createSandbox 负责一个开箱即用的 Node 24 渲染沙箱(含系统依赖、glibc 2.35 补丁与 headless-shell 下载),addBundleToSandbox 负责 Bundle 分发,renderMediaOnVercel/renderStillOnVercel 负责以 JSON 配置驱动的无头渲染,getRenderProgress 与 uploadToVercelBlob 分别覆盖异步进度追踪与产物上传。对于需要在无状态云端按需生成视频的 Remotion 项目,这是一条不依赖长期 GPU 实例的轻量渲染路径;实现细节可直接在 packages/vercel/src/ 下按上述文件名查阅。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00