Skip to main content

delayRender() and continueRender()

delayRender() pauses the render to let an asynchronous task such as data fetching complete.
It has no effect when you are in a preview environment (e.g. Studio or Player).

This API returns a handle. Once you have fetched data or finished the asynchronous task, you should call continueRender(handle) to let Remotion know that the render can continue.

If the asynchronous task fails and you cannot recover, call cancelRender() to cancel the render.

delayRender() can be imported from the remotion package, but preferrably, you should use the useDelayRender() hook instead, because it future-proofs your code for browser rendering.

Example

tsx
import {useCallback, useEffect, useState} from 'react';
import {useDelayRender} from 'remotion';
 
export const MyVideo = () => {
const [data, setData] = useState(null);
const {delayRender, continueRender, cancelRender} = useDelayRender();
const [handle] = useState(() => delayRender());
 
const fetchData = useCallback(async () => {
try {
const response = await fetch('http://example.com/api');
const json = await response.json();
setData(json);
 
continueRender(handle);
} catch (err) {
cancelRender(err);
}
}, []);
 
useEffect(() => {
fetchData();
}, []);
 
return <div>{data ? <div>This video has data from an API! {JSON.stringify(data)}</div> : null}</div>;
};

Timeout

After calling delayRender(), you need to call continueRender() within 30 seconds or the render will fail with a timeout error. You can customize the timeout.

If continueRender() is not called within the timeout frame, the render will fail with an exception similarly to this:

A delayRender() was called but not cleared after 28000ms. See https://remotion.dev/docs/timeout for help. The delayRender was called

See the Timeout page to troubleshoot timeouts.

Adding a labelv2.6.13

If you encounter a timeout and don't know where it came from, you can add a label as a parameter:

tsx
delayRender('Fetching data from API...');

If the call times out, the label will be referenced in the error message:

Uncaught Error: A delayRender() "Fetching data from API..." was called but not cleared after 28000ms. See https://remotion.dev/docs/timeout for help. The delayRender was called

Multiple calls

You can call delayRender() multiple times. The render will be blocked for as long as at least one blocking handle exists that has not been cleared by continueRender().

tsx
import {useEffect, useState} from 'react';
import {useDelayRender} from 'remotion';
 
const MyComp: React.FC = () => {
const {delayRender, continueRender} = useDelayRender();
const [handle1] = useState(() => delayRender());
const [handle2] = useState(() => delayRender());
 
useEffect(() => {
// You need to clear all handles before the render continues
continueRender(handle1);
continueRender(handle2);
}, []);
 
return null;
};

Encapsulation

You should put delayRender() calls inside your components rather than placing them as a top-level statement, to avoid blocking a render if a different composition is rendered.

❌ Bug - Other compositions are blocked
tsx
import {useEffect} from 'react';
import {continueRender, delayRender} from 'remotion';
 
// Don't call a delayRender() call outside a component -
// it will block the render if a different composition is rendered
// as well as block the fetching of the list of compositions.
const handle = delayRender();
 
const MyComp: React.FC = () => {
useEffect(() => {
continueRender(handle);
}, []);
 
return null;
};

Also avoid creating a new handle on every React render:

❌ Bug - New handle is create on every React re-render
tsx
import {useEffect} from 'react';
import {continueRender, delayRender} from 'remotion';
 
const MyComp: React.FC = () => {
// New handle is created on every React re-render
const handle = delayRender();
 
useEffect(() => {
continueRender(handle);
}, []);
 
return null;
};

It's best to always use useDelayRender() in the first place since this mistake cannot happen and it enables browser rendering in the future.

✅ Best practice - useDelayRender() + useState()
tsx
import {useEffect, useState} from 'react';
import {useDelayRender} from 'remotion';
 
const MyComp: React.FC = () => {
const {delayRender, continueRender} = useDelayRender();
const [handle] = useState(() => delayRender());
 
useEffect(() => {
continueRender(handle);
}, []);
 
return null;
};

Data fetching

Note that data fetching can also be performed in calculateMetadata(), which has 2 advantages:

  • It runs only once, rather than the amount of concurrency.
  • You don't need to call continueRender() or cancelRender() manually.

Failing with an errorv4.0.374

If your code fails to do an asynchronous operation and you want to cancel the render, you can call cancelRender() with an error message. This will automatically cancel all delayRender() calls to not further delay the render.

MyComposition.tsx
tsx
import React, {useEffect, useState} from 'react';
import {cancelRender, continueRender, delayRender} from 'remotion';
 
export const MyComp: React.FC = () => {
const [handle] = useState(() => delayRender('Fetching data...'));
 
useEffect(() => {
fetch('https://example.com')
.then(() => {
continueRender(handle);
})
.catch((err) => cancelRender(err));
}, []);
 
return null;
};

Retryingv4.0.140

If an operation is flaky (for example, if loading an asset from a CDN does sometimes give 5xx errors), you can pass an object with a retries value as a second argument.
If a delayRender() call is not cleared within the timeout, the whole browser tab will be closed and the frame will be retried from scratch.

Retrying a delayRender()
tsx
delayRender('Loading asset...', {
retries: 1, // default: 0
});

The <Img>, <Video>, <Audio>, <Html5Audio>, <Html5Video> and <IFrame> tags support a delayRenderRetries prop to control the value of retries for the delayRender() call that those components make.

Modifying the timeoutv4.0.140

In addition to the global timeout that can be set, the timeout can be modified on a per-delayRender() level.

Modifying the timeout of a delayRender()
tsx
delayRender('Loading asset...', {
timeoutInMilliseconds: 7000,
});

The <Img>, <Video>, <Audio>, <Html5Audio>, <Html5Video> and <IFrame> tags support a delayRenderTimeoutInMilliseconds prop to control the value of timeoutInMilliseconds for the delayRender() call that those components make.

Difference to useBufferState().delayPlayback()

useBufferState() is a different API that allows pausing playback in the Studio and in the Player.
delayRender() only has an effect when a video is being rendered.

If you are loading data, you might want to both delay the screenshotting of your component during rendering and start a buffering state during Preview, in which case you need to use both APIs together.

Using delayRender() and delayPlayback() together
tsx
import React from 'react';
import {useBufferState, useDelayRender} from 'remotion';
 
const MyComp: React.FC = () => {
const buffer = useBufferState();
const {delayRender, continueRender} = useDelayRender();
const [handle] = React.useState(() => delayRender());
 
React.useEffect(() => {
const delayHandle = buffer.delayPlayback();
 
setTimeout(() => {
delayHandle.unblock();
continueRender(handle);
}, 5000);
 
return () => {
delayHandle.unblock();
};
}, []);
 
return <></>;
};

See also