MEDIA

WHY HOTROD SHIPS WITH VERCEL BLOB AND MUX BAKED IN

Content media doesn't belong in your repo. We picked two services that get out of the way — Blob for images, Mux for video — and wired both into the starter.

Jono 13 May 2026 5 min read

The most common thing that goes wrong on a small content site is media. The first hero image is committed to public/. So is the second. Six months later the repo is 800 MB, the dev server takes two minutes to start, and someone has accidentally pushed a 40 MB MP4 through a pull request.

Hotrod takes a hard position on this so it can’t happen: public/ is for chrome — the favicon, the logo SVG, anything that ships with the site itself. Everything else is content, and content lives in a service designed for content.

The two services we picked are Vercel Blob for images and Mux for video. Here’s why each one, and how Hotrod wires them up.

Vercel Blob for images

Blob is Vercel’s object store. The product surface is small on purpose:

  • A single put() call uploads a file and returns a permanent URL
  • URLs are cacheable, served from Vercel’s edge
  • Pricing is pay-per-use storage plus bandwidth — there is no per-seat fee
  • It supports both public assets and signed-URL private assets

For a content site, that’s exactly the right shape. You don’t want a separate dashboard to log into, a separate billing relationship, or a separate auth provider. Blob lives in the same Vercel project as the site, uses the same env vars, and shows up in the same billing line.

Hotrod gives you an upload helper and an image block that takes a Blob URL plus alt text. The image block schema is strict — the URL is required and the alt text is required. If an agent tries to add an image block without alt text, the build fails. That’s the right default for a real-world site: accessibility isn’t optional.

We use probe-image-size at build time to pre-fetch the dimensions of every Blob image. That number gets baked into the rendered <img> so the layout is stable before the image loads. No CLS, no layout shift, no extra round-trips at runtime.

Why not just public/ plus the Astro image pipeline?

Because the moment a non-technical owner or an agent uploads a 6 MB phone photo, you want it processed somewhere else. Putting it in public/ means:

  • It gets committed to git. Forever. Even after you “delete” it.
  • It rebuilds the entire site every time you add or replace one.
  • It bloats the deployment artifact.
  • Two people working in parallel can stomp on the same filename.

Blob removes every one of those problems. The image lives at a stable URL the moment it’s uploaded. The site doesn’t know or care when the image changed. The repo stays small.

Mux for video

Video is its own category and we didn’t want to fake it. Mux does four things you almost certainly want and almost certainly do not want to build yourself:

Encoding. You upload a single source file. Mux produces an adaptive bitrate ladder, so the player can pick the right resolution for the viewer’s connection and screen.

Streaming. HLS, served from Mux’s CDN, with bandwidth that scales with viewers and not with your wallet’s panic threshold.

Thumbnails and posters. A still image at any timestamp, on demand, generated by Mux. No need to upload a separate poster image.

Analytics. Mux Data is the industry standard for “did people actually finish watching this.” It’s optional, but the moment you care about video conversion, you’ll want it.

The alternative is hosting MP4s on your CDN and serving them via <video>. That works for a five-second loop. It doesn’t work for a one-minute product video being watched on a 3G connection by someone in another country. Mux makes the second case as easy as the first.

Hotrod ships with a video block schema that takes a Mux playback ID, a title, and a poster timestamp. Same rules as the image block: required fields are required. If a video block has no playback ID, the build fails.

What the two have in common

We picked Blob and Mux for the same reason, and that reason is: small surface area. Two API calls per service. No bespoke client SDK that’s going to break in a major version. No vendor-specific React components you have to wrap and re-wrap. No “platform” you have to learn. Both services do one job and stop.

That matches the rest of Hotrod’s design philosophy. The starter has no shadcn, no Radix, no headless component library. Every dependency is one you can audit in an afternoon. The media stack is the same — Blob is a few hundred lines of API surface, Mux is a JSON object with a playback ID. An agent can use either without a wrapper.

How to think about media in Hotrod

The rule of thumb that’s easy to teach an agent and a human at the same time:

  • In the repo: code, content (MDX), references to media (URLs, playback IDs)
  • Not in the repo: the binary files those URLs point to

If you find yourself wanting to commit a .jpg or a .mp4, you almost certainly want a Blob URL or a Mux playback ID instead. The rule has held for every site we’ve built on this pattern.

What’s wired up today

The image block is in src/blocks/image-feature/. It accepts a Blob URL, alt text, and optional badge / stats / heading copy. The component handles responsive sizing, layout-stable rendering, and the v0 brand frame. The Mux video block is on the short list for the next release.

Once those two ship, you’ve got a starter that handles every common content-site media case without you owning the infrastructure for any of it.

Jono

Founder, Roboto Studio

Builds open-source tools for agent-driven websites.