SD_04_T88_4148B_E2_X_EC_1.webp

Next.js Image component and Strapi content

Images added while using the WYSIWYG editor are returned as part of the content response which is a string. So we end up with something like this:

[`  ...
  <p>Bleh bleh.
  <img src="YOUR IMAGE SRC" alt="YOUR IMAGE ALT"></p>
  <h2>Blah blah:</h2>
    ...`
]

We could just render this as it is, but we would not get the benefits offered by Next.js's Image component.

The Next.js Image component, next/image, is an extension of the HTML element, evolved for the modern web. It includes a variety of built-in performance optimizations to help you achieve good Core Web Vitals. These scores are an important measurement of user experience on your website, and are factored into Google's search rankings.

The features are:

  • Always serve correctly sized image for each device, using modern image formats
  • Prevent Cumulative Layout Shift automatically.
  • Images are only loaded when they enter the viewport, with optional blur-up placeholders
  • On-demand image resizing, even for images stored on remote servers

The plan

  • Use the .split() method to break the string down and store it in to an array.
  • When rendering, we will loop the array
  • If an item in the array is an img, use Next.js image component
  • If an item in the array is not an img, out put as normal

Break down the string

The split() method divides a String into an ordered list of substrings, puts these substrings into an array, and returns the array. The division is done by searching for a pattern; where the pattern is provided as the first parameter in the method's call.

Source

We will use a regex as the delimeter to split the string at every img tag. We will also want to preserve everything inside the image tag. For this I will create a regex object to store some handy regexes:

export const regexes = {
  imgTag: /(<img [^>]*src="[^"]*"[^>]*>)/gm,
  imgSrc: /src="(.*?)"/,
  imgAlt: /alt="(.*?)"/,
};

With that out the way we can now use these regexes to begin breaking down the string litral returned from Strapi:

const content = YOUR.PROPS.split(regexes.imgTag);

The above will give us a new array similar to below:

[`  ...
  '<p>Bleh bleh,'
  '<img src="YOUR IMAGE SRC" alt="YOUR IMAGE ALT">',
  '</p><h2>Blah blah:</h2>'
    ...`
]

Some helper functions

We will use the regexes.imgSrc and regexes.imgAlt from before to help us here:

function getImgSource(image) {
  return image.match(regexes.imgSrc)[1];
}

function getImgAlt(image) {
  return image.match(regexes.imgAlt)[1];
}

The .match() method retrieves the result of matching a string against a regular expression.

Source

Use the new array

The .map() method creates a new array populated with the results of calling a provided function on every element in the calling array.

Source

With the new array and our helper functions ready we can now use the .map() method to render our content

{
  content.map((a) => {
    {
      if (a.match(regexes.imgTag)) {
        return (
          <div key={a}>
            <Image
              src={getImgSource(a)}
              alt={getImgAlt(a)}
              size={25}
              layout="fill"
              objectFit="contain"
            />
          </div>
        );
      } else {
        return <div key={a} dangerouslySetInnerHTML={{ __html: a }} />;
      }
    }
  });
}

Styles

Next.js image component has many configurable options. In my case I don't actually know the diemensions of my images that I will be using so I will use the fill property. (More info here)

size={25} layout="fill" objectFit="contain" 

This will get the image at 25% of its original size, place the image so it fills the entire container but never break out of it. Using fill sets the image to position: absolute, this means I will also need to wrap ths image component in an element that has a position set to relative to not break the pages layout

<div key={a} style={{ position: "relative" }}>
  <Image
    src={getImgSource(a)}
    alt={getImgAlt(a)}
    size={25}
    layout="fill"
    objectFit="contain"
  />
</div>;