Data fetching
In Remotion, you may fetch data from an API to use it in your video. On this page, we document recipes and best practices.
Fetching data before the render
You may use the calculateMetadata
prop of the <Composition />
component to alter the props that get passed to your React component.
When to use
The data being fetched using calculateMetadata()
must be JSON-serializable. That means it is useful for API responses, but not for assets in binary format.
Usage
Pass a callback function which takes the untransformed props
, and return an object with the new props.
src/Root.tsxtsx
import {Composition } from "remotion";typeApiResponse = {title : string;description : string;};typeMyCompProps = {id : string;data :ApiResponse | null;};constMyComp :React .FC <MyCompProps > = () => null;export constRoot :React .FC = () => {return (<Composition id ="MyComp"component ={MyComp }durationInFrames ={300}fps ={30}width ={1920}height ={1080}defaultProps ={{id : "1",data : null,}}calculateMetadata ={async ({props }) => {constdata = awaitfetch (`https://example.com/api/${props .id }`);constjson = awaitdata .json ();return {props : {...props ,data :json ,},};}}/>);};
src/Root.tsxtsx
import {Composition } from "remotion";typeApiResponse = {title : string;description : string;};typeMyCompProps = {id : string;data :ApiResponse | null;};constMyComp :React .FC <MyCompProps > = () => null;export constRoot :React .FC = () => {return (<Composition id ="MyComp"component ={MyComp }durationInFrames ={300}fps ={30}width ={1920}height ={1080}defaultProps ={{id : "1",data : null,}}calculateMetadata ={async ({props }) => {constdata = awaitfetch (`https://example.com/api/${props .id }`);constjson = awaitdata .json ();return {props : {...props ,data :json ,},};}}/>);};
The props
being passed to calculateMetadata()
are the input props merged together with the default props.
In addition to props
, defaultProps
can also be read from the same object.
When transforming, the input and the output must be the same TypeScript type.
Consider using a nullable type for your data and throw an error inside your component to deal with the null
type:
MyComp.tsxtsx
typeMyCompProps = {id : string;data :ApiResponse | null;};constMyComp :React .FC <MyCompProps > = ({data }) => {if (data === null) {throw newError ("Data was not fetched");}return <div >{data .title }</div >;};
MyComp.tsxtsx
typeMyCompProps = {id : string;data :ApiResponse | null;};constMyComp :React .FC <MyCompProps > = ({data }) => {if (data === null) {throw newError ("Data was not fetched");}return <div >{data .title }</div >;};
TypeScript typing
You may use the CalculateMetadataFunction
type from remotion
to type your callback function. Pass as the generic value (<>
) the type of your props.
src/Root.tsxtsx
import {CalculateMetadataFunction } from "remotion";export constcalculateMyCompMetadata :CalculateMetadataFunction <MyCompProps > = ({props }) => {return {props : {...props ,data : {title : "Hello world",description : "This is a description",},},};};export constMyComp :React .FC <MyCompProps > = () => null;
src/Root.tsxtsx
import {CalculateMetadataFunction } from "remotion";export constcalculateMyCompMetadata :CalculateMetadataFunction <MyCompProps > = ({props }) => {return {props : {...props ,data : {title : "Hello world",description : "This is a description",},},};};export constMyComp :React .FC <MyCompProps > = () => null;
Colocation
Here is an example of how you could define a schema, a component and a fetcher function in the same file:
MyComp.tsxtsx
import {CalculateMetadataFunction } from "remotion";import {z } from "zod";constapiResponse =z .object ({title :z .string (),description :z .string () });export constmyCompSchema =z .object ({id :z .string (),data :z .nullable (apiResponse ),});typeProps =z .infer <typeofmyCompSchema >;export constcalcMyCompMetadata :CalculateMetadataFunction <Props > = async ({props ,}) => {constdata = awaitfetch (`https://example.com/api/${props .id }`);constjson = awaitdata .json ();return {props : {...props ,data :json ,},};};export constMyComp :React .FC <Props > = ({data }) => {if (data === null) {throw newError ("Data was not fetched");}return <div >{data .title }</div >;};
MyComp.tsxtsx
import {CalculateMetadataFunction } from "remotion";import {z } from "zod";constapiResponse =z .object ({title :z .string (),description :z .string () });export constmyCompSchema =z .object ({id :z .string (),data :z .nullable (apiResponse ),});typeProps =z .infer <typeofmyCompSchema >;export constcalcMyCompMetadata :CalculateMetadataFunction <Props > = async ({props ,}) => {constdata = awaitfetch (`https://example.com/api/${props .id }`);constjson = awaitdata .json ();return {props : {...props ,data :json ,},};};export constMyComp :React .FC <Props > = ({data }) => {if (data === null) {throw newError ("Data was not fetched");}return <div >{data .title }</div >;};
src/Root.tsxtsx
importReact from "react";import {Composition } from "remotion";import {MyComp ,calcMyCompMetadata ,myCompSchema } from "./MyComp";export constRoot = () => {return (<Composition id ="MyComp"component ={MyComp }durationInFrames ={300}fps ={30}width ={1920}height ={1080}defaultProps ={{id : "1",data : null,}}schema ={myCompSchema }calculateMetadata ={calcMyCompMetadata }/>);};
src/Root.tsxtsx
importReact from "react";import {Composition } from "remotion";import {MyComp ,calcMyCompMetadata ,myCompSchema } from "./MyComp";export constRoot = () => {return (<Composition id ="MyComp"component ={MyComp }durationInFrames ={300}fps ={30}width ={1920}height ={1080}defaultProps ={{id : "1",data : null,}}schema ={myCompSchema }calculateMetadata ={calcMyCompMetadata }/>);};
By implementing this pattern, The id
in the props editor can now be tweaked and Remotion will refetch the data whenever it changes.
Setting the duration based on data
You may set the durationInFrames
, fps
, width
and height
by returning those keys in the callback function:
tsx
import {CalculateMetadataFunction } from "remotion";typeMyCompProps = {durationInSeconds : number;};export constcalculateMyCompMetadata :CalculateMetadataFunction <MyCompProps > = ({props }) => {constfps = 30;constdurationInSeconds =props .durationInSeconds ;return {durationInFrames :durationInSeconds *fps ,fps ,};};
tsx
import {CalculateMetadataFunction } from "remotion";typeMyCompProps = {durationInSeconds : number;};export constcalculateMyCompMetadata :CalculateMetadataFunction <MyCompProps > = ({props }) => {constfps = 30;constdurationInSeconds =props .durationInSeconds ;return {durationInFrames :durationInSeconds *fps ,fps ,};};
Learn more about this feature in the Variable metadata page.
Aborting stale requests
The props in the props editor may rapidly change for example by typing fast.
It is a good practice to cancel requests which are stale using the abortSignal
that gets passed to the calculateMetadata()
function:
src/MyComp.tsxtsx
export const calculateMyCompMetadata: CalculateMetadataFunction<MyCompProps> = async ({ props, abortSignal }) => {const data = await fetch(`https://example.com/api/${props.id}`, { signal: abortSignal, });const json = await data.json();return { props: {...props,data: json,},};};export const MyComp: React.FC<MyCompProps> = () => null;
src/MyComp.tsxtsx
export const calculateMyCompMetadata: CalculateMetadataFunction<MyCompProps> = async ({ props, abortSignal }) => {const data = await fetch(`https://example.com/api/${props.id}`, { signal: abortSignal, });const json = await data.json();return { props: {...props,data: json,},};};export const MyComp: React.FC<MyCompProps> = () => null;
This abortSignal
is created by Remotion using the AbortController
API.
Debouncing requests
If you are making requests to an expensive API, you might want to only fire a request after the user has stopped typing for a while. You may use the following function for doing so:
src/wait-for-no-input.tstsx
import {getRemotionEnvironment } from "remotion";export constwaitForNoInput = (signal :AbortSignal ,ms : number) => {// Don't wait during renderingif (getRemotionEnvironment ().isRendering ) {returnPromise .resolve ();}if (signal .aborted ) {returnPromise .reject (newError ("stale"));}returnPromise .race <void>([newPromise <void>((_ ,reject ) => {signal .addEventListener ("abort", () => {reject (newError ("stale"));});}),newPromise <void>((resolve ) => {setTimeout (() => {resolve ();},ms );}),]);};
src/wait-for-no-input.tstsx
import {getRemotionEnvironment } from "remotion";export constwaitForNoInput = (signal :AbortSignal ,ms : number) => {// Don't wait during renderingif (getRemotionEnvironment ().isRendering ) {returnPromise .resolve ();}if (signal .aborted ) {returnPromise .reject (newError ("stale"));}returnPromise .race <void>([newPromise <void>((_ ,reject ) => {signal .addEventListener ("abort", () => {reject (newError ("stale"));});}),newPromise <void>((resolve ) => {setTimeout (() => {resolve ();},ms );}),]);};
src/MyComp.tsxtsx
export constcalculateMyCompMetadata :CalculateMetadataFunction <MyCompProps > = async ({props ,abortSignal }) => {awaitwaitForNoInput (abortSignal , 750);constdata = awaitfetch (`https://example.com/api/${props .id }`, {signal :abortSignal ,});constjson = awaitdata .json ();return {props : {...props ,data :json ,},};};export constMyComp :React .FC <MyCompProps > = () => null;
src/MyComp.tsxtsx
export constcalculateMyCompMetadata :CalculateMetadataFunction <MyCompProps > = async ({props ,abortSignal }) => {awaitwaitForNoInput (abortSignal , 750);constdata = awaitfetch (`https://example.com/api/${props .id }`, {signal :abortSignal ,});constjson = awaitdata .json ();return {props : {...props ,data :json ,},};};export constMyComp :React .FC <MyCompProps > = () => null;
Time limit
When Remotion calls the calculateMetadata()
function, it wraps it in a delayRender()
, which by default times out after 30 seconds.
Fetching data during the render
Using delayRender()
and continueRender()
you can tell Remotion to wait for asynchronous operations to finish before rendering a frame.
When to use
Use this approach to load assets which are not JSON serializable or if you are using a Remotion version lower than 4.0.
Usage
Call delayRender()
as soon as possible, for example when initializing the state inside your component.
tsx
import {useCallback ,useEffect ,useState } from "react";import {cancelRender ,continueRender ,delayRender } from "remotion";export constMyComp = () => {const [data ,setData ] =useState (null);const [handle ] =useState (() =>delayRender ());constfetchData =useCallback (async () => {try {constresponse = awaitfetch ("http://example.com/api");constjson = awaitresponse .json ();setData (json );continueRender (handle );} catch (err ) {cancelRender (err );}}, [handle ]);useEffect (() => {fetchData ();}, [fetchData ]);return (<div >{data ? (<div >This video has data from an API! {JSON .stringify (data )}</div >) : null}</div >);};
tsx
import {useCallback ,useEffect ,useState } from "react";import {cancelRender ,continueRender ,delayRender } from "remotion";export constMyComp = () => {const [data ,setData ] =useState (null);const [handle ] =useState (() =>delayRender ());constfetchData =useCallback (async () => {try {constresponse = awaitfetch ("http://example.com/api");constjson = awaitresponse .json ();setData (json );continueRender (handle );} catch (err ) {cancelRender (err );}}, [handle ]);useEffect (() => {fetchData ();}, [fetchData ]);return (<div >{data ? (<div >This video has data from an API! {JSON .stringify (data )}</div >) : null}</div >);};
Once the data is fetched, you can call continueRender()
to tell Remotion to continue rendering the video.
In case the data fetching fails, you can call cancelRender()
to cancel the render without waiting for the timeout.
Time limit
You need to clear all handles created by delayRender()
within 30 seconds after the page is opened. You may increase the timeout.
Prevent overfetching
During rendering, multiple headless browser tabs are opened to speed up rendering.
In Remotion Lambda, the rendering concurrency may be up to 200x.
This means that if you are fetching data inside your component, the data fetching will be performed many times.
frame
as a dependency of the useEffect()
, directly or indirectly, otherwise data will be fetched every frame leading to slowdown and potentially running into rate limits.