Skip to content
A Astro Rocket

Video Slides in Project Galleries — Zero Bytes Until Play

Project carousels now accept self-hosted video slides next to images. Here's how to use them, how they work, and why they don't cost a single Lighthouse point.

H

Hans Martens

4 min read

Project galleries accept video slides alongside images — in both places a gallery appears: the hero carousel that comes from frontmatter, and the <ProjectGallery> component you drop into the MDX body. A short video often shows software better than stills — a checkout flow, an animation, anything that moves — and the feature is built so a video slide costs zero bytes until the visitor presses play: a project page with video ships exactly as fast as one without. This post shows how to use video slides and how the performance side works.

Using it in frontmatter

A gallery slide was always src + alt. It can now also be video + poster + alt:

---
title: "My Product"
description: "..."
gallery:
  - video: "/videos/demo.mp4"
    poster: "../../assets/projects/demo-poster.jpg"
    alt: "30-second product demo"
  - src: "../../assets/projects/shot-1.jpg"
    alt: "Dashboard view"
  - src: "../../assets/projects/shot-2.jpg"
    alt: "Settings page"
---

Two things to notice:

  • The video is a root-relative path, not an import. Video files live in public/ (for example public/videos/), because Astro’s asset pipeline is for images — video gets served as-is.
  • The poster is required. The poster is what makes the performance model work — see how it stays Lighthouse-neutral below.

The first slide is the featured one, so putting the video first gives you a product-page layout: demo up front, stills behind it.

Using it in the post body

The same shape works in <ProjectGallery>, the in-body carousel with the click-to-zoom lightbox — including an optional caption:

import ProjectGallery from '@/components/projects/ProjectGallery.astro';
import poster from '@/assets/projects/demo-poster.jpg';
import shot1 from '@/assets/projects/shot-1.jpg';

<ProjectGallery
  images={[
    { video: '/videos/demo.mp4', poster, alt: 'Product demo', caption: 'The checkout flow, start to finish.' },
    { src: shot1, alt: 'Dashboard', caption: 'The main dashboard.' },
  ]}
/>

One behavioural difference from image slides: clicking an image opens the lightbox, but clicking a video plays it inline — the native controls own that click. If you navigate to a video inside the lightbox (arrow keys or the chevrons), it plays there too, full-size.

How it stays Lighthouse-neutral

Four decisions keep video slides performance-neutral:

  1. preload="none" on every video element. The browser downloads no video data — not even metadata — until the visitor presses play. A project page with a video slide ships exactly as many media bytes as one without.
  2. The poster is the slide. What you see in the carousel is the poster image, and it goes through the same astro:assets pipeline as a regular slide — optimised, resized, served as WebP. If the video is your first slide, the poster is your LCP candidate, and it behaves like any other optimised hero image.
  3. No autoplay. Autoplaying video means downloading video on page load, which is the whole thing being avoided. The visitor decides.
  4. Swiping away pauses. The carousel script pauses any video that’s no longer the active slide, so audio never keeps playing off-screen.

Galleries intentionally do not support YouTube or Vimeo embeds: a third-party iframe adds hundreds of kilobytes of player script, cookies, and consent obligations, while self-hosted files keep the page under your control. For a YouTube video in an article, use the click-to-play YouTube embed component instead — it loads nothing from YouTube until the reader presses play.

Keeping the file small

A few encoding settings keep demo files small:

  • Keep it short — 15 to 45 seconds is enough to show a flow.
  • 1280×720 matches the carousel’s aspect ratio (aspect-video) exactly.
  • H.264 MP4 plays everywhere. With ffmpeg:
ffmpeg -i recording.mov -vf "scale=1280:720" -c:v libx264 -crf 28 \
  -movflags +faststart -an public/videos/demo.mp4

-crf 28 is plenty for UI recordings, -an drops the audio track if your demo doesn’t need one, and +faststart moves the metadata to the front of the file so playback starts immediately. A 30-second UI recording lands around 1–2 MB — and it isn’t downloaded until the visitor presses play.

Under the hood

For reference: the slide union is validated in src/content.config.ts (a slide is either src+alt or video+poster+alt — anything else fails the build), the shared GallerySlide type lives in src/lib/gallery.ts, and the rendering happens in ProjectCarousel.astro (hero) and ProjectGallery.astro (body + lightbox). No new dependencies, no client framework — the same native scroll-snap carousel, with a <video> element where a slide asks for one.

src/content/projects/en/ecommerce-store.mdx carries a ready-to-uncomment video slide as a starting point. Drop a file in public/videos/, point a slide at it, give it a poster — done.

Share:

Related Posts

YouTube in Your Posts — the Click-to-Play Embed

Embed a YouTube video in any post or page with one component. The page ships only a thumbnail; the player loads from youtube-nocookie.com when the reader presses play. Lighthouse stays at 100.

H Hans Martens
3 min read
#astro-rocket #features #components #performance

The Stack Marquee — A Pure-CSS Infinite Tech Strip

Astro Rocket's homepage has a new Stack Marquee: two rows of tech-stack cards scrolling opposite ways with zero JavaScript. Here's how the loop works and why it's hidden on phones.

H Hans Martens
5 min read
#astro-rocket #animation #css #components #ux #performance

Comments on Blog Posts — Giscus, Cusdis, or Artalk, Lazy-Loaded

Astro Rocket's blog comments are pluggable: pick Giscus, Cusdis, or the fully self-hosted Artalk. All three lazy-load on scroll — skip them and you pay nothing.

H Hans Martens
7 min read
#astro-rocket #features #blog #comments #giscus #cusdis #artalk

Follow along

Stay in the loop — new articles, thoughts, and updates.