You might have come across some of the videos on our website and thought: “Gee wowsers, they’re still hitting near perfect performance scores in Lighthouse”. Well, let me tell you: it was a struggle.
However, the good news is, today we want to share what took us far too long to perfect, and give you the opportunity to use Mux yourself, without tanking performance with the Mux Player.
It's also worth mentioning that the Mux player has more compatibility and better video experience (see Feedback from Dylan Jhaveri for more context) but frankly, I'm fairly certain folks aren't browsing our website on IE8, nor do we want to build for IE8, so this is the compromise we are willing to make (sorry dinosaurs 🦕).
The code
typescript1'use client';2// Swap these out for another icon set if you want3import { Loader2, PlayIcon } from 'lucide-react';4// To dynamically load the video player5import dynamic from 'next/dynamic';6import { FC, useState } from 'react';7import { preload } from 'react-dom';8// This is from Sanity type generation9import { MuxVideo, Video as VideoType } from '~/sanity.types';1011const getVideoToPlayUrl = (muxId: string) => {12 return `https://stream.mux.com/${muxId}.m3u8`;13};1415const getPosterUrl = (muxId: string) => {16 return `https://image.mux.com/${muxId}/thumbnail.webp?fit_mode=smartcrop&time=0`;17};1819const getMuxVideoProps = (muxId: string) => {20 return {21 poster: getPosterUrl(muxId),22 vidUrl: getVideoToPlayUrl(muxId),23 };24};2526type VideoPlay = {27 poster: string;28 vidUrl: string;29 loop?: boolean;30 muted?: boolean;31 control?: boolean;32};3334const VideoAutoPlay = ({ control, loop, muted, poster, vidUrl }: VideoPlay) => {35 const HlsVideo = dynamic(36 () => import('./hls-video').then((mod) => mod.HlsVideo),37 {38 loading: () => {39 return (40 <div className="relative size-full">41 {/* eslint-disable-next-line @next/next/no-img-element */}42 <img43 src={poster}44 // Pick your most commonly used video size to avoid CLS45 width={1920}46 height={1080}47 alt="a"48 className="mx-auto size-full "49 />50 <div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 transform ">51 <Loader2 className="animate-spin" size={112} strokeWidth={0.5} />52 </div>53 </div>54 );55 },56 },57 );58 return (59 <HlsVideo60 autoPlay61 poster={poster}62 vidUrl={vidUrl}63 loop={control ? loop : undefined}64 muted={control ? muted : undefined}65 controls={control}66 width={1920}67 height={1080}68 className="mx-auto size-full"69 />70 );71};7273const VideoWithOutAutoPlay = (74 props: VideoPlay & { loading?: 'lazy' | 'eager' },75) => {76 const { poster, loading } = props;77 const [play, setPlay] = useState(false);7879 return (80 <div className=" size-full">81 {play ? (82 <div className="relative size-full ">83 <div className="my-4">84 <VideoAutoPlay {...props} />85 </div>86 </div>87 ) : (88 <div className="relative size-full">89 {/* eslint-disable-next-line @next/next/no-img-element */}90 <img91 src={poster}92 width={1920}93 height={1080}94 loading={loading === 'lazy' ? 'lazy' : 'eager'}95 alt="a"96 className="mx-auto size-full "97 />98 <div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 transform ">99 <button100 className="grid place-items-center rounded-full bg-black p-8 hover:bg-hotpink-400"101 onClick={() => setPlay(true)}102 >103 <PlayIcon className="h-8 w-8 fill-white text-white" />104 <span className="sr-only">Play</span>105 </button>106 </div>107 </div>108 )}109 </div>110 );111};112113export type VideoProps = VideoType & {114 videoEmbed: NonNullable<VideoType['videoEmbed']> & {115 video: MuxVideo & {116 asset: {117 playbackId: string;118 };119 };120 };121};122123export const Video: FC<VideoProps & { loading?: 'lazy' | 'eager' }> = ({124 videoEmbed,125 loading = 'eager',126}) => {127 const { video, autoPlay, control, loop, muted } = videoEmbed ?? {};128129 const { poster, vidUrl } = getMuxVideoProps(video?.asset?.playbackId);130131 preload(vidUrl, {132 as: 'video',133 fetchPriority: 'high',134 });135 preload(poster, {136 as: 'image',137 fetchPriority: 'high',138 });139140 const videoProps = { poster, loading, vidUrl, loop, muted, control };141142 return (143 <div className="mx-auto flex max-w-7xl">144 {control && autoPlay ? (145 <VideoAutoPlay {...videoProps} />146 ) : (147 <VideoWithOutAutoPlay {...videoProps} loading={loading} />148 )}149 </div>150 );151};152
Performance improvements
Well remember how there was some bloke called Charles Goodhart said: "When a measure becomes a target, it ceases to be a good measure". Well we basically ignored everything that person said, and decided to optimise to see if it was possible to hit 100/100 on a website that still has a lot of images, fancy libraries and analytics.
If you're looking to try and get the perfect score on your website, get in touch with us, because we sure as hell know a lot about this stuff, after all the hassle we've been through to try and achieve it.
A quick explainer what this is good for

Feedback from Dylan Jhaveri
We posted this on Vercel Community, and got some great feedback and better context. So, before you go ahead and straight up copy the code without knowing the tradeoffs make sure you read this bit.
"Love the work you’ve done in your example to optimize for lighthouse score. There is a fundamental tradeoff here that I think folks should think about:
web vitals (lighthouse score) vs. video performance
The biggest change that I can see, if I’m following correctly is waiting to load <hls-video>
until after the user clicks play.
That’s going to make your lighthouse score great, (which is great!) – but the tradeoff is that when the user clicks play it’s going to take more time for playback to start and the user to see the first frame of the video.
For your use case on roboto.studio I think that probably makes perfect sense. For other use cases where you want the video to load fast you may want to lazy load the player 2 (https://docs.mux.com/guides/player-lazy-loading), which tries to balance the tradeoffs:
- Doesn’t block the initial page load (despite this, your lighthouse scores will be impacted negatively)
- Lazy loads the player without requiring the user to click play so that when the user does click play the video starts up immediately"
Cheers Dylan, and thanks for the free content ✌️ go read his blog posts here