R RenderComp
remotion version-control git react typescript

Remotion テンプレートをGitブランチで改訂管理

執筆: RenderComp チーム 編集方針

Remotion では映像を React コンポーネントとして記述します。useCurrentFrame() が返す整数フレーム番号を入力として、同じフレームを何度レンダリングしても同じピクセルが出力されます。この決定論的な性質が、テンプレートの改訂管理に直接役立ちます。コードが変われば出力が変わり、コードが同じなら出力も同じです。Git のコミットハッシュひとつで、特定バージョンのテンプレートを1フレームの誤差なく再現できます。

一方、映像を MP4 ファイルとして管理すると、巨大なバイナリが差分追跡の外に置かれます。編集履歴は「完成_v3.mp4」「完成_v3_最終.mp4」というファイル名の連鎖に転落します。Remotion のテンプレートをソースコードとして扱えば、そのパターンを避けられます。Remotion はオープンソースとして GitHub で公開されており、フレームワーク自体の変更もリリースタグで追跡されています。


ブランチ命名の基本方針

テンプレートリポジトリに適したブランチ命名を一つ示します。main を「常時レンダリング可能なベース」として保護し、改訂ごとに短命ブランチを切ります。

main
├── template/social-card-v2
├── fix/title-overflow-720p
└── experiment/kinetic-type

template/ プレフィックスは新バリアントを、fix/ は既存テンプレートのバグ修正を、experiment/ は採用未定のプロトタイプを示します。命名規則をリポジトリの CONTRIBUTING.md に一行書いておくと、複数人で作業する際にブランチ一覧が整理された状態を保てます。


Props スキーマのバージョン管理

テンプレート改訂で最も注意が必要な箇所は Props の型変更です。Remotion 4 以降、zod スキーマを defineSchema で登録すると、Remotion Studio と renderMedia の両方が同じ型定義を参照します。バージョン間で Props が変わる場合は、古い形式から新しい形式への後方互換を optional() と default() で担保します。

// src/schemas/social-card.ts
import { z } from "zod";

// v1: テキストのみ
export const SocialCardPropsV1 = z.object({
  title: z.string(),
  subtitle: z.string(),
});

// v2: アバター画像とアクセントカラーを追加
export const SocialCardPropsV2 = z.object({
  title: z.string(),
  subtitle: z.string(),
  avatarSrc: z.string().optional(),           // 既存データを壊さない
  accentColor: z.string().default("#0ea5e9"), // 省略時のデフォルト値
});

export type SocialCardProps = z.infer<typeof SocialCardPropsV2>;

avatarSrc を optional() にしておくと、v1 のデータを渡しても renderMedia がエラーを返しません。default() はスキーマレベルで値を補完するため、コンポーネント側に ?? の防衛コードを書かずに済みます。


コンポーネントの実装

Props スキーマが確定したら、コンポーネントに反映します。useVideoConfig() から fps を取得して spring() に渡す点が重要です。

// src/compositions/SocialCard.tsx
import {
  AbsoluteFill,
  useCurrentFrame,
  useVideoConfig,
  spring,
  interpolate,
} from "remotion";
import type { SocialCardProps } from "../schemas/social-card";

export const SocialCard: React.FC<SocialCardProps> = ({
  title,
  subtitle,
  avatarSrc,
  accentColor,
}) => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();
  // fps を参照することで 30fps / 60fps どちらでも同じ動きのテンポになる

  const titleOpacity = spring({
    frame,
    fps,
    from: 0,
    to: 1,
    config: { damping: 14, stiffness: 120 },
  });

  const titleY = interpolate(titleOpacity, [0, 1], [24, 0]);

  return (
    <AbsoluteFill style={{ background: "#0f172a", padding: 48 }}>
      <div
        style={{
          opacity: titleOpacity,
          transform: `translateY(${titleY}px)`,
          fontSize: 64,
          fontWeight: 700,
          color: accentColor,
        }}
      >
        {title}
      </div>
      <div
        style={{ opacity: titleOpacity, fontSize: 32, color: "#94a3b8", marginTop: 16 }}
      >
        {subtitle}
      </div>
      {avatarSrc && (
        <img
          src={avatarSrc}
          style={{ width: 96, height: 96, borderRadius: "50%", marginTop: 32 }}
        />
      )}
    </AbsoluteFill>
  );
};

