Remotion Lambda Serverless Rendering Architecture: How It Scales Video Rendering on AWS

Remotion Lambda uses a coordinator-worker pattern where a single launch function validates input, splits frames into chunks, invokes parallel worker Lambdas via streaming invocations, aggregates progress in S3, and merges the final output using FFmpeg.

The remotion-dev/remotion repository implements a sophisticated serverless rendering architecture that distributes video composition workloads across AWS Lambda functions. This system enables massive parallelization of frame rendering while maintaining a simple client API, making it possible to render complex videos at scale without managing infrastructure.

Architecture Overview

Remotion Lambda’s architecture consists of three logical layers that cooperate through a single launch function and a pool of worker renderer functions:

Layer Responsibility Main Source File
Launch Validates the request, plans frame ranges, creates payloads for each worker, invokes the workers, tracks progress, and finally merges the chunks into the final output. packages/serverless/src/handlers/launch.ts
Worker Receives a chunk payload, renders its assigned frames, streams video/audio chunks and artifacts back to the launch function, and reports progress. packages/serverless/src/stream-renderer.ts
Progress & Merging Stores intermediate state in an S3 object (overallProgressKey), aggregates per-chunk timings, retries failed chunks and finally stitches the media together. packages/serverless/src/overall-render-progress.ts

The Launch Handler: Orchestration and Validation

The entry point for every serverless render is the launchHandler in packages/serverless/src/handlers/launch.ts. This function acts as the central coordinator, performing several critical validation steps before distributing work.

First, the handler validates the request and environment configuration (lines 68-84). It then decompresses input props if they were compressed for transmission (lines 96-106). Finally, it validates the composition exists and can be rendered (lines 118-130).

After validation, the launch function plans the frame distribution strategy. It calculates how to split the total frame range into chunks that can be processed in parallel:

const {chunks} = planFrameRanges({
  framesPerFunction: framesPerLambda,
  frameRange: realFrameRange,
  everyNthFrame: params.everyNthFrame,
});

If the number of chunks exceeds MAX_FUNCTIONS_PER_RENDER, the handler throws an error to prevent excessive concurrency (lines 111-115).

Frame Range Planning and Chunk Distribution

The planFrameRanges function in packages/serverless/src/plan-frame-ranges.ts (lines 4-28) determines how frames are distributed across worker functions. It takes into account the total frame range, the configured frames per Lambda, and any frame skipping parameters.

For each calculated chunk, the launch function builds a renderer payload with the ServerlessRoutines.renderer type. Key fields include:

  • frameRange – the start/end frames for this specific chunk
  • chunk – the index of the chunk
  • resolvedProps – compressed, possibly uploaded input props
  • renderId, renderBucketName, and rendering options (codec, bitrate, etc.)

These payloads are stored in lambdaPayloads (lines 64-115 in launch.ts) and passed to the streaming invocation system.

Worker Invocation and Streaming Protocol

The launch function invokes workers through streamRendererFunctionWithRetry (lines 90-105 in launch.ts). This function wraps streamRenderer in packages/serverless/src/stream-renderer.ts, which handles the core streaming implementation.

The streaming system creates a streaming invocation via providerSpecifics.callFunctionStreaming and registers a message handler (receivedStreamingPayload) that processes every message type sent from the worker (lines 55-167).

Workers communicate back to the launch function through a lightweight messaging protocol. The launch handler processes these message types:

Message type Action taken by launch
lambda-invoked Marks the chunk as started via overallProgress.setLambdaInvoked (lines 57-60)
frames-rendered Updates per-chunk frame counters via overallProgress.setFrames (lines 62-68)
video-chunk-rendered / audio-chunk-rendered Writes the raw binary chunk to a temporary file and records the file path (lines 71-98)
chunk-complete Marks the chunk as finished and records timing via overallProgress.addChunkCompleted (lines 101-112)
artifact-emitted Deserializes the artifact, stores it in S3 via providerSpecifics.writeFile, and adds it to progress state (lines 114-132)
error-occurred Logs the error, updates progress via addErrorWithoutUpload, and signals whether to retry (lines 134-163)

The system automatically retries failed chunks based on the shouldRetry flag (lines 41-68 in stream-renderer.ts).

Progress Tracking and State Management

The progress object lives in OverallRenderProgress and is updated through the helper returned by makeOverallRenderProgress (lines 56-82 in packages/serverless/src/overall-render-progress.ts).

State is periodically uploaded to S3 using the overallProgressKey(renderId) function (lines 19-35). This allows the system to:

  • Track which chunks have started, completed, or failed
  • Aggregate per-chunk timing data for cost estimation
  • Resume or retry specific chunks without losing progress
  • Provide real-time progress updates to the client via getProgress polling

Merging Chunks and Finalizing Output

When all worker promises resolve, the launch function calls mergeChunksAndFinishRender (lines 107-112 in launch.ts). This function, implemented in packages/serverless/src/merge-chunks.ts, stitches the video and audio chunks together using FFmpeg on the launch container.

