Debugging "Frame Timeout" Errors in Remotion Renders: A Complete Guide

A "Frame timeout" error occurs when Remotion's headless Chromium instance fails to complete navigation or finish delayRender calls within the configured timeout period, triggering a TimeoutError from the LifecycleWatcher class.

When rendering compositions, the remotion-dev/remotion package spins up a headless browser via Puppeteer and monitors navigation lifecycle events. If the page load or component initialization exceeds the specified duration, the renderer throws a frame timeout error. Understanding the underlying mechanism in LifecycleWatcher.ts and FrameManager.ts is essential for diagnosing these failures.

What Triggers a Frame Timeout Error?

Remotion renders work by navigating a headless Chromium browser to a page containing your component tree. During this process, the renderer creates a LifecycleWatcher instance that monitors browser lifecycle events (load, domcontentloaded, etc.) while simultaneously running a timeout timer.

If navigation does not complete before the timer expires, the watcher resolves with a TimeoutError defined in packages/renderer/src/browser/Errors.ts. This error bubbles up through FrameManager.navigateFrame in packages/renderer/src/browser/FrameManager.ts and surfaces as the "Frame timeout" message in your logs.

How the Timeout Mechanism Works

The LifecycleWatcher Implementation

The core timeout logic resides in packages/renderer/src/browser/LifecycleWatcher.ts. The private method #createTimeoutPromise sets up the timer that eventually triggers the error:

// packages/renderer/src/browser/LifecycleWatcher.ts
private async #createTimeoutPromise(): Promise<TimeoutError | undefined> {
    if (!this.#timeout) {
        return new Promise(noop);
    }
    const errorMessage = 'Navigation timeout of ' + this.#timeout + ' ms exceeded';
    await new Promise((fulfill) => {
        this.#maximumTimer = setTimeout(fulfill, this.#timeout);
    });
    return new TimeoutError(errorMessage);
}

The LifecycleWatcher is instantiated inside FrameManager.navigateFrame with the timeout value passed from the renderer configuration:

// packages/renderer/src/browser/FrameManager.ts
const watcher = new LifecycleWatcher(this, frame, waitUntil, timeout);

Minimum Timeout Enforcement

Before the timeout reaches Chromium, Remotion validates it in packages/renderer/src/validate-puppeteer-timeout.ts. The renderer enforces a hard minimum of 7000 milliseconds:

// packages/renderer/src/validate-puppeteer-timeout.ts
if (timeoutInMilliseconds < 7000 && process.env.NODE_ENV !== 'test') {
    throw new TypeError(
        `'timeoutInMilliseconds' should be bigger or equal than 7000, but is ${timeoutInMilliseconds}`,
    );
}

Attempting to set a timeout below 7000ms (except when NODE_ENV=test) immediately throws a TypeError, which can be mistaken for a frame timeout if not checked properly.

Configuring the Timeout Duration

Remotion provides two methods to customize the navigation timeout, both managed through packages/renderer/src/options/timeout.tsx:

CLI Flag – Use --timeout with the render command (default: 30000ms):

remotion render MyComp.jsx out.mp4 --timeout 120000

Programmatic API – Import setDelayRenderTimeoutInMilliseconds before calling renderMedia:

import {setDelayRenderTimeoutInMilliseconds, renderMedia} from 'remotion';

// Set timeout to 90 seconds
setDelayRenderTimeoutInMilliseconds(90_000);

await renderMedia({
  composition: MyComposition,
  serveUrl: 'http://localhost:3000',
  codec: 'h264',
  outputLocation: './out.mp4',
});

Both methods update the currentTimeout value that the renderer reads when creating the LifecycleWatcher.

Common Causes of Frame Timeouts

  • Heavy component trees or long-running delayRender – Complex initialization prevents navigation from reaching the expected lifecycle event before the timer fires.
  • Network-blocked resources – Chromium waits for external fonts, images, or APIs, but the timeout continues running.
  • Sub-7s timeout values – Violating the minimum threshold in validate-puppeteer-timeout.ts triggers immediate validation errors or premature timeouts.
  • Browser crashes or detachment – If the Puppeteer instance crashes, the LifecycleWatcher never receives navigation events, causing the timeout to fire.

