Using continueRender for Async Operations in Remotion Compositions

Call delayRender() before starting asynchronous work to generate a unique handle, then invoke continueRender(handle) when the operation completes to unblock the Remotion render pipeline.

When building video compositions with the remotion-dev/remotion framework, components often need to pause rendering while fetching data, loading fonts, or processing media. The continueRender function works in tandem with delayRender to manage these asynchronous operations by signaling exactly when the composition is ready to proceed.

How delayRender and continueRender Work Under the Hood

The mechanism relies on a handle-based tracking system implemented in packages/core/src/delay-render.ts. When you call delayRender(), the internal delayRenderInternal function generates a random numeric handle, stores it in window.remotion_delayRenderHandles, and sets remotion_renderReady to false (lines 71-78). This signals the Remotion engine to pause frame rendering.

When the asynchronous work completes, calling continueRender(handle) triggers continueRenderInternal (lines 62-94). This function removes the handle from the collection, clears any pending timeout, and—crucially—sets remotion_renderReady to true only when no handles remain. The renderer then proceeds to the next frame.

Implementing Async Operations with continueRender

Creating a Delay Handle

Before initiating any asynchronous work, generate a handle by calling delayRender(). Optionally pass a descriptive label and timeout configuration:

import { delayRender } from 'remotion';

const handle = delayRender('loading-font', {
  timeoutInMilliseconds: 60000
});

Resolving with continueRender

Once the async operation completes, pass the handle to continueRender() to unblock the renderer:

import { continueRender } from 'remotion';

await fetch('https://example.com/data.json');
continueRender(handle);

Handling Timeouts and Errors

If continueRender() is never called, a default 30-second timeout triggers cancelRenderInternal, aborting the render with an error. Handle failures explicitly using cancelRender():

import { cancelRender } from 'remotion';

try {
  await riskyAsyncOperation();
  continueRender(handle);
} catch (error) {
  cancelRender(error);
}

Using the useDelayRender Hook for Scoped Operations

For cleaner component integration, Remotion provides the useDelayRender() hook in packages/core/src/use-delay-render.tsx (lines 28-56). This hook returns scoped versions of the delay and continue functions that automatically manage the handle lifecycle within your component's React tree:

import { useDelayRender } from 'remotion';
import React from 'react';

export const MyComp: React.FC = () => {
  const { delayRender, continueRender, cancelRender } = useDelayRender();
  const handle = delayRender('load-font');

  React.useEffect(() => {
    const loadFont = async () => {
      try {
        await fetch('https://example.com/font.woff2');
        continueRender(handle);
      } catch (e) {
        cancelRender(e);
      }
    };
    loadFont();
  }, [handle, continueRender, cancelRender]);

  return <div>Content</div>;
};

Common Use Cases for Async Operations in Remotion

Fetching Remote Data: Pause rendering while retrieving JSON configuration or dynamic content from APIs before the first frame renders.

Loading Custom Fonts: Ensure web fonts or local font files referenced via staticFile() are fully loaded before text appears in the composition.

Decoding Video Frames: Wait for off-thread video processing or WebGL texture initialization to complete before continuing the render pipeline.

Summary

  • delayRender() generates a unique numeric handle and pauses the Remotion render pipeline by setting internal readiness flags in packages/core/src/delay-render.ts.
  • continueRender(handle) removes the specific handle from the tracking collection and resumes rendering only when all handles are cleared.
  • The useDelayRender() hook provides a scoped, React-friendly alternative to the global API for component-level async management.
  • Default timeouts (30 seconds) protect against infinite stalls, customizable via the timeoutInMilliseconds option.

Frequently Asked Questions

What happens if continueRender is never called?

If continueRender() is not invoked within the default 30-second timeout window, Remotion automatically calls the internal cancelRenderInternal function, aborting the render and throwing an error indicating which handle failed to resolve. You can extend this timeout using the timeoutInMilliseconds option in delayRender().

Can I use multiple delayRender handles simultaneously?

Yes. Remotion supports multiple concurrent handles. The render pipeline only resumes when continueRender() has been called for every active handle and the window.remotion_delayRenderHandles collection is empty. This allows parallel async operations across different components or resources.

How do I customize the timeout duration?

Pass a configuration object as the second argument to delayRender() with the timeoutInMilliseconds property. For example: delayRender('slow-api', { timeoutInMilliseconds: 120000 }) sets a 2-minute timeout instead of the default 30 seconds.

Should I use the hook or the global functions?

Prefer useDelayRender() for React components because it scopes handle management to your component lifecycle and automatically cleans up resources. Use the global delayRender() and continueRender() functions only when working outside React components or in utility modules where hook context is unavailable.

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 →