How we shipped a hero video — without bloating the repo
2026
jasperhaynes.com

How we shipped a hero video — without bloating the repo

Going from a static headshot to a calm, cinematic coast-aerial hero using free stock video — and why none of the 50 candidates we evaluated ever touched the repo.

50 Clips evaluated
0 Bytes committed
Pexels CDN Bandwidth source
videoprototypingcase-studytoolingpexels

Today we went from "should the homepage have a hero video?" to "which of the 50 candidates ships?" to "it's live." Along the way we hit every recurring gotcha of working with stock video on a static site: bot-protected catalogs, undocumented CDN URLs, 400 MB of binary bloat threatening the repo, and browsers quietly giving up when you pile too many <video> tags on one page.

This is the case study.

The setup

The old hero was a faded circular headshot. It worked, but felt static. We wanted motion — calm, cinematic, natural — without turning the page into a bandwidth hog.

Constraint: no local video files committed to git. The repo stays lean, Vercel bandwidth stays cheap, Core Web Vitals stay green.

Where to find free stock video

Three sources that consistently deliver usable footage:

Pexels wins for any workflow that needs scale.

How we actually loaded them

Grab a free API key from pexels.com/api/new — it arrives in seconds — and hit this endpoint:

GET https://api.pexels.com/videos/search
    ?query=coast+aerial
    &orientation=landscape
    &size=large
    &per_page=30
Authorization: <your-key>

The response gives you a list of videos, each with a video_files array of MP4 renditions at different resolutions. A tiny Node script picks the highest rendition per video and writes a manifest:

const res = await fetch(
  `https://api.pexels.com/videos/search?query=${encodeURIComponent('coast aerial')}&per_page=30&size=large`,
  { headers: { Authorization: process.env.PEXELS_API_KEY } }
);
const { videos } = await res.json();
const picks = videos.map(v => {
  const best = v.video_files
    .filter(f => f.file_type === 'video/mp4')
    .sort((a, b) => b.height - a.height)[0];
  return { id: v.id, url: best.link, poster: v.image, author: v.user.name };
});

50 clips across 6 categories (mountain drone, ocean waves, forest mist, aurora, sunset clouds, coast aerial) — written to a single JSON the prototype picker page consumed.

The part we learned the hard way

First instinct: download the 50 clips locally, commit them to the repo, embed them with <video src="/videos/x.mp4">. Cost: 406 MB. Vercel's deploy limit balks, git history bloats forever, and every visitor downloads a chunk of that for the hero alone.

The fix is trivial in retrospect: the MP4 URLs the Pexels API returns are permanent, publicly accessible CDN URLs at videos.pexels.com. Point <source src> straight at them. Zero repo bytes, Pexels pays the bandwidth.

<video autoplay muted loop playsinline
       poster="https://images.pexels.com/videos/36519600/pexels-photo-36519600.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=1080&w=1920">
  <source
    src="https://videos.pexels.com/video-files/36519600/15485595_3840_2160_60fps.mp4"
    type="video/mp4" />
</video>

That's the actual hero, live on the homepage right now.

See for yourself

Three of the 50 candidates we evaluated — streamed directly from Pexels, no bytes shipped from this page:

1. Coast aerial — the winner

Mallorca cliffs from above. Calm but moving. By Mike Art on Pexels.

2. Aurora borealis — the dramatic alternative

Great on a landing page; too theatrical for the calm hero we wanted.

3. Coding shot — different vibe entirely

Briefly auditioned for an "Experiment Pipeline" tile. Nice on its own; fought for attention next to the hero.

Takeaways

Total: ~3 hours of iteration, 50 clips evaluated, zero bytes of video committed. The clip that shipped has streamed ~3 MB to each visitor so far — the same cost as a single decent-quality hero image.