Remotion LabRemotion Lab
疑難排解在打包程式碼中呼叫 bundle()

在打包程式碼中呼叫 bundle()

排解 Remotion 中「Calling bundle() in bundled code」錯誤,了解為何不能在瀏覽器端或已打包的程式碼中呼叫 bundle() 函式。

在打包程式碼中呼叫 bundle()

錯誤訊息

Error: Calling bundle() in bundled code is not supported.

或類似的變體:

bundle() cannot be called from within a Webpack bundle.
@remotion/bundler cannot be used in a browser environment.

錯誤原因

bundle()@remotion/bundler 提供的 Node.js API,它的功能是呼叫 Webpack 將你的 Remotion 專案打包成一個可供渲染的 bundle。這個操作需要:

  • 存取檔案系統(讀取原始碼、設定檔等)
  • 執行 Webpack 進程
  • 使用 Node.js 原生模組

以上這些都只能在 Node.js 環境中執行,無法在瀏覽器環境或已被 Webpack 打包過的程式碼中使用。

當你嘗試在 React 元件、Next.js 的 Client Component 或其他前端程式碼中匯入並呼叫 bundle() 時,就會觸發此錯誤。

錯誤示範

以下是幾種常見的錯誤用法

// ❌ 錯誤:在 React 元件中直接呼叫 bundle()
import { bundle } from '@remotion/bundler';
import { useEffect } from 'react';
 
export const RenderButton: React.FC = () => {
  const handleRender = async () => {
    // 這裡不能呼叫 bundle()!
    const bundled = await bundle({
      entryPoint: './src/remotion/index.ts',
    });
    // ...
  };
 
  return <button onClick={handleRender}>開始渲染</button>;
};
// ❌ 錯誤:在 Next.js Client Component 中呼叫
'use client';
import { bundle } from '@remotion/bundler';
 
export default function Page() {
  const render = async () => {
    const bundled = await bundle({ ... }); // 錯誤!
  };
  // ...
}

正確做法

方案一:在 Server Action 中執行(Next.js App Router)

若使用 Next.js App Router,可以在 Server Action 中執行 bundle() 和渲染邏輯:

// app/actions.ts
'use server';
 
import { bundle } from '@remotion/bundler';
import { renderMedia, selectComposition } from '@remotion/renderer';
import path from 'path';
 
export async function startRender(props: { title: string }) {
  const bundled = await bundle({
    entryPoint: path.resolve('./src/remotion/index.ts'),
  });
 
  const composition = await selectComposition({
    serveUrl: bundled,
    id: 'MyComposition',
    inputProps: props,
  });
 
  await renderMedia({
    composition,
    serveUrl: bundled,
    codec: 'h264',
    outputLocation: `out/${Date.now()}.mp4`,
  });
 
  return { success: true };
}
// app/page.tsx
'use client';
import { startRender } from './actions';
 
export default function Page() {
  const handleRender = async () => {
    await startRender({ title: '我的影片' });
  };
 
  return <button onClick={handleRender}>開始渲染</button>;
}

方案二:建立 API 路由

在後端 API 路由中執行渲染邏輯,前端僅發送 HTTP 請求:

// pages/api/render.ts(Next.js Pages Router)
import type { NextApiRequest, NextApiResponse } from 'next';
import { bundle } from '@remotion/bundler';
import { renderMedia, selectComposition } from '@remotion/renderer';
import path from 'path';
 
export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }
 
  const { title } = req.body;
 
  const bundled = await bundle({
    entryPoint: path.resolve('./src/remotion/index.ts'),
  });
 
  const composition = await selectComposition({
    serveUrl: bundled,
    id: 'MyComposition',
    inputProps: { title },
  });
 
  await renderMedia({
    composition,
    serveUrl: bundled,
    codec: 'h264',
    outputLocation: `out/${Date.now()}.mp4`,
  });
 
  res.status(200).json({ success: true });
}

前端發起渲染請求:

// 前端元件
const handleRender = async () => {
  const response = await fetch('/api/render', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ title: '我的影片' }),
  });
  const result = await response.json();
  console.log(result);
};

方案三:獨立的 Node.js 腳本

若渲染不需要透過 Web API 觸發,可以直接撰寫獨立的 Node.js 腳本:

// scripts/render.ts
import { bundle } from '@remotion/bundler';
import { renderMedia, selectComposition } from '@remotion/renderer';
import path from 'path';
 
async function main() {
  const bundled = await bundle({
    entryPoint: path.resolve('./src/remotion/index.ts'),
  });
 
  const composition = await selectComposition({
    serveUrl: bundled,
    id: 'MyComposition',
    inputProps: { title: '我的影片' },
  });
 
  await renderMedia({
    composition,
    serveUrl: bundled,
    codec: 'h264',
    outputLocation: 'out/video.mp4',
  });
 
  console.log('渲染完成!');
}
 
main();

tsx 或編譯後執行:

npx tsx scripts/render.ts

方案四:使用 AWS Lambda 或 Cloud Run

若需要可擴展的雲端渲染,使用 @remotion/lambda 或 Cloud Run。這些服務在後端執行 bundle() 和渲染,前端透過 SDK 觸發:

// 這段程式碼在後端(Node.js)執行
import { renderMediaOnLambda } from '@remotion/lambda/client';
 
const result = await renderMediaOnLambda({
  region: 'ap-northeast-1',
  functionName: 'remotion-render',
  serveUrl: 'https://your-site.s3.amazonaws.com',
  composition: 'MyComposition',
  inputProps: { title: '我的影片' },
  codec: 'h264',
});

效能提示:快取 Bundle

bundle() 每次執行需要花費數秒鐘。在同一個 Node.js 程序中,若需要多次渲染,建議快取 bundle 的結果:

let cachedBundleUrl: string | null = null;
 
async function getBundleUrl() {
  if (!cachedBundleUrl) {
    cachedBundleUrl = await bundle({
      entryPoint: path.resolve('./src/remotion/index.ts'),
    });
  }
  return cachedBundleUrl;
}
 
// 多次渲染重複使用同一個 bundle
const bundleUrl = await getBundleUrl();

相關資源