---
name: remotion-best-practices
description: Best practices for building videos with Remotion - React-based programmatic video framework. Use when creating MP4 videos with React components, generating dynamic video content, or building video generation pipelines with TypeScript + React.
---

# Remotion Best Practices

Comprehensive guide for building production videos with Remotion. Follow these conventions when creating MP4 videos with React components.

## Project setup

Install Remotion in a fresh project:

```bash
npx create-video@latest
# or
npm i remotion @remotion/cli @remotion/bundler
```

Project structure:

```
src/
  Composition.tsx     # main video component
  Root.tsx            # register compositions
  index.ts            # entry point
  compositions/
    WelcomeVideo.tsx
    ProductShowcase.tsx
public/                # static assets (images, fonts)
```

## Composition setup

Register every video variant with `<Composition />`:

```tsx
import { Composition } from 'remotion';
import { WelcomeVideo } from './compositions/WelcomeVideo';

export const RemotionRoot: React.FC = () => {
  return (
    <>
      <Composition
        id="welcome"
        component={WelcomeVideo}
        durationInFrames={180}
        fps={30}
        width={1920}
        height={1080}
        defaultProps={{ name: 'Dvir' }}
      />
    <\/>
  );
};
```

### Standard sizes
- 1920x1080 - Full HD (landscape, YouTube)
- 1080x1920 - Vertical (Reels, TikTok, Stories)
- 1080x1080 - Square (Instagram feed)
- 1280x720 - HD (lightweight)

### Standard fps
- 30 - default for most content (balance)
- 60 - gaming, sports, smooth motion
- 24 - cinematic look

## Animation

Use `useCurrentFrame()` + `interpolate()` for smooth animations:

```tsx
import { useCurrentFrame, interpolate } from 'remotion';

export const FadeIn: React.FC = () => {
  const frame = useCurrentFrame();
  const opacity = interpolate(frame, [0, 30], [0, 1], {
    extrapolateRight: 'clamp',
  });
  return <div style={{ opacity }}>Hello<\/div>;
};
```

Use `spring()` for natural motion:

```tsx
import { spring, useCurrentFrame, useVideoConfig } from 'remotion';

const { fps } = useVideoConfig();
const frame = useCurrentFrame();
const scale = spring({
  frame,
  fps,
  config: { damping: 200, stiffness: 100, mass: 1 },
});
```

Stagger animations with delay:

```tsx
const frame = useCurrentFrame();
const items = [0, 1, 2, 3].map(i => {
  const opacity = interpolate(frame - i * 10, [0, 20], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });
  return { opacity };
});
```

## Performance rules

1. Keep components pure - no side effects, no useState for video state (use props)
2. Use `<Sequence />` to mount components only when visible:

```tsx
<Sequence from={60} durationInFrames={90}>
  <SecondScene />
<\/Sequence>
```

3. Use Remotion's `<Img />` instead of `<img>` for proper asset loading:

```tsx
import { Img, staticFile } from 'remotion';

<Img src={staticFile('logo.png')} />
```

4. Pre-load assets that depend on network:

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

const [handle] = useState(() => delayRender());

useEffect(() => {
  fetch('/api/data').then(() => continueRender(handle));
}, []);
```

## Rendering

Local render:

```bash
npx remotion render welcome out/video.mp4 
  --codec=h264 
  --concurrency=4
```

Options:
- `--codec` - h264 (default), h265, vp8, vp9, prores
- `--quality` - 1-100 for h264 (default 80)
- `--pixel-format` - yuv420p (compatible), yuv444p (higher quality)
- `--frame-range` - render subset: `0-59`

Server-side render with Lambda for scale:

```bash
npx remotion lambda render 
  https://my-bucket.s3.amazonaws.com/bundle 
  welcome 
  --composition=welcome
```

## Dynamic data via props

Pass data when rendering:

```bash
npx remotion render welcome out/video.mp4 
  --props='{"name":"John","items":[1,2,3]}'
```

Access in component:

```tsx
export const WelcomeVideo: React.FC<{name: string}> = ({name}) => {
  return <div>Hello {name}<\/div>;
};
```

## Studio development

Live preview with hot reload:

```bash
npx remotion studio
```

Opens at http://localhost:3000 with:
- Timeline scrubbing
- Props editor
- Composition switcher
- Render button

## TypeScript

Always use TypeScript for video components. Props typed with Zod for runtime validation:

```tsx
import { z } from 'zod';

const schema = z.object({
  name: z.string(),
  count: z.number(),
});

export type Props = z.infer<typeof schema>;

<Composition
  id="welcome"
  component={WelcomeVideo}
  schema={schema}
  defaultProps={{ name: 'Dvir', count: 5 }}
  ...
/>
```

## Common patterns

### Progress bar
```tsx
const frame = useCurrentFrame();
const { durationInFrames } = useVideoConfig();
const progress = frame / durationInFrames;
```

### Audio sync
```tsx
import { Audio, staticFile } from 'remotion';

<Audio src={staticFile('music.mp3')} />
```

### Video concatenation
```tsx
<Sequence from={0} durationInFrames={60}>
  <FirstScene />
<\/Sequence>
<Sequence from={60} durationInFrames={60}>
  <SecondScene />
<\/Sequence>
```

### Variable-rate animation
```tsx
const speed = interpolate(frame, [0, 30, 60], [1, 3, 1]);
const position = frame * speed;
```

## Deployment

For production video generation:

1. Bundle once: `npx remotion bundle`
2. Deploy to S3/Lambda: `npx remotion lambda sites create bundle/ --site-name=my-site`
3. Render via API: `npx remotion lambda render ... --composition=welcome`

For serverless with props:

```typescript
import { renderMediaOnLambda } from '@remotion/lambda/client';

const { bucketName, renderId } = await renderMediaOnLambda({
  region: 'us-east-1',
  functionName: 'remotion-render',
  composition: 'welcome',
  serveUrl: 'https://xxx.amazonaws.com/sites/my-site/index.html',
  inputProps: { name: 'John' },
  codec: 'h264',
});
```

## Testing checklist before production

- Render a single frame: `--frame=30`
- Test lowest quality first: `--quality=50`
- Verify fonts load (use `<Font />` or @font-face)
- Test with actual production data, not test values
- Measure render time per video to estimate costs
- Monitor Lambda timeout (default 120s, max 900s)

## Common pitfalls

- Using `setState` inside components - state is tied to frame, not time
- Using `Math.random()` - produces different output each render (use seeded random)
- Date/time functions returning current time (bake in via props)
- External fetches without `delayRender` (missing content on render)
- Using animations based on timeouts (use frame-based animation only)