Add a poster and loading placeholder
Set the image shown before playback, then add a lightweight placeholder while it loads
A poster is the image shown before your video plays. There are several places to set one, so work down this list and stop at the first step that fits:
- Let the media provide it. Some media components create a poster from the source, so there is nothing extra to host or keep in sync.
- Set
posteron the player. With a packaged skin, that is all a static image needs. - Take control of the image when you need a framework image component, a
<picture>element, or a loading placeholder. The skin renders your image instead of its own. - Keep it working in an ejected skin. The player’s
posterstill reaches the Poster component your skin renders, or you can set the image’s source yourself.
Let the media provide a poster
Some media components choose a poster for you. For example, MuxVideo creates one from its playback ID:
Install the React façade and the Mux playback adapter:
pnpm add @videojs/react @videojs/mux-videoimport '@videojs/react/video/skin.css';
import { MuxVideo } from '@videojs/react/media/mux-video';
import { VideoPlayer, VideoSkin } from '@videojs/react/video';
export function MyPlayer() {
return (
<VideoPlayer>
<VideoSkin>
<MuxVideo
source={{
playbackId: 'BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM',
poster: { time: 2 },
}}
playsInline
/>
</VideoSkin>
</VideoPlayer>
);
}Install the HTML façade and the Mux playback adapter:
pnpm add @videojs/html @videojs/mux-video<video-player>
<video-skin>
<mux-video
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM.m3u8"
poster-time="2"
playsinline
></mux-video>
</video-skin>
</video-player>
<script type="module">
import '@videojs/html/video/player';
import '@videojs/html/video/skin';
import '@videojs/html/media/mux-video';
</script>Set a poster on the player
When the media has no poster of its own, set the player’s poster to an image URL:
import '@videojs/react/video/skin.css';
import { Video, VideoPlayer, VideoSkin } from '@videojs/react/video';
export function MyPlayer() {
return (
<VideoPlayer poster="/poster.jpg">
<VideoSkin>
<Video src="/video.mp4" playsInline />
</VideoSkin>
</VideoPlayer>
);
}<video-player poster="/poster.jpg">
<video-skin>
<video src="/video.mp4" playsinline></video>
</video-skin>
</video-player>If you use a ready-made skin, it includes Poster and shows the image automatically. If you build your own UI, add Poster yourself. It hides after the user plays or seeks.
When the player and the media component both choose a poster, the player’s poster wins. See Metadata for how the player combines those values.
Posters are decorative by default, with an empty alt. If the image communicates something that is not available elsewhere, add alt text when you customize the poster image.
Take control of the poster image
The packaged skins also accept an image of your own, for when a plain <img> is not enough:
Pass renderPoster to the skin. Video.js passes the poster URL as src alongside the rest of the image props; src stays undefined until a poster URL resolves. That fits a framework image component:
import '@videojs/react/video/skin.css';
import { Video, VideoPlayer, VideoSkin } from '@videojs/react/video';
import NextImage from 'next/image';
export function MyPlayer() {
return (
<VideoPlayer poster="/poster.jpg">
<VideoSkin
renderPoster={({ src, ...props }) =>
src ? <NextImage {...props} src={src} alt="" fill /> : null
}
>
<Video src="/video.mp4" playsInline />
</VideoSkin>
</VideoPlayer>
);
}Add your image to the skin’s poster slot. Leave off src and Video.js fills in the player’s poster. Provide a src, a srcset, or <picture> sources and Video.js leaves the image alone:
<video-player>
<video-skin>
<video src="/video.mp4" playsinline></video>
<picture slot="poster">
<source media="(min-width: 1200px)" srcset="/poster-1080.jpg" />
<img src="/poster-480.jpg" alt="" />
</picture>
</video-skin>
</video-player>Add a loading placeholder
A placeholder is a tiny preview that appears while the poster downloads, so a slow poster does not leave the player as empty space. Keep it small enough to include with the page, such as a short data URL. Add it to the customized image from the previous section.
Use your framework’s image component
If your image component supports placeholders, pass yours through renderPoster:
import '@videojs/react/video/skin.css';
import { Video, VideoPlayer, VideoSkin } from '@videojs/react/video';
import NextImage from 'next/image';
export function MyPlayer() {
return (
<VideoPlayer poster="/poster.jpg">
<VideoSkin
renderPoster={({ src, ...props }) =>
src ? (
<NextImage
{...props}
src={src}
alt=""
fill
placeholder="blur"
blurDataURL="data:image/webp;base64,…"
/>
) : null
}
>
<Video src="/video.mp4" playsInline />
</VideoSkin>
</VideoPlayer>
);
}Next.js generates blur data for static imports. Here, the poster URL arrives while the player runs, so provide blurDataURL yourself.
Use a plain image
For more control, add the placeholder as a background on the image that VideoSkin draws:
import '@videojs/react/video/skin.css';
import { Video, VideoPlayer, VideoSkin } from '@videojs/react/video';
export function MyPlayer() {
return (
<VideoPlayer poster="/poster.jpg">
<VideoSkin
renderPoster={(props) => (
<img
{...props}
style={{
...props.style,
background:
"url('data:image/webp;base64,…') var(--media-object-position, center) / contain no-repeat",
}}
/>
)}
>
<Video src="/video.mp4" playsInline />
</VideoSkin>
</VideoPlayer>
);
}Add an image to the poster slot
Add an image to the skin’s poster slot with the placeholder as its background. Leave off src: Video.js adds the poster URL, while the background appears immediately.
<video-player poster="/poster.jpg">
<video-skin>
<video src="/video.mp4" playsinline></video>
<img
slot="poster"
alt=""
style="background: url('data:image/webp;base64,…') var(--media-object-position, center) / contain no-repeat"
>
</video-skin>
</video-player>The examples use contain for both images. Use cover for both when the poster should fill the player. Keeping the same size and position prevents the image from jumping when the poster appears.
Set the poster in an ejected skin
Ejecting a skin changes nothing about the first two approaches: the player’s poster, or the poster the media provides, still reaches the Poster component your skin renders.
Poster renders an <img> and fills its src from the player, so srcSet, sizes, loading, and the rest of the image attributes are yours. Provide your own src or srcSet and Video.js leaves the image alone.
When your UI already includes Poster, apply a placeholder through its render prop:
<Poster
render={(props) => (
<img
{...props}
style={{
...props.style,
background:
"url('data:image/webp;base64,…') var(--media-object-position, center) / contain no-repeat",
}}
/>
)}
/><media-poster> fills the src of the image inside it. Give that image a src, a srcset, or <picture> sources instead and Video.js leaves it untouched — set the source at this level when the poster should bypass player state entirely.
For a placeholder, put the background on the image inside <media-poster>:
<media-poster>
<img
alt=""
style="background: url('data:image/webp;base64,…') var(--media-object-position, center) / contain no-repeat"
>
</media-poster>Troubleshooting
No poster appears
Check that the player has a poster or that the media component can create one from its source.
The final poster never appears
Pass the src from renderPoster to your image. If your image component requires a source, return null until src is available.
Leave off src, srcset, and any parent <picture> sources when you want Video.js to add the poster URL. If you provide one of those values, Video.js leaves it unchanged.
The placeholder and poster use different crops
Use the same fit for both images: pair background-size: contain with object-fit: contain, or use cover for both.
The placeholder adds too much page weight
Use fewer pixels and stronger compression. The placeholder only needs to suggest the poster’s color and shape until the full image appears.