Chris Meagher

Using the Next image component with Kentico Kontent assets

The Next image (next/image) component has built-in "Automatic Image Optimization" features, which gives you lots of cool stuff like lazy-loading, resizing, and optimisations such as transcoding to modern formats like webp (when supported). The component was added to Next in v10 and worked really well in development and if you were hosting on Vercel, or using one of the supported third-party image providers in your project. If you were using other image providers or hosting platforms such as Netlify however then you would run into problems where the component would not work at all, or was only partially supported or implemented.

In Next v10.0.5 a loader prop was added to next/image that allows you to use a custom image provider for image optimisation and transformation, and if you are using Kentico Kontent then you already have this capability available to you as it is built into their Delivery API. The Javascript SDK also provides an ImageUrlBuilder class to make generating the requests to the API super easy.

To use Kontent's Delivery API as a loader prop you can create and use a function like this:

import { ImageUrlBuilder, ImageCompressionEnum } from '@kentico/kontent-delivery'

const kontentImageLoader = ({ src, width, quality }) => {
  const builder = new ImageUrlBuilder(src)
    .withWidth(width)
    .withQuality(quality || 75)
    .withCompression(ImageCompressionEnum.Lossless)
    .withAutomaticFormat()

  return builder.getUrl()
}

You may also want to consider only applying the loader if the src is to a URL on a Kontent asset domain, and fallback to the default or a different loader if not. The following is an example component that will only apply the kontentImageLoader based on such conditions:

import { ImageUrlBuilder, ImageCompressionEnum } from '@kentico/kontent-delivery'
import NextImage from 'next/image'

const kontentAssetHostnames = [
  'assets-eu-01.kc-usercontent.com',
  'preview-assets-eu-01.kc-usercontent.com'
]

const srcIsKontentAsset = (src) => {
  try {
    const srcUrl = new URL(src)
    return kontentAssetHostnames.includes(srcUrl.hostname)
  }
  catch {
    return false
  }
}

const kontentImageLoader = ({ src, width, quality }) => {
  const imageUrlBuilder = new ImageUrlBuilder(src)
    .withWidth(width)
    .withQuality(quality || 75)
    .withCompression(ImageCompressionEnum.Lossless)
    .withAutomaticFormat()

  return imageUrlBuilder.getUrl()
}

const Image = ({ src, width, height, alt, quality }) => {
  const loader = srcIsKontentAsset(src)
    ? kontentImageLoader
    : undefined

  return <NextImage
    src={src}
    width={width}
    height={height}
    alt={alt}
    quality={quality}
    loader={loader}
  />
}

export default Image

We can now use the above Image component to get optimisation of images through Kontent's Delivery API, but still take advantage of the rendering smarts of the next/image component.