spring() に fps を渡す理由は、フレームレートに依存しない一定のイージング感を維持するためです。30fps と 60fps では frame の刻みが異なるため、fps を固定値でハードコードすると解像度プリセットを変更したときに動きのテンポがずれます。stiffness: 120 と damping: 14 は短い弾みのあるイージングを作り、interpolate で titleOpacity を Y 軸のオフセットに変換することでフェードとスライドを同期させています。


Composition の登録

src/Root.tsx は Remotion のエントリポイントであり、どの Composition が存在するかを宣言します。ブランチ間で Composition を追加・削除するとき、id の変更が最も追跡しやすい差分になります。

// src/Root.tsx
import { Composition } from "remotion";
import { SocialCard } from "./compositions/SocialCard";
import { SocialCardPropsV2 } from "./schemas/social-card";

export const RemotionRoot: React.FC = () => (
  <>
    <Composition
      id="SocialCardV2"
      component={SocialCard}
      durationInFrames={90}    // 3 秒 × 30 fps
      fps={30}
      width={1200}
      height={630}
      schema={SocialCardPropsV2}
      defaultProps={{
        title: "タイトルテキスト",
        subtitle: "サブタイトル",
        accentColor: "#0ea5e9",
      }}
    />
  </>
);

id は renderMedia の呼び出し側や package.json のスクリプトに埋め込まれることが多く、ブランチ間で変更すると下流が壊れます。CI でレンダリングスクリプトを走らせていれば、id のミスマッチは renderMedia が返すエラーで即座に検出できます。


Git タグでリリースを固定する

本番データを渡してレンダリングした映像を後で再現するとき、「どのコミットでレンダリングしたか」が特定できれば十分です。main にマージした時点でタグを打ちます。

git tag -a template-social-card-v2.0.0 -m "SocialCard v2: avatar + accentColor"
git push origin template-social-card-v2.0.0

このタグから再現するときは次のように実行します。

git checkout template-social-card-v2.0.0
npx remotion render SocialCardV2 out/replay.mp4 \
  --props='{"title":"サンプルタイトル","subtitle":"説明文","accentColor":"#0ea5e9"}'

--props に JSON を渡すと、Remotion Studio を開かずにコマンド一本で同じ映像を出力できます。タグと Props JSON を組み合わせると、コードと入力データの両方が固定され、ビット単位で同一のフレームが得られます。


マージ前に確認すること

Props の型が変わる改訂をマージする前に確認しておく項目があります。zod スキーマに optional() または default() を設けて既存の Props JSON がバリデーションを通ること、Root.tsx の id がレンダリングスクリプトと一致していること、30fps と 60fps の両方で spring() の動きを確認していることです。

型チェックだけでは spring() の視覚的なリグレッションは検出できません。ブランチ上でスクリーンショットを撮り、main ブランチの出力と目視で比較する手順を Pull Request のテンプレートに加えておくと、レビュアーが何を確認すべきかを明示できます。

販売中

1,000以上のRemotionテンプレートを一括入手

買い切り(一括払い)・サブスクなし・生涯アップデート無料。TypeScript製。

料金プランを見る →

無料50本

テンプレート50本を ZIP で受け取る

メールアドレスを入れると、50本の ZIP のリンクをすぐにお送りします。

50本の ZIP をメールで受け取ります。あわせて RenderComp テンプレートの更新と、有料ライブラリを含む製品のご案内を受け取ることに同意します(メールは数通・ワンクリック解除)。 プライバシーポリシー(英語)

メールを使わずに受け取ることもできます。GitHub のリポジトリは公開のままで、登録も不要です。 リポジトリを開く