How to Debug Frame Timeout Errors

  1. Inspect the error message – It contains the exact timeout value that was exceeded (e.g., "Navigation timeout of 30000 ms exceeded").
  2. Check render logs – Look for "Waiting for root component to load" to identify where navigation stalls.
  3. Increase the timeout – Extend via CLI (--timeout 120000) or setDelayRenderTimeoutInMilliseconds(120_000).
  4. Verify the minimum threshold – Ensure your timeout is ≥ 7000ms unless running with NODE_ENV=test.
  5. Validate network resources – Confirm all external assets are reachable; network issues manifest as navigation timeouts.
  6. Test without the minimum bound – Set NODE_ENV=test temporarily to bypass the 7s limit and see if the render completes faster.

Practical Code Examples

Extending Timeout via CLI


# Allow 2 minutes for navigation and delayRender calls

remotion render src/Video.jsx out.mp4 --timeout 120000

Programmatic Timeout Configuration

// src/render.ts
import {renderMedia, setDelayRenderTimeoutInMilliseconds} from 'remotion';
import {MyComposition} from './MyComposition';

async function run() {
  // Extend to 90 seconds
  setDelayRenderTimeoutInMilliseconds(90_000);

  await renderMedia({
    composition: MyComposition,
    serveUrl: 'http://localhost:3000',
    codec: 'h264',
    outputLocation: './out.mp4',
    // Optional per-render override:
    // timeoutInMilliseconds: 90_000,
  });
}
run();

Catching TimeoutError in Custom Renderers

import {renderMedia} from 'remotion';
import {TimeoutError} from '@remotion/renderer/dist/browser/Errors';

try {
  await renderMedia({ /* configuration */ });
} catch (err) {
  if (err instanceof TimeoutError) {
    console.error('Frame timed out:', err.message);
    // Implement retry logic with increased timeout
  } else {
    throw err;
  }
}

Summary

  • Frame timeouts originate in LifecycleWatcher.ts when navigation exceeds the configured duration before reaching lifecycle events.
  • Default timeout is 30000ms (30 seconds), enforced by TimeoutSettings.ts and validated in validate-puppeteer-timeout.ts with a hard minimum of 7000ms.
  • Configuration happens via --timeout CLI flag or setDelayRenderTimeoutInMilliseconds() from options/timeout.tsx.
  • ** debugging** requires checking error messages for exact millisecond values, verifying network resource availability, and ensuring timeout values meet the 7-second minimum threshold.
  • Error handling can specifically catch TimeoutError from browser/Errors.ts to implement custom retry logic.

Frequently Asked Questions

What is the default frame timeout in Remotion?

The default timeout is 30000 milliseconds (30 seconds), defined in packages/renderer/src/browser/TimeoutSettings.ts. If your composition takes longer to initialize or fetch resources, you must explicitly increase this value using the --timeout CLI flag or the setDelayRenderTimeoutInMilliseconds API.

Why do I get a TypeError about timeout being less than 7000ms?

This occurs because packages/renderer/src/validate-puppeteer-timeout.ts enforces a minimum timeout of 7000ms to prevent unstable renders. If you need to bypass this validation (for testing only), set NODE_ENV=test. In production, always configure timeouts above this threshold.

How do I distinguish between a network timeout and a slow component?

Check the render logs for "Waiting for root component to load" messages. If the error occurs before this message appears, Chromium is likely stuck fetching external resources (fonts, images, APIs). If it appears after, your component's delayRender calls or heavy initialization logic are the culprit. Increase the timeout and instrument your component with console logs to pinpoint the bottleneck.

Can I catch frame timeout errors programmatically?

Yes. Import TimeoutError from @remotion/renderer/dist/browser/Errors (or @remotion/renderer/src/browser/Errors in development) and use instanceof checks to catch navigation-specific timeouts separately from other render failures. This allows you to implement automatic retries with exponential backoff or fallback compositions.

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 →