# Using delayRender for Async Operations in Remotion Compositions

> Unlock async operations in Remotion with delayRender. Pause frame rendering for data fetching or font loading, then continue or cancel. Enhance your video creation workflow today.

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

---

**Use `delayRender()` to pause frame rendering until asynchronous tasks like data fetching, font loading, or texture preparation complete, then unblock with `continueRender()` or abort with `cancelRender()`.**

The `delayRender` API is the standard mechanism in the remotion-dev/remotion repository for handling asynchronous operations within video compositions. When you need to fetch external data, load custom fonts, or prepare WebGL textures before a frame can be rendered, this API ensures the renderer waits for completion rather than outputting incomplete frames.

## How delayRender Works Internally

When you invoke `delayRender()`, the function creates a unique handle and registers it in the global render state. According to the source code in [`packages/core/src/delay-render.ts`](https://github.com/remotion-dev/remotion/blob/main/packages/core/src/delay-render.ts), the implementation follows this sequence:

1. **Handle Generation**: The function generates a random identifier and pushes it onto `scope.remotion_delayRenderHandles` (lines 71-73).
2. **Render Blocking**: It immediately sets `scope.remotion_renderReady = false`, signaling the renderer that the current frame is not ready for capture (lines 109-111).
3. **Timeout Management**: When running in headless environments, an optional timeout begins. If the handle isn't cleared before expiration, `cancelRenderInternal` aborts the render with a descriptive error (lines 84-106).
4. **Completion Handling**: Calling `continueRender(handle)` clears the timeout, removes the handle from the list, and sets `remotion_renderReady` back to `true` if no other handles remain (lines 62-94).

The `useDelayRender()` hook in [`packages/core/src/use-delay-render.tsx`](https://github.com/remotion-dev/remotion/blob/main/packages/core/src/use-delay-render.tsx) wraps these internal functions, exposing `delayRender`, `continueRender`, and `cancelRender` through React context (lines 17-21).

## Implementing Async Workflows with delayRender

The standard pattern involves creating a handle at the start of an async operation, performing the work, then signaling completion or failure. This pattern applies whether you are fetching JSON, loading binary assets, or initializing WebGL contexts.

### Fetching Data Before Frame Rendering

When external data determines visual output, use the `useDelayRender` hook to block rendering until the fetch completes:

```tsx
import {useEffect, useState} from 'react';
import {useDelayRender} from 'remotion';

export const DataDrivenComposition = () => {
  const {delayRender, continueRender, cancelRender} = useDelayRender();
  const [data, setData] = useState<any>(null);
  
  // Initialize handle once per component lifecycle
  const [handle] = useState(() => delayRender('Fetching API data'));

  useEffect(() => {
    fetch('https://api.example.com/data')
      .then((res) => res.json())
      .then((json) => {
        setData(json);
        continueRender(handle);  // Unblock rendering
      })
      .catch((err) => {
        cancelRender(err);       // Abort with error
      });
  }, [handle, continueRender, cancelRender]);

  // Frame rendering pauses here until continueRender executes
  return <div>{data ? data.title : 'Loading...'}</div>;
};

```

**Critical implementation detail**: Create the handle using `useState(() => delayRender())` rather than calling `delayRender()` directly in the render body. This prevents creating multiple handles during React re-renders.

### Loading Custom Fonts

For typography that isn't system-default, fonts must load before text measurement and rendering occur. The remotion-dev/remotion repository includes this pattern in [`packages/template-react-router/app/remotion/load-fonts.ts`](https://github.com/remotion-dev/remotion/blob/main/packages/template-react-router/app/remotion/load-fonts.ts):

```tsx
import {delayRender, continueRender, staticFile} from 'remotion';

export const loadCustomFont = async () => {
  const handle = delayRender('Loading custom font');
  
  try {
    const fontUrl = staticFile('fonts/CustomFont.woff2');
    const fontFace = new FontFace('CustomFont', `url(${fontUrl})`);
    await fontFace.load();
    document.fonts.add(fontFace);
    
    continueRender(handle);
  } catch (error) {
    cancelRender(error);
  }
};

```

Invoke this loader in your composition's root or within a `useEffect` to ensure fonts are available before the first frame renders.

### Preparing Video Textures for Three.js

When integrating `@remotion/three`, video textures require asynchronous initialization. The implementation in [`packages/three/src/use-video-texture.ts`](https://github.com/remotion-dev/remotion/blob/main/packages/three/src/use-video-texture.ts) demonstrates blocking render until the video element is ready:

```tsx
import {useDelayRender} from 'remotion';
import {useRef, useState} from 'react';

export const useVideoTexture = (src: string) => {
  const {delayRender, continueRender, cancelRender} = useDelayRender();
  const videoRef = useRef<HTMLVideoElement>(null);
  const [texture, setTexture] = useState(null);
  
  const [handle] = useState(() => delayRender('Video texture loading'));

  const initializeVideo = async () => {
    try {
      const {VideoTexture} = await import('three/src/textures/VideoTexture.js');
      const video = videoRef.current;
      if (video) {
        const tex = new VideoTexture(video);
        setTexture(tex);
        continueRender(handle);
      }
    } catch (error) {
      cancelRender(error);
    }
  };

  return {videoRef, texture, initializeVideo};
};

```

This pattern ensures the Three.js canvas only renders after the video texture is fully initialized, preventing black frames or incomplete WebGL scenes.

### Configuring Timeouts and Retry Logic

The `delayRender` function accepts an optional configuration object for timeout handling. When rendering in headless environments, this prevents indefinite hangs:

```tsx
const handle = delayRender('Fetching with retry logic', {
  retries: 3,
  timeoutInMilliseconds: 15000
});

```

According to the source in [`packages/core/src/delay-render.ts`](https://github.com/remotion-dev/remotion/blob/main/packages/core/src/delay-render.ts) (lines 80-90), when the timeout expires, Remotion automatically retries the render up to the specified number of attempts. If all retries exhaust, the render fails with an error message identifying the specific handle that caused the timeout.

## Key Source Files in the Remotion Repository

Understanding the implementation requires familiarity with these specific files in the remotion-dev/remotion repository:

- **[`packages/core/src/delay-render.ts`](https://github.com/remotion-dev/remotion/blob/main/packages/core/src/delay-render.ts)** – Core implementation containing `delayRenderInternal`, `continueRenderInternal`, and timeout management logic.
- **[`packages/core/src/use-delay-render.tsx`](https://github.com/remotion-dev/remotion/blob/main/packages/core/src/use-delay-render.tsx)** – React hook wrapping the core functions for component-level access via context.
- **[`packages/core/src/cancel-render.ts`](https://github.com/remotion-dev/remotion/blob/main/packages/core/src/cancel-render.ts)** – Error handling logic for aborting renders when async operations fail.
- **[`packages/three/src/use-video-texture.ts`](https://github.com/remotion-dev/remotion/blob/main/packages/three/src/use-video-texture.ts)** – Real-world example of texture loading with `delayRender`.
- **[`packages/template-react-router/app/remotion/load-fonts.ts`](https://github.com/remotion-dev/remotion/blob/main/packages/template-react-router/app/remotion/load-fonts.ts)** – Font loading implementation demonstrating the async pattern.

## Summary

- **`delayRender()`** creates a unique handle that pauses frame rendering until explicitly cleared, storing state in `window.remotion_delayRenderHandles` and setting `remotion_renderReady` to `false`.
- **`continueRender(handle)`** removes the specific handle from the pending list and resumes rendering when no handles remain, while **`cancelRender(error)`** aborts the entire render with the provided error.
- **The `useDelayRender()` hook** exposes these functions through React context, allowing components to manage async state without accessing global window objects directly.
- **Timeout and retry configuration** via `{retries: n, timeoutInMilliseconds: ms}` prevents indefinite hangs during headless rendering, automatically retrying failed attempts before throwing.
- **Common use cases** include API data fetching, custom font loading via `FontFace`, and WebGL texture preparation in `@remotion/three` compositions.

## Frequently Asked Questions

### What happens if I forget to call continueRender?

If `continueRender()` is never invoked for a given handle, the render will hang indefinitely in development mode. In headless rendering environments, the timeout mechanism will eventually trigger after the default or specified `timeoutInMilliseconds`, throwing an error that identifies the specific handle that remained open. Always ensure `continueRender` or `cancelRender` executes in both success and error paths of your async operations.

### Can I use multiple delayRender handles in one component?

Yes. You can call `delayRender()` multiple times to create independent handles for different asynchronous operations. The renderer will wait until **all** handles have been cleared via `continueRender()` before marking the frame as ready. This is useful when fetching multiple independent data sources or loading several assets concurrently, as each operation can resolve at its own pace while the frame remains blocked.

### How does delayRender handle timeouts and retries?

When you provide the `timeoutInMilliseconds` option, Remotion schedules a timer that checks if the handle still exists after the specified duration. If the handle persists, the render fails and automatically retries up to the number specified in the `retries` option (defaulting to 0). Each retry creates a fresh render attempt, and the timeout applies to each individual attempt. This logic is implemented in [`packages/core/src/delay-render.ts`](https://github.com/remotion-dev/remotion/blob/main/packages/core/src/delay-render.ts) lines 80-106.

### Is useDelayRender required or can I use the global functions directly?

While you can import and use `delayRender`, `continueRender`, and `cancelRender` directly from `'remotion'`, the `useDelayRender()` hook is the recommended approach for React components. The hook provides the same functions through React context, ensuring better testability and avoiding direct global window object access. Both approaches ultimately interact with the same internal state in `window.remotion_delayRenderHandles`, but the hook offers a more idiomatic React pattern that works seamlessly with the component lifecycle.