R RenderComp
remotion real-estate automation property-video batch-rendering

Remotion for Real Estate: Automate Property Listing Videos at Scale

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 — these are not luxury add-ons anymore. They are table stakes for any agency competing in a crowded market.

The problem is volume. A mid-sized brokerage might manage 50 active listings at any moment. Each listing is unique — 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. You write the template once. You feed it data. You get a unique, on-brand video for every property — automatically.


The Architecture: 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. Props replace manual edits.

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. The bedroom and bathroom counts appear with an icon alongside. 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 but can be letterboxed into 16:9 for MLS embeds if 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. A typical 9-second property video follows this structure:

  • Frames 0–60: Hero photo with address overlay fading in
  • Frames 60–120: Photo carousel (2–3 interior shots)
  • Frames 120–180: Key stats panel (beds, baths, sqft, price)
  • Frames 180–240: Feature highlights with animated bullets
  • Frames 240–270: Neighborhood stats + agent call-to-action

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 — it makes the viewer feel like they are 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.


Static property photos feel dated. Apply a subtle Ken Burns (slow pan and zoom) effect to each image to add cinematic movement without distracting 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 intentional rather than repetitive.


Neighborhood Stats Overlay

Modern buyers care about walkability, school quality, and commute times as much as square footage. A neighborhood stats overlay — appearing 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 + 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 with inline SVG using your brand icon set.


Batch Generating 50+ Listings

The render loop is where Remotion’s real-estate value proposition crystallizes. Rather than rendering once, you script the rendering 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);

On a modern MacBook Pro (M3), this pipeline renders a 9-second 1080×1920 video in roughly 8–12 seconds per listing. Fifty listings complete in under 10 minutes. Move to Remotion Lambda for cloud parallelization and that drops to under 2 minutes for the same batch.


Format Considerations: 9:16 vs 16:9 vs 1:1

Different distribution channels require different aspect ratios:

PlatformRatioRemotion Size
Instagram Stories / Reels9:161080 × 1920
Facebook / YouTube16:91920 × 1080
Instagram Feed / Facebook Post1:11080 × 1080
TikTok9:161080 × 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. This is straightforward since agent data is already in your JSON structure. Animate the agent card in during the final 2 seconds with a slide-up entrance, then hold for the last second before a 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:

  1. Export listings from MLS or CRM as JSON (most platforms support this natively)
  2. Normalize the export with a small transformation script to match your ListingProps schema
  3. Download photos from the MLS CDN URLs into a local photos/ directory to avoid network latency during rendering
  4. Run the batch script — output arrives in out/videos/ with filenames keyed to listing ID
  5. 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. 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 only bounded by what React can express. Pricing is fixed (the renderer is open-source; Lambda costs are infrastructure costs, not license fees). The template is your intellectual property.

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 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. Drop in your data and render immediately.


FAQ

Q: Can Remotion pull property photos directly from MLS CDN URLs, or do they need to be local files? A: 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.

Q: How do I handle listings where some photos are missing or fewer than expected? A: 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.

Q: What is the best codec for social media upload (Instagram, TikTok)? A: 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.

Q: Can I add background music to listing videos? A: Yes. Use Remotion’s Audio component 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.

Q: How does price animation handle price reductions mid-campaign? A: 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.

Q: Is Remotion Lambda worth it for a 50-listing batch, or is local rendering sufficient? A: 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.

Q: Can the template support property types beyond residential — commercial listings, rentals, vacation properties? A: 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 →