When we are deploying the website, our first concern to load the web page as fast as possible, then we also think what could be the page performance across different device and networks. Core Web Vital would a best way to measure the application page speed report. Light house or PageSpeed Insight could be a great tool to measure the application performance report.
To make this guide easier to discover and more relevant for readers, it now naturally covers Core Web Vital, React Router, React, and Remix.
Images are responsible for the Largest Contentful Paint on 85% of desktop pages and 76% of mobile pages, according to the 2025 Web Almanac. That makes image optimization the single most impactful thing you can do for your Core Web Vitals. But images do not just affect loading speed. They can cause layout shifts, and on image-heavy pages, even slow down interactivity.
A basic image handling (native javaScript)
This is the simplest approach to optimized image loading in React Router or Remix. It's just good ol' HTMLimg 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.
export default function Image() {
return (
<div
className="max-w-[500px] aspect-[1/1]"
style={{
background: `top / contain no-repeat url(${placeholderImgSrc})`,
}}
>
{" "}
<img
className="w-full"
alt="background image"
src={fallbackImgSrc}
sizes="(min-width: 400px) 800px, 100vw"
srcSet={`${smallImgSrc} 400w, ${mediumImgSrc} 640w, ${largeImgSrc} 800w`}
/>{" "}
</div>
);
}
The most acceptable and well performed image format would be .webp and .avif.
Generate webp format image
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 ./bg.jpg -vf scale=200:-1 -q:v 80 ./bg.webp
The placeholder image is created by scaling down the original image and applying a box blur effect:
$ ffmpeg -i ./bg.jpg -vf "scale=iw/10:-1,boxblur=5:1" ./bg_placeholder.webp
In React Router or Remix world, 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" }];
}
Optimized images using vite-plugin-image-optimizer and image-tools
If you have a bunch of local images and you want to compress or optimized those images during build time, then vite-plugin-image-optimizer could be a good options to try out. vite-image-optimizer is nice for passing over all images and optimizing them at build.
Some downsides is that it doesn't convert from one format to another, and it applies the same optimization to all images. If you're wanting to use srcset and sizes to ship different images to different devices, you'll need to use a different tool.
You need to install both the following packages -
npm install vite-plugin-image-optimizer --save-dev npm install vite-imagetools --save-dev
And the following is the vite.config.ts file structure.
import { reactRouter } from "@react-router/dev/vite";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
import rehypePrettyCode from "rehype-pretty-code";
import { ViteImageOptimizer } from "vite-plugin-image-optimizer";
import { imagetools } from "vite-imagetools";
export default defineConfig({
resolve: { tsconfigPaths: true },
plugins: [
ViteImageOptimizer({
logStats: true,
test: /bg\.*/,
jpg: { quality: 80 },
cache: true,
cacheLocation: "node_modules/.cache",
}),
imagetools(),
reactRouter(),
],
});
You can measure the performace now, I am pettry sure you would see the before and after difference.