Remotion for Real Estate: Automate Property Listing Videos
By RenderComp Team Editorial policy
Remotion for Real Estate: Automate Property Listing Videos at Scale
Real estate is a visual industry. Buyers scroll through dozens of listings on Zillow, Redfin, and Realtor.com before they ever contact an agent, and the listings that capture attention are the ones with compelling video. Instagram Stories of a sun-drenched kitchen, a 9:16 walkthrough with neighborhood stats gracefully fading in, a price animation that makes a $549,000 home feel like a deal worth acting on. None of this qualifies as a luxury add-on anymore. For any agency competing in a crowded market, these have become table stakes.
The problem is volume. A mid-sized brokerage might manage 50 active listings at any moment. Each listing is unique: a different address, price, bedroom count, photography, and neighborhood context. Creating a handcrafted video for each one is not scalable at any price point. Most agencies either skip video entirely or pay for templated productions that look identical to every competitor’s output.
Remotion offers a third path. Because video compositions are React components driven by props, a single template can render differently for every listing in your database. Write the template once, feed it data, and you get a unique, on-brand video for every property without touching the timeline again.
From JSON to Listing Video
The pipeline is straightforward once you internalize the core idea: a Remotion composition is just a React component that renders over time. Data flows in as props; updating the JSON is all that changes between videos.
Your listing data probably lives in a CRM, an MLS export, or a database. For Remotion, you need to normalize it into a predictable JSON shape. Here is a representative structure:
{
"listings": [
{
"id": "prop-001",
"address": "2847 Maple Ridge Drive",
"city": "Austin",
"state": "TX",
"zipCode": "78701",
"price": 549000,
"pricePerSqFt": 312,
"bedrooms": 4,
"bathrooms": 2.5,
"sqft": 1759,
"lotSize": "0.18 acres",
"yearBuilt": 2003,
"photos": [
"https://cdn.example.com/prop-001/exterior.jpg",
"https://cdn.example.com/prop-001/kitchen.jpg",
"https://cdn.example.com/prop-001/living.jpg",
"https://cdn.example.com/prop-001/primary-suite.jpg"
],
"features": ["Granite countertops", "Hardwood floors", "2-car garage", "Pool"],
"neighborhood": "South Congress",
"walkScore": 72,
"schoolRating": "8/10",
"agentName": "Sarah Chen",
"agentPhone": "512-555-0192",
"brokerageName": "Apex Realty Group",
"listingUrl": "https://apexrealty.com/listings/prop-001"
}
]
}
Every key in this object maps to something you will animate in your composition. The address becomes a title card, the photos cycle through in a timed slideshow, and the bedroom and bathroom counts appear alongside an icon. The features list becomes a bullet sequence with staggered entrance animations.
Building the Property Listing Composition
Start with a 9:16 composition (1080×1920 pixels, 30fps) optimized for Instagram Stories and Reels. This format works for social sharing and can be letterboxed into 16:9 for MLS embeds when needed.
import { Composition } from 'remotion';
import { PropertyListingVideo } from './PropertyListingVideo';
import type { ListingProps } from './types';
export const RemotionRoot = () => {
return (
<Composition
id="PropertyListing"
component={PropertyListingVideo}
durationInFrames={270} // 9 seconds at 30fps
fps={30}
width={1080}
height={1920}
defaultProps={{
address: '2847 Maple Ridge Drive',
city: 'Austin',
state: 'TX',
price: 549000,
bedrooms: 4,
bathrooms: 2.5,
sqft: 1759,
photos: [],
features: [],
neighborhood: 'South Congress',
walkScore: 72,
schoolRating: '8/10',
agentName: 'Sarah Chen',
brokerageName: 'Apex Realty Group',
} as ListingProps}
/>
);
};
The main composition component handles the timeline. In a typical nine-second property video, the first two seconds show the hero photo with the address overlay fading in. A carousel of two or three interior shots follows through frame 120, and then the key stats panel (beds, baths, sqft, price) fills frames 120–180. Feature highlights animate as a staggered sequence through frame 240, with neighborhood stats and the agent call-to-action closing out the final three seconds.
Animating the Price Display
Price is the most emotionally loaded element in any listing video. A static number on screen is forgettable. An animated count-up from zero creates psychological engagement, drawing viewers into the sensation of watching value being revealed.
import { useCurrentFrame, interpolate, spring, useVideoConfig } from 'remotion';
type AnimatedPriceProps = {
targetPrice: number;
startFrame: number;
};
export const AnimatedPrice: React.FC<AnimatedPriceProps> = ({
targetPrice,
startFrame,
}) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const progress = spring({
frame: frame - startFrame,
fps,
config: {
damping: 18,
stiffness: 80,
mass: 0.8,
},
});
const displayPrice = Math.round(interpolate(progress, [0, 1], [0, targetPrice]));
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
maximumFractionDigits: 0,
}).format(displayPrice);
return (
<div style={{
fontSize: 72,
fontWeight: 700,
color: '#FFFFFF',
fontFamily: '-apple-system, "Segoe UI", Roboto, sans-serif',
textShadow: '0 2px 12px rgba(0,0,0,0.5)',
letterSpacing: '-0.02em',
}}>
{formatted}
</div>
);
};
The spring configuration matters here. A high damping value (around 18) creates a smooth deceleration that feels professional rather than mechanical. Too little damping and the number bounces past the target and back, which reads as cheap.
Photo Carousel with Ken Burns Effect
Static property photos feel dated. Apply a subtle Ken Burns (slow pan and zoom) effect to each image to add cinematic movement without pulling focus from the content.
import { Img, staticFile, interpolate, useCurrentFrame } from 'remotion';
type KenBurnsImageProps = {
src: string;
startFrame: number;
durationFrames: number;
direction?: 'zoom-in' | 'zoom-out' | 'pan-left' | 'pan-right';
};
export const KenBurnsImage: React.FC<KenBurnsImageProps> = ({
src,
startFrame,
durationFrames,
direction = 'zoom-in',
}) => {
const frame = useCurrentFrame();
const relativeFrame = frame - startFrame;
const progress = relativeFrame / durationFrames;
const scale = direction === 'zoom-in'
? interpolate(progress, [0, 1], [1.0, 1.08])
: interpolate(progress, [0, 1], [1.08, 1.0]);
const translateX = direction === 'pan-left'
? interpolate(progress, [0, 1], [0, -40])
: direction === 'pan-right'
? interpolate(progress, [0, 1], [0, 40])
: 0;
return (
<Img
src={src}
style={{
width: '100%',
height: '100%',
objectFit: 'cover',
transform: `scale(${scale}) translateX(${translateX}px)`,
}}
/>
);
};
Alternate the direction with each photo in the sequence so the visual rhythm feels deliberate rather than repetitive.
Neighborhood Stats Overlay
Modern buyers weigh walkability, school quality, and commute times alongside square footage. A neighborhood stats overlay, placed over a map screenshot or a neighborhood hero image, turns these data points into visual selling points.
type NeighborhoodStatsProps = {
neighborhood: string;
walkScore: number;
schoolRating: string;
startFrame: number;
};
export const NeighborhoodStats: React.FC<NeighborhoodStatsProps> = ({
neighborhood,
walkScore,
schoolRating,
startFrame,
}) => {
const frame = useCurrentFrame();
const opacity = interpolate(frame - startFrame, [0, 20], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const translateY = interpolate(frame - startFrame, [0, 20], [24, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const stats = [
{ label: 'Walk Score', value: `${walkScore}/100` },
{ label: 'Schools', value: schoolRating },
{ label: 'Neighborhood', value: neighborhood },
];
return (
<div style={{
opacity,
transform: `translateY(${translateY}px)`,
backgroundColor: 'rgba(0, 0, 0, 0.7)',
backdropFilter: 'blur(12px)',
borderRadius: 16,
padding: '24px 32px',
display: 'flex',
flexDirection: 'column',
gap: 12,
}}>
{stats.map(({ label, value }) => (
<div key={label} style={{ display: 'flex', justifyContent: 'space-between', gap: 24 }}>
<span style={{ color: 'rgba(255,255,255,0.7)', fontSize: 24, fontFamily: '-apple-system, "Segoe UI", Roboto, sans-serif' }}>
{label}
</span>
<span style={{ color: '#FFFFFF', fontWeight: 600, fontSize: 24, fontFamily: '-apple-system, "Segoe UI", Roboto, sans-serif' }}>
{value}
</span>
</div>
))}
</div>
);
};
Zillow/Redfin-Style Stat Cards
The standard MLS-style layout uses icon and number pairs for the headline specs. In Remotion, these work best as staggered entrance animations where each card slides in with a slight delay.
const STATS = [
{ icon: '🛏', label: 'Beds', value: bedrooms },
{ icon: '🚿', label: 'Baths', value: bathrooms },
{ icon: '📐', label: 'Sqft', value: sqft.toLocaleString() },
];
// Stagger each card by 8 frames
{STATS.map((stat, i) => {
const delay = i * 8;
const itemOpacity = interpolate(
frame - startFrame - delay,
[0, 16],
[0, 1],
{ extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }
);
const itemY = interpolate(
frame - startFrame - delay,
[0, 16],
[20, 0],
{ extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }
);
return (
<div key={stat.label} style={{
opacity: itemOpacity,
transform: `translateY(${itemY}px)`,
backgroundColor: 'rgba(255,255,255,0.12)',
borderRadius: 12,
padding: '20px 28px',
textAlign: 'center',
}}>
<div style={{ fontSize: 40 }}>{stat.icon}</div>
<div style={{ fontSize: 44, fontWeight: 700, color: '#FFFFFF', fontFamily: '-apple-system, "Segoe UI", Roboto, sans-serif' }}>
{stat.value}
</div>
<div style={{ fontSize: 22, color: 'rgba(255,255,255,0.65)', fontFamily: '-apple-system, "Segoe UI", Roboto, sans-serif' }}>
{stat.label}
</div>
</div>
);
})}
Using actual emoji icons rather than SVG avoids any import overhead and renders consistently across environments. For agency branding, replace them with inline SVG using your brand icon set.
Batch Generating 50+ Listings
The render loop is where Remotion’s real-estate value proposition becomes concrete. Rather than rendering once, you script the process to iterate over every listing in your JSON export.
import { renderMedia, selectComposition } from '@remotion/renderer';
import path from 'path';
import listings from './data/listings.json';
const BUNDLE_LOCATION = './out/bundle'; // pre-built with bundle()
async function renderAllListings() {
console.log(`Rendering ${listings.length} property videos...`);
// Process in batches of 5 to avoid memory pressure
const BATCH_SIZE = 5;
for (let i = 0; i < listings.length; i += BATCH_SIZE) {
const batch = listings.slice(i, i + BATCH_SIZE);
await Promise.all(batch.map(async (listing) => {
const composition = await selectComposition({
serveUrl: BUNDLE_LOCATION,
id: 'PropertyListing',
inputProps: listing,
});
const outputPath = path.join(
'./out/videos',
`${listing.id}-listing.mp4`
);
await renderMedia({
composition,
serveUrl: BUNDLE_LOCATION,
codec: 'h264',
outputLocation: outputPath,
inputProps: listing,
concurrency: 2,
});
console.log(`✓ ${listing.address} → ${outputPath}`);
}));
console.log(`Batch ${Math.ceil((i + BATCH_SIZE) / BATCH_SIZE)} complete`);
}
}
renderAllListings().catch(console.error);
Render time depends on your hardware, the composition, and your concurrency settings. A short 1080×1920 listing video renders in seconds per listing on a modern laptop, and moving to Remotion Lambda for cloud parallelization can cut the wall-clock time for a full batch substantially.
Aspect Ratios for Different Platforms
Different distribution channels require different aspect ratios:
| Platform | Ratio | Remotion Size |
|---|---|---|
| Instagram Stories / Reels | 9:16 | 1080 × 1920 |
| Facebook / YouTube | 16:9 | 1920 × 1080 |
| Instagram Feed / Facebook Post | 1:1 | 1080 × 1080 |
| TikTok | 9:16 | 1080 × 1920 |
Rather than maintaining separate templates, use a single responsive composition that accepts a format prop and adjusts layout accordingly. The core animations remain identical; only the container dimensions and text positioning shift.
// Render all three formats from one template
const formats = ['story', 'landscape', 'square'] as const;
for (const format of formats) {
await renderMedia({
composition: await selectComposition({
id: `PropertyListing_${format}`,
inputProps: { ...listing, format },
}),
outputLocation: `./out/${listing.id}-${format}.mp4`,
// ...
});
}
Agent CTA and Branding
Every listing video should end with the listing agent’s name and contact information. Since agent data is already in your JSON structure, this is a straightforward addition. Animate the agent card in during the final 2 seconds with a slide-up entrance, hold it for the last second, then fade to black with your brokerage logo.
The agent’s headshot, if available, can be loaded via Img with a subtle circular crop and a slight scale-up entrance:
<Img
src={agentPhotoUrl}
style={{
width: 80,
height: 80,
borderRadius: '50%',
objectFit: 'cover',
border: '3px solid rgba(255,255,255,0.8)',
transform: `scale(${spring({ frame: frame - ctaStartFrame, fps, config: { damping: 12, stiffness: 100 } })})`,
}}
/>
Practical Workflow for Real Estate Agencies
The full workflow for a brokerage looks like this:
- Export listings from MLS or CRM as JSON (most platforms support this natively)
- Normalize the export with a small transformation script to match your
ListingPropsschema - Download photos from the MLS CDN URLs into a local
photos/directory to avoid network latency during rendering - Run the batch script, saving output to
out/videos/with filenames keyed to listing ID - Upload to Instagram, the brokerage website, or embed in MLS listing pages via video CDN
For agencies managing ongoing listings, this pipeline can be triggered automatically whenever a new listing is published, via a webhook from the CRM, a nightly cron job, or a CI/CD pipeline.
Why Remotion Over Dedicated Real Estate Video Tools
Several SaaS tools exist specifically for real estate video creation. They are faster to start with but hit hard limits quickly. Template variety is constrained by what the vendor has built, branding options stop at logo uploads and color hex codes, and bulk generation usually carries per-video credit costs that add up painfully at scale.
Remotion gives you a blank canvas that happens to render to video. Your animation design is bounded only by what React can express. Pricing is fixed (the renderer is open-source; Lambda costs are infrastructure costs, not license fees), and the template compounds in value as you refine and extend it.
For agencies serious about differentiated digital marketing, the build-once, render-many approach Remotion enables is not just cheaper at scale — it is architecturally superior.
Getting Started with RenderComp Templates
Building a polished real estate video template from scratch takes time, especially when it comes to getting the animation curves, typography hierarchy, and photo transition timing to feel genuinely premium rather than amateur.
RenderComp provides production-ready Remotion templates purpose-built for data-driven video generation. The real estate collection includes a fully parameterized 9:16 listing template with animated price cards, photo carousels, neighborhood stat overlays, and a configurable agent CTA, all accepting the JSON structure described in this guide. Import your listing JSON and the render pipeline takes it from there.
Frequently asked questions
Can Remotion pull property photos directly from MLS CDN URLs, or do they need to be local files?
Remotion can load remote images via the `Img` component's `src` prop. However, for batch rendering, downloading photos locally first is strongly recommended. Remote fetching during rendering adds network latency, and MLS CDN URLs sometimes require authentication headers that are cumbersome to inject at render time. A pre-render download step (using `fetch` + `writeFile` in Node) typically saves 30–50% of total rendering time.
How do I handle listings where some photos are missing or fewer than expected?
Always validate your props before passing to the composition and provide fallback images. Define a `getPhotosWithFallback` utility that pads the array to your expected minimum length using a placeholder image — ideally a blurred brand-color gradient that looks intentional rather than broken.
What is the best codec for social media upload (Instagram, TikTok)?
H.264 (the default `h264` codec in Remotion) is the most broadly compatible for social platforms. Use a CRF value around 18–22 for a balance of quality and file size. Instagram recommends keeping Stories videos under 15MB; at 9 seconds and 1080×1920, H.264 at CRF 20 typically lands around 4–8MB.
Can I add background music to listing videos?
Yes. Use Remotion's [`Audio` component](/blog/remotion-audio-component-guide/) with a local audio file. For real estate social content, use royalty-free instrumental tracks and keep the volume low (around 15–25% with a short fade-out) so the video works equally well with and without sound, since many Instagram Stories autoplay muted.
How does price animation handle price reductions mid-campaign?
Since the price is a prop, you regenerate the video with the updated price and re-upload. For batch pipelines, it is worth adding a `priceChangedAt` timestamp to your schema and only re-rendering listings whose data has changed since the last run, using a simple checksum or modification timestamp comparison.
Is Remotion Lambda worth it for a 50-listing batch, or is local rendering sufficient?
For 50 listings, local rendering on a modern laptop completes in under 10 minutes — Lambda is overkill. Lambda becomes compelling above ~200 listings, when you need sub-5-minute turnaround for same-day listing publication, or when rendering is triggered by automated events (new listing webhooks) that need to complete without a developer's machine being available.
Can the template support property types beyond residential — commercial listings, rentals, vacation properties?
Absolutely. The template is just TypeScript props. Add optional fields for `propertyType`, `monthlyRent`, `capRate`, `occupancy`, or any other metric your listing type requires. Use conditional rendering within the composition to show the relevant stats panel based on `propertyType`. A single codebase can serve every listing category your agency manages.
Now available
Get 1,000+ Remotion Templates
Pay once — no subscription. Lifetime updates. TypeScript-first.
View pricing →