This is the simplest approach to optimized image loading in Remix. In fact, it barely even uses anything from Remix. It's just good ol' HTML img component using srcset and sizes attributes, along with a blurred placeholder image set using the CSS background property. The only Remix-y thing we do is exporting a links function to preload the placeholder image.
Here's is the result:

And here's what the full code would look like:
import placeholderImgSrc from "./sleepy_girl_placeholder.webp";
import fallbackImgSrc from "./sleepy_girl.webp";
import smallImgSrc from "./sleepy_girl_small.webp";
import mediumImgSrc from "./sleepy_girl_medium.webp";
import largeImgSrc from "./sleepy_girl_large.webp";
export function links() {
return [
{
rel: "preload",
href: placeholderImgSrc,
as: "image",
},
];
}
export default function Image() {
return (
<div
className="max-w-[800px] aspect-[3/4]"
style={{
background: `top / contain no-repeat url(${placeholderImgSrc})`,
}}
>
<img
className="w-full"
alt="A puppy sleeping on it's back with it's mouth slightly open"
src={fallbackImgSrc}
sizes="(min-width: 400px) 800px, 100vw"
srcSet={`${smallImgSrc} 400w, ${mediumImgSrc} 640w, ${largeImgSrc} 800w`}
/>
</div>
);
}And here is the a video of the image loading:
When delivering images on the web, it's important to provide different sizes and formats to ensure the best performance across various devices and network conditions. In this example, we use the WebP format for the images, which provides better compression and quality than JPEG and PNG.
You also need multiple images of different sizes to serve the most suitable image based on the viewport size and device pixel ratio.
Providing on-demand optimized images of various formats and sizes is what an Image CDN does for you. Since this is just a basic example, we're providing these images manually. This is what we've got:
sleepy_girl.jpg: The original image in JPEG format.sleepy_girl_placeholder.webp: A small, blurred version of the image that serves as a placeholder while the actual image loads.sleepy_girl.webp: The fallback image that will be loaded if the browser doesn't support srcset.sleepy_girl_small.webp, sleepy_girl_medium.webp, sleepy_girl_large.webp: Different sizes of the image to be loaded based on the viewport size and device pixel ratio.We use ffmpeg to generate these images from an original photo. The command below scales the image to a specific width (in pixels) and sets the image quality (-q:v) to 80 (out of 100) to reduce the file size:
$ ffmpeg -i ./sleepy_girl.jpg -vf scale=200:-1 -q:v 80 ./sleepy_girl.webpThe placeholder image is created by scaling down the original image and applying a box blur effect:
$ ffmpeg -i ./sleepy_girl.jpg -vf "scale=iw/10:-1,boxblur=5:1" ./sleepy_girl_placeholder.webpThe resulting file structure looks like this:
├── sleepy_girl.jpg
├── sleepy_girl.webp
├── sleepy_girl_small.webp
├── sleepy_girl_medium.webp
├── sleepy_girl_large.webp
└── sleepy_girl_placeholder.webp
We render the placeholder image using the CSS background property. This approach provides a smooth user experience as the placeholder is instantly replaced by the actual image once it's loaded. To avoid cumulative layout shift (CLS), it's crucial to provide the proper width and an aspect ratio:
<div
className="max-w-[800px] aspect-[3/4]"
style={{
background: `top / contain no-repeat url(${placeholderImgSrc})`,
}}
>
<img />
</div>To ensure the placeholder image loads as quickly as possible, we preload it using the links export for the route:
export function links() {
return [
{
rel: "preload",
href: placeholderImgSrc,
as: "image",
},
];
}This guaruntees that this image will start loading first
srcset and sizes AttributesThe srcset attribute allows the browser to choose the most suitable image source based on the viewport size and device pixel ratio. The sizes attribute informs the browser about the displayed size of the image. While it's possible to manually set these values, it's recommended to have them programmatically created for a more efficient loading strategy:
<img
className="w-full"
alt="A puppy sleeping on it's back with it's mouth slightly open"
src={fallbackImgSrc}
sizes="(min-width: 400px) 800px, 100vw"
srcSet={`${smallImgSrc} 400w, ${mediumImgSrc} 640w, ${largeImgSrc} 800w`}
/>This is what causes the browser to load the most suitable image based on the viewport size and device pixel ratio, as seen in the demo video above.
This guide provides a basic understanding of how to handle images in Remix using srcset and sizes attributes, along with a blurred placeholder image. For a deeper understanding of these concepts, consider exploring the MDN Web Docs for the img attribute.