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

> Discover the Remotion Lambda serverless rendering architecture for scalable video on AWS. Learn how coordinator-worker patterns optimize frame processing and FFmpeg merging for efficient results.

- Repository: [Remotion/remotion](https://github.com/remotion-dev/remotion)
- Tags: architecture
- Published: 2026-02-16

---

**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`](https://github.com/remotion-dev/remotion/blob/main/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`](https://github.com/remotion-dev/remotion/blob/main/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`](https://github.com/remotion-dev/remotion/blob/main/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`](https://github.com/remotion-dev/remotion/blob/main/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:

```typescript
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`](https://github.com/remotion-dev/remotion/blob/main/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`](https://github.com/remotion-dev/remotion/blob/main/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`](https://github.com/remotion-dev/remotion/blob/main/launch.ts)). This function wraps `streamRenderer` in [`packages/serverless/src/stream-renderer.ts`](https://github.com/remotion-dev/remotion/blob/main/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`](https://github.com/remotion-dev/remotion/blob/main/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`](https://github.com/remotion-dev/remotion/blob/main/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`](https://github.com/remotion-dev/remotion/blob/main/launch.ts)). This function, implemented in [`packages/serverless/src/merge-chunks.ts`](https://github.com/remotion-dev/remotion/blob/main/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`](https://github.com/remotion-dev/remotion/blob/main/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:

```typescript
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)](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)](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)](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)](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)](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`](https://github.com/remotion-dev/remotion/blob/main/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`](https://github.com/remotion-dev/remotion/blob/main/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`](https://github.com/remotion-dev/remotion/blob/main/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`](https://github.com/remotion-dev/remotion/blob/main/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.