Rendering Outside the Editor
Use the useRender hook to trigger video rendering from any React component without mounting the full editor
Rendering Outside the Editor
The useRender hook lets you trigger video rendering from any React component — without mounting the full <ReactVideoEditor>. This is useful when you want to render from a dashboard, batch queue, custom UI, or anywhere outside the editor.
It supports both cloud rendering (via your VideoRenderer) and browser rendering (via WebCodecs), and returns a promise-based API with full state tracking.
import { useRender } from '@reactvideoeditor/react-video-editor/hooks/use-render';Quick Example
import { useRender } from '@reactvideoeditor/react-video-editor/hooks/use-render';
function RenderButton({ tracks, renderer }) {
const { render, state, cancel } = useRender('my-composition', {
tracks,
width: 1920,
height: 1080,
fps: 30,
renderer, // cloud rendering
});
const handleRender = async () => {
try {
const result = await render('1080p');
console.log('Video ready:', result.url, result.size);
} catch (err) {
console.error('Render failed:', err);
}
};
return (
<div>
<button onClick={handleRender} disabled={state.status === 'rendering'}>
{state.status === 'rendering'
? `Rendering ${Math.round(state.progress * 100)}%`
: 'Render Video'}
</button>
{state.status === 'rendering' && (
<button onClick={cancel}>Cancel</button>
)}
{state.status === 'done' && (
<a href={state.url} download>Download</a>
)}
</div>
);
}Browser Rendering
To render entirely in the browser without a server, use webRender: true instead of providing a renderer:
const { render, state } = useRender('my-composition', {
tracks,
width: 1920,
height: 1080,
fps: 30,
webRender: true,
});
const result = await render('1080p');
// result.url — blob URL to the rendered MP4Browser requirements
Browser rendering requires WebCodecs support (Chrome 94+ or Edge 94+). See Client-Side Rendering for browser compatibility details.
Options
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
tracks | Track[] | Yes | — | The tracks to render |
width | number | Yes | — | Composition width in pixels |
height | number | Yes | — | Composition height in pixels |
fps | number | Yes | — | Frames per second |
renderer | VideoRenderer | — | — | Cloud renderer instance, such as an HttpRenderer connected to RVE Cloud Rendering. Either renderer or webRender must be provided |
webRender | boolean | — | false | Enable browser-side rendering via WebCodecs |
background | CanvasBackground | — | — | Canvas background: { type: 'color' | 'gradient' | 'css', value } or { type: 'image', src, fit, position, blur }. It replaces backgroundColor |
backgroundColor | string | — | 'white' | Deprecated. Use background |
watermark | WatermarkConfig | — | — | Watermark for browser renders. true shows the RVE badge. An object { src, position?, opacity?, scale?, padding? } shows your image. See Watermark |
quality | 'very-low' | 'low' | 'medium' | 'high' | 'very-high' | — | 'high' | Browser render quality. Cloud renders ignore it. See Export quality |
videoBitrate | number | — | — | Browser video bitrate in bits per second. It overrides quality |
audioBitrate | number | — | — | Browser audio bitrate in bits per second. It overrides quality |
baseUrl | string | — | — | Optional base URL for asset resolution |
pollingInterval | number | — | 1000 | Cloud render progress polling interval (ms) |
initialDelay | number | — | 0 | Delay before first progress poll (ms) |
onExportStart | function | — | — | Called when rendering begins |
onExportProgress | function | — | — | Called for progress changes and periodic heartbeats until rendering ends |
onExportComplete | function | — | — | Called when rendering succeeds |
onExportFail | function | — | — | Called when rendering fails |
onExportCancel | function | — | — | Called when the user calls cancel(). A cancellation does not call onExportFail |
exportProgressIntervalMs | number | — | 1000 | Time between progress heartbeats (ms). Set a value of 0 or less to stop heartbeats |
webRenderStallTimeoutMs | number | — | 300000 | Maximum visible-tab time without browser render progress (ms). Set 0 to stop this timeout |
webRenderFinalizationTimeoutMs | number | — | 1800000 | Maximum visible-tab time for each browser finalization phase (ms) |
Return Value
The hook returns an object with four properties:
| Property | Type | Description |
|---|---|---|
render | (resolution?: ExportResolution, exportScale?: number) => Promise<RenderResult> | Start a render. Resolves with { url, size } on success. Defaults to '1080p'. Pass an optional exportScale to override the preset — see Exporting Larger than the Canvas |
state | RenderState | Current render state (see below) |
cancel | () => void | Cancel an in-progress render and reset to init |
reset | () => void | Reset state back to init, cancelling any in-progress render |
RenderState
The state is a discriminated union — TypeScript narrows the available fields based on status:
type RenderState =
| { status: 'init' }
| { status: 'invoking' }
| {
status: 'rendering';
renderId: string;
progress: number;
phase?: 'preparing' | 'rendering' | 'muxing' | 'storing';
bucketName?: string;
}
| { status: 'error'; renderId: string | null; error: Error }
| { status: 'done'; url: string; size: number };RenderResult
type RenderResult = { url: string; size: number };Lifecycle Callbacks
The hook fires the same lifecycle callbacks as the editor's built-in rendering:
const { render } = useRender('my-composition', {
tracks,
width: 1920,
height: 1080,
fps: 30,
renderer,
onExportStart: ({ compositionId, durationInSeconds, renderType, estimatedSizeInBytes }) => {
// estimatedSizeInBytes is only present for browser renders
console.log('Render started', compositionId, estimatedSizeInBytes);
},
onExportProgress: ({
phase,
progress,
renderedFrames,
encodedFrames,
totalFrames,
timestamp,
heartbeat,
}) => {
console.log({
phase,
progress,
renderedFrames,
encodedFrames,
totalFrames,
timestamp,
heartbeat,
});
},
onExportComplete: ({ compositionId, url, sizeInBytes, durationInSeconds, renderType }) => {
console.log('Render complete', url);
},
onExportFail: ({ compositionId, error, durationInSeconds, renderType }) => {
console.error('Render failed', error);
},
onExportCancel: ({ compositionId, durationInSeconds, renderType }) => {
console.log('Render cancelled', compositionId);
},
});onExportStart receives estimatedSizeInBytes for browser renders. The value comes from the bitrate and the duration. onExportComplete receives the real sizeInBytes.
onExportProgress reports a normalized progress value from 0 to 1. The phase is preparing, rendering, muxing, or storing. Browser exports also report renderedFrames and encodedFrames. A heartbeat repeats the latest values with a new timestamp and sets heartbeat to true.
The muxing and storing phases can continue after progress reaches 1. Wait for onExportComplete or for the render() promise to resolve before you treat the file as complete.
Browser exports use a watchdog for a stalled render. They also use a separate deadline for each finalization phase. These deadlines pause while the document is hidden. A timeout calls onExportFail. A user cancellation calls onExportCancel instead.
Errors Before the Render Starts
For a browser render, render() checks quality, videoBitrate, and audioBitrate first. If quality has an unknown value, or a bitrate is not a positive number, these things occur:
statechanges to{ status: 'error' }.render()throws the error.onExportStartandonExportFailare not called.
const { render } = useRender('my-composition', {
tracks,
width: 1920,
height: 1080,
fps: 30,
webRender: true,
quality: 'medium',
});Cancellation and Cleanup
cancel()aborts any in-flight render and resets state toinitreset()does the same — cancels and resets- Unmounting the component automatically cancels any in-progress render and cleans up resources (blob URLs, OPFS assets)
- Only
cancel()callsonExportCancel. Internal cleanup fromreset()or unmount does not call an export lifecycle failure callback
const { render, cancel } = useRender('my-composition', { /* ... */ });
// Start a render
const promise = render('1080p');
// Cancel it mid-way
cancel();
// The promise will reject with an AbortErrorResolution
The render() function accepts an optional ExportResolution parameter:
| Resolution | Short Edge |
|---|---|
'720p' | 720px |
'1080p' | 1080px (default) |
'4k' | 2160px |
'original' | The composition size (scale 1, no scaling) |
The actual pixel dimensions are calculated from the width and height you provide, scaled to match the target resolution.
Exporting Larger than the Canvas
By default the export size is chained to the resolution preset. If you want to edit on a small canvas but export at full source resolution, pass an optional exportScale multiplier as the second argument to render(). When supplied, it overrides the resolution preset and drives the render scale directly:
// Preview/edit at 1332×720, export at ~3840×2076
const result = await render('original', 2.88);- When
exportScaleis provided, it takes precedence over theresolutionpreset. - When omitted, behaviour is unchanged — the preset drives the scale as before, so existing callers are unaffected.
exportScalemust be a finite, positive number. Invalid values (e.g.0, negative,NaN,Infinity) throw synchronously.
Hosts that think in pixels can compute the multiplier from the target width themselves — e.g. targetWidth / compositionWidth.
Cloud renders need a composition that reads the scale
Browser renders always apply exportScale. For a self-managed cloud render, the SDK sends inputProps.scale. Your composition must multiply its width and height by that value in calculateMetadata. If the composition uses a fixed size, the cloud render ignores the scale. The hook logs a console.warn when you use exportScale with a cloud render. See Custom compositions.
Types
Both RenderState and RenderResult are exported from the hook and from the main types entrypoint:
import type { RenderState, RenderResult } from '@reactvideoeditor/react-video-editor/hooks/use-render';
// or
import type { RenderState, RenderResult } from '@reactvideoeditor/react-video-editor/types';