The merge process:

  1. Concatenates temporary chunk files written during the streaming phase
  2. Writes the final media asset to the target S3 bucket
  3. Returns a PostRenderData object containing:
    • outputFile (S3 URL)
    • errors (any worker errors encountered)
    • timeToFinish and cost estimates

Webhooks and Error Handling

After a successful merge, the launch handler optionally fires a success webhook (lines 327-358 in launch.ts). If the function times out before completion, a timeout webhook is sent (lines 96-126).

Cleanup tasks are registered via registerCleanupTask and executed in the finally block (lines 740-787). These tasks include:

  • Deleting temporary chunk files from the local filesystem
  • Forgetting the browser event loop to prevent memory leaks
  • Closing any open connections

Implementation Example

Here is a minimal implementation invoking the Remotion Lambda architecture from a server environment:

import {renderMediaOnLambda} from '@remotion/lambda/client';

// Called from your own API route or server
export async function handler(req, res) {
  const result = await renderMediaOnLambda({
    serveUrl: 'https://my-remotion-site.com',
    composition: 'HelloWorld',
    outName: 'hello.mp4',
    // Optional: custom bucket, codec, timeout, etc.
  });

  // result contains the S3 URL of the rendered video
  res.json({url: result.outputFile});
}

The client library builds the launch payload, sends it to the launch Lambda, and returns the final PostRenderData when the entire pipeline finishes.

Key Source Files

File Purpose
[packages/serverless/src/handlers/launch.ts](https://github.com/remotion-dev/remotion/blob/main/packages/serverless/src/handlers/launch.ts) Main entry point; validates input, plans frames, creates worker payloads, coordinates streaming, merges results.
[packages/serverless/src/stream-renderer.ts](https://github.com/remotion-dev/remotion/blob/main/packages/serverless/src/stream-renderer.ts) Handles a single worker invocation, processes streamed messages (progress, chunks, artifacts, errors).
[packages/serverless/src/overall-render-progress.ts](https://github.com/remotion-dev/remotion/blob/main/packages/serverless/src/overall-render-progress.ts) In-memory model of the render, periodic S3 uploads, retry tracking, error aggregation.
[packages/serverless/src/plan-frame-ranges.ts](https://github.com/remotion-dev/remotion/blob/main/packages/serverless/src/plan-frame-ranges.ts) Calculates how many chunks are needed and the exact start/end frame for each chunk.
[packages/serverless/src/provider-implementation.ts](https://github.com/remotion-dev/remotion/blob/main/packages/serverless/src/provider-implementation.ts) Types that abstract cloud-provider-specific operations (S3 read/write, function creation/invocation, webhook calls, etc.).

Summary

Remotion Lambda’s serverless rendering architecture implements a coordinator-worker pattern that distributes video rendering across AWS Lambda functions:

  • Single launch function validates requests, plans frame ranges, and manages the entire lifecycle
  • Parallel worker functions render independent frame chunks via streaming invocations
  • Real-time progress tracking stores state in S3 using OverallRenderProgress for durability and visibility
  • Automatic retry logic handles transient failures at the chunk level via streamRendererFunctionWithRetry
  • FFmpeg-based merging combines chunks on the launch container before writing the final output to S3

This design allows Remotion to scale from single-frame renders to massive parallel workloads without requiring persistent infrastructure.

Frequently Asked Questions

How does Remotion Lambda handle failed worker invocations?

The architecture implements automatic retry logic through streamRendererFunctionWithRetry in packages/serverless/src/stream-renderer.ts. When a worker returns an error-occurred message, the launch handler evaluates the shouldRetry flag (lines 134-163). Transient errors trigger a retry with exponential backoff, while permanent errors are recorded in the OverallRenderProgress state and surfaced in the final PostRenderData output.

What is the maximum number of parallel workers allowed?

Remotion Lambda enforces a concurrency limit through the MAX_FUNCTIONS_PER_RENDER constant. If planFrameRanges calculates more chunks than this limit allows, the launch handler throws an error (lines 111-115 in packages/serverless/src/handlers/launch.ts). This prevents excessive AWS Lambda concurrency that could hit account limits or cause runaway costs.

How does the launch function track render progress in real time?

Progress tracking relies on the OverallRenderProgress class in packages/serverless/src/overall-render-progress.ts. The launch function maintains an in-memory state object that is periodically serialized and uploaded to S3 using the overallProgressKey(renderId) path (lines 19-35). Workers stream progress messages (frames rendered, chunks completed) back to the launch function via the streaming protocol, which updates this state immediately.

Where does the final video merging occur?

The final merge happens on the launch Lambda container, not on the workers. After all worker promises resolve, mergeChunksAndFinishRender (called at lines 107-112 in packages/serverless/src/handlers/launch.ts) executes FFmpeg to concatenate the temporary video and audio chunk files collected during the streaming phase. The resulting file is then uploaded to the target S3 bucket specified in the render configuration.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →