# How to Handle Asynchronous Asset Loading with delayRender and continueRender in Remotion

> Master asynchronous asset loading in Remotion with delayRender and continueRender. Ensure your videos render perfectly by waiting for fonts and data to load. Learn how now.

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

---

**Use `delayRender()` to block frame rendering until async assets like fonts, videos, or data finish loading, then call `continueRender()` with the returned handle to unblock the render pipeline.**

When building videos with Remotion, components often need to load external assets asynchronously before the frame can be painted. Whether fetching data, decoding video frames, or loading custom fonts, the headless Chromium instance used by Remotion requires explicit synchronization. The `delayRender` and `continueRender` API in `remotion-dev/remotion` provides the mechanism to pause rendering until these asynchronous operations complete.

## Understanding the delayRender API

The API consists of three core functions exported from the Remotion core:

- **`delayRender(label?, options?)`**: Returns a numeric handle and marks the current frame as not ready. Internally stores the handle in `window.remotion_delayRenderHandles` and starts a default 30-second timeout.
- **`continueRender(handle)`**: Clears the timeout for the given handle, removes it from the internal list, and sets `remotion_renderReady = true` if no handles remain.
- **`cancelRender(error)`**: Immediately aborts the render with the supplied error message.

## Core Implementation Details

The implementation resides in [`packages/core/src/delay-render.ts`](https://github.com/remotion-dev/remotion/blob/main/packages/core/src/delay-render.ts). When `delayRender()` is invoked during rendering, it creates a timeout that is 2 seconds shorter than the user-provided or Puppeteer timeout, allowing Remotion to clean up before the browser kills the process. The system supports retry logic through constants like `DELAY_RENDER_RETRIES_LEFT` and `DELAY_RENDER_RETRY_TOKEN`.

The `useDelayRender()` hook, exported from [`packages/core/src/index.ts`](https://github.com/remotion-dev/remotion/blob/main/packages/core/src/index.ts), provides convenient access to these functions within React components, returning `{delayRender, continueRender, cancelRender}` bound to the current global scope.

## Practical Implementation Examples

### Loading Custom Fonts

When loading custom fonts in templates like the TikTok template, you must block rendering until the font is available:

```typescript
// packages/template-tiktok/src/load-font.ts
import { continueRender, delayRender, staticFile } from "remotion";

let loaded = false;

export const loadFont = async (): Promise<void> => {
  if (loaded) return;

  const waitForFont = delayRender("Loading custom font");
  loaded = true;

  const font = new FontFace(
    "TheBoldFont",
    `url('${staticFile("theboldfont.ttf")}') format('truetype')`
  );

  await font.load();
  document.fonts.add(font);
  
  continueRender(waitForFont);
};

```

### Video Textures in React Three Fiber

For Three.js video textures, use the `useDelayRender` hook to synchronize video loading:

```typescript
// packages/three/src/use-video-texture.ts
import { useDelayRender } from "remotion";

export const useVideoTexture = (videoRef) => {
  const { delayRender, continueRender, cancelRender } = useDelayRender();

  const loadHandle = delayRender(
    "Waiting for texture in useVideoTexture() to be loaded"
  );

  const onReady = () => {
    const vt = new VideoTexture(videoRef.current);
    setVideoTexture(vt);
    continueRender(loadHandle);
  };

  const onError = (error) => {
    cancelRender(error);
  };

  // Setup video element and event listeners...
};

```

### Off-Thread Video with Retries and Custom Timeouts

For complex assets like off-thread video frames, configure retries and per-asset timeouts:

```typescript
// packages/three/src/use-offthread-video-texture.ts
const fetchTexture = useCallback(() => {
  const handle = delayRender("fetch offthread video frame", {
    retries: delayRenderRetries,
    timeoutInMilliseconds: delayRenderTimeoutInMilliseconds,
  });

  loader
    .loadAsync(offthreadVideoFrameSrc)
    .then((texture) => {
      setImageTexture(texture);
      continueRender(handle);
    })
    .catch((err) => cancelRender(err));
}, [offthreadVideoFrameSrc, delayRenderRetries, delayRenderTimeoutInMilliseconds]);

```

## Error Handling and Timeout Management

The `delayRender` API includes robust timeout handling to prevent indefinite hangs. By default, each handle has a 30-second timeout, though this adjusts dynamically during rendering to be 2 seconds shorter than the Puppeteer timeout. If `continueRender` is not called within this window, the render fails with a timeout error.

For error scenarios, `cancelRender(error)` immediately terminates the render and propagates the error to the CLI. This is essential when async operations like network requests or video decoding fail, ensuring that broken assets don't produce silent blank frames.

## Summary

- **Use `delayRender()`** to block frame rendering when loading asynchronous assets like fonts, videos, or external data.
- **Call `continueRender(handle)`** after the asset loads successfully to unblock the render pipeline.
- **Handle failures with `cancelRender(error)`** to abort rendering when async operations fail.
- **Configure timeouts and retries** via the options parameter for complex assets requiring multiple attempts.
- **Reference the core implementation** in [`packages/core/src/delay-render.ts`](https://github.com/remotion-dev/remotion/blob/main/packages/core/src/delay-render.ts) and use the `useDelayRender()` hook from [`packages/core/src/index.ts`](https://github.com/remotion-dev/remotion/blob/main/packages/core/src/index.ts) for React integration.

## Frequently Asked Questions

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

If you call `delayRender()` but never invoke `continueRender()` with the returned handle, the render will timeout after the default 30 seconds (or your custom timeout) and fail with an error message indicating which label was never resolved.

### Can I use multiple delayRender calls in the same component?

Yes. Each call to `delayRender()` returns a unique numeric handle stored in the global `window.remotion_delayRenderHandles` map. The render remains blocked until `continueRender()` is called for every outstanding handle, allowing you to load multiple assets in parallel.

### How do I set a custom timeout for a specific asset?

Pass a `timeoutInMilliseconds` option to `delayRender()`. For example: `delayRender("Loading video", { timeoutInMilliseconds: 60000 })` sets a 60-second timeout for that specific asset, overriding the default 30-second limit.

### What is the difference between delayRender and useDelayRender?

`delayRender` and `continueRender` are the core functions that manage the render state. `useDelayRender()` is a React hook exported from `remotion` that simply returns these functions bound to the current global scope, making them convenient to use within functional components without manual window references.