How to Optimize Images On a Static Next.js Website
Next.js advertises Image Optimization out of the box. You will quickly realize that this optimization does not work with Static Site Generation (SSG). Additionally, the custom image loader implementation is never invoked because that functionality depends on the inbuilt server side image optimization service that is not available on a static generated site.
Even with a Server Side Rendered (SSR) site, the inbuilt optimization is not the best available. I suggest utilizing a 3rd party image optimization and CDN service such as imagekit.io, which offers a generous free tier, cloudflare, cloudinary or others. These services will optimize your images and serve them over an efficient CDN that will typically be much faster than whatever system you are hosting your website from.
There may be benefits in using this approach even on SSR sites. Although custom loaders can be utilized in this type of environment, there are at the time of writing a very limited set of variables exposed to the custom loader.
Just Wrap It
The solution is easy, simply wrap next/image
. In this way you can access the full range of options within Image which will let you make the most of the transformation options available with most image optimization services.
In this example I am using imagekit.io. This example should be easy to adapt to any of the large image optimization services.
typescript1import Image, { ImageProps } from "next/image"; 2import { buildSrc, Transformation } from "@imagekit/javascript"; 3 4export type StaticImageProps = ImageProps & { 5 cropMode?: Transformation["cropMode"]; 6 crop?: Transformation["crop"]; 7 format?: Transformation["format"]; 8}; 9 10export default async function StaticImage(props: StaticImageProps) { 11 const { cropMode, crop, format, ...imageProps } = props; 12 13 // Imagekit requires absolute urls to be prefixed with a slash. 14 const transformedSrc = props.src.toString().startsWith("http") 15 ? `/${props.src.toString()}` 16 : props.src.toString(); 17 18 const imageSrc = buildSrc({ 19 urlEndpoint: `${process.env.IMAGEKIT_ENDPOINT}`, 20 src: transformedSrc, 21 transformation: [ 22 { 23 width: props.width, 24 height: props.height, 25 crop: crop || "force", 26 cropMode: cropMode || "pad_resize", 27 format: format || "avif", 28 metadata: false, 29 }, 30 ], 31 transformationPosition: "path", 32 }); 33 34 const nextImageProps: ImageProps = { 35 ...imageProps, 36 src: imageSrc, 37 unoptimized: true, // Disable Next.js optimization 38 }; 39 40 // eslint-disable-next-line jsx-a11y/alt-text 41 return <Image {...nextImageProps} />; 42}
Implementation is a drop in replacement for Image:
tsx1{/*...*/} 2 <div className="mb-8"> 3 <StaticImage 4 src={post.featuredImage} 5 alt={`Featured image for ${post.title}`} 6 width={800} 7 height={400} 8 className="rounded-lg object-cover w-full h-[400px]" 9 /> 10 </div> 11{/*...*/}
This will now render to html as:
html1<div class="mb-8"> 2 <img 3 alt="Featured image for How to Optimize Images On a Static Next.js Website" 4 loading="lazy" 5 width="800" 6 height="400" 7 decoding="async" 8 data-nimg="1" 9 class="rounded-lg object-cover w-full h-[400px]" 10 style="color:transparent" 11 src="https://ik.imagekit.io/yedu6ugrg/tr:w-800,h-400,c-force,cm-pad_resize,f-avif,md-false/https:/picsum.photos/seed/how-to-optimize-images-on-static-nextjs-sites/800/400"> 12</div>
A service like picsum uses fastly for optimization, but typically a custom implementation will produce better results, especially if you make use of next generation image formats such as avif. For instance the image used on this blog, is 22.4 kB from picsum. However, with the customizations possible through imagekit, this has been reduced to 9.9 kB.