Static Site Generation with Next.js and TypeScript (Part III) - Optimizing Image Loading with Plaiceholder and BlurHash

Responses (2)

project repository
Roman Shestakov2 months ago

404, not working

Clap
1|2|

Disclaimer - Please read the second part of this blog post here before proceeding. It explains the different data-fetching techniques that Next.js supports, and it guides you through the process of statically rendering a Next.js application page that fetches data at build time via the getStaticProps function. If you just want to jump straight into this tutorial, then clone the project repository and install the dependencies.

Slow page loading times hurt the user experience. Anytime a user waits longer than a few seconds for a page's content to appear, they usually lose their patience and close out the page in frustration. A significant contributor to slow page loading times is image sizes. The larger an image is, the longer it takes the browser to download and render it to the page.

One way to improve the perceived load time of images (and by extension, page) is to initially show a placeholder image to the user. This image should occupy the same space as the intended image to prevent cumulative layout shifting. Additionally, compared to the intended image, this image should be much smaller in size (at most, several KBs) so that it loads instantaneously (within the window of the page's first contentful paint). The placeholder image can be as simple as a single, solid color (e.g., Google Images or Medium) or as advanced as a blurred representation of the image (e.g., Unsplash).

For Next.js application's, the <Image /> component from next/image augments the <img /> HTML element by automatically handling image optimizations, such as...

  • Serving the correct image for a specific viewport size.

  • Preserving image positions in a layout.

  • Loading an image on-demand only when it enters the viewport.

  • Resizing, compressing and reformatting images.

These optimizations make the page's content immediately available to users to interact with. As a result, they help to improve not only a page's Core Web Vitals, but also the page's SEO ranking and user experience.

Next.js <Image /> components support blurred placeholder images. To tell Next.js to load an image with a blurred placeholder image, you can either...

  • Use the default blurDataUrl prop. This prop only requires you to set the placeholder prop to blur on the <Image /> component, but the image must be statically imported (cannot be images hosted on an external service). With this approach, Next.js automatically pre-generates a blurred representation of the image without us having to specify a Data URL for a blurred placeholder image.

  • Pass a custom Data URL of a blurred placeholder image to the <Image /> component's blurDataUrl prop.

If the Next.js application happens to load images from an external service like Unsplash, then for each image, would we need to manually create a blurred placeholder image, convert the blurred placeholder image into a Data URL and pass the Data URL to the blurDataUrl prop?

With the Plaiceholder library, we can transform any image into a blurred placeholder image, regardless of where the image is hosted. The blurred placeholder image is a very low resolution version of the original image, and it can be embedded within the page via several methods: CSS, SVG, Base64 and Blurhash.

Blurhash is an algorithm that encodes a blurred representation of an image as a small, compact string. Decoding this string yields the pixels that make up the blurred placeholder image. Using these pixels, you can render the blurred placeholder image within a <canvas /> element. Blurhash allows you to render a blurred placeholder image without ever having to send a network request to download it.

Below, I'm going to show you how to improve image loading in a Next.js application with the Plaiceholder library and Blurhash.

Installation and Setup#

To get started, clone the project repository and install the dependencies.

If you're coming from the second part of this tutorial series, then you can continue on from where the second part left off.

Within the project directory, let's install several dependencies:

  • plaiceholder - A library for generating blurred placeholder images.

  • @plaiceholder/next - A plugin for overriding Next.js's Webpack configuration to accommodate the use of Plaiceholder in a Next.js application.

  • react-blurhash - A React library for rendering a blurred placeholder image within a <canvas /> element from a Blurhash encoded string.

Then, wrap the Next.js configuration with withPlaiceholder to extend the Next.js Webpack configuration so that Webpack excludes the sharp image processing library from the output bundle.

(next.config.js)

Specifying images.domains in the Next.js configuration restricts the domains that remote images can come from. This protects the application from malicious domains while still allowing Next.js to apply image optimizations to remote images.

Transforming Unsplash Images to Blurred Placeholder Images#

Currently, the images on the home page are high resolution and take a lot of time to be fully downloaded. In fact, without any image processing, some of the raw Unsplash images are as large as 20 MBs in size.

However, by replacing these images with more optimized images, we can significantly improve the performance of the Next.js application. Using the <Image /> component from next/image and the Plaiceholder library, the home page will...

  1. Initially render the blurred placeholder images.

  2. Download the intended images (now formatted as WebP images) that are at most several hundred KBs large in size. These new, optimized images preserve as much of the image's original quality as possible and save bandwidth usage.

These changes should shrink the bars in the developer console's network waterfall chart.

To generate the blurred placeholder images, let's first modify the <HomePage /> component's getStaticProps() function so that these images get generated at build time. For each pet animal type, call the getPlaiceholder() method of the Plaiceholder library with one argument: a string that references the image. This string can be a relative path or an absolute URL. Here, the string will be an absolute URL since the images come directly from Unsplash. From the getPlaiceholder() method, we can destructure out any of the following:

  • css - An object that contains backgroundImage, backgroundPosition, backgroundSize and backgroundRepeat. The CSS-based blurred placeholder image is comprised of a set of linear gradients.

  • svg - An <svg /> element.

  • base64 - A Base64 string (data:image/<format>;base64,... for the src attribute of an <image /> element or background-image CSS property).

  • blurhash - An object that contains a Blurhash string (hash) and the dimensions of the blurred placeholder image (width and height).

  • img - An object that contains the attributes that are required by an <img /> element (src, width, height and type).

For the Next.js application, we will destructure out blurhash for a Blurhash-based blurred placeholder image and img for information about the actual image.

(pages/index.tsx)

Note #1: The getPlaiceholder() method can accept a second, optional argument that lets you override the default placeholder size, configure the underlying Sharp library that Plaiceholder uses for image processing, etc. Check the Plaiceholder documentation here for more information.

Note #2: If you append the query parameters fm=blurhash&w=32 to the URL of an Unsplash image, then the response returned will be a Blurhash encoded string for that Unsplash image.

Then, we need to update the shared/interfaces/petfinder.interface.ts file to account for the newly added properties (blurhash and img) on the AnimalType interface.

(shared/interfaces/petfinder.interface.ts)

After updating the shared/interfaces/petfinder.interface.ts file, let's replace the image that's set as a CSS background image in the <TypeCard /> component with two components:

  • A <Blurhash /> component from the react-blurhash library. This component will take the Blurhash string and dimensions from the pet animal type's blurhash object (from the getPlaiceholder() method) and render a beautiful blurred placeholder image within a <canvas /> element.

  • A <Image /> component from next/image. This component will take the attributes of the original image from the pet animal type's img object (also from the getPlaiceholder() method) and render an optimized version of the image (formatted as WebP, smaller in size, etc.).

Run the Next.js application in development mode:

When you visit the Next.js application in a browser, you will notice that the blurred placeholder images immediately appear. However, when the optimized images are loaded, you will notice that some of them are not aligned correctly within their parent containers:

To fix this, let's use some of the <Image /> component's advanced props to instruct how an image should fit (via the objectFit prop) and be positioned (via the objectPosition prop) within its parent container. For these props to work, you must first set the <Image /> component's layout prop to fill so that the image can grow within its parent container (in both the x and y axes).

The objectFit and objectPosition props correspond to the CSS properties object-fit and object-position. For objectFit, set it to cover so that the image occupies the entirety of the parent container's space while maintaining the image's aspect ratio. The outer portions of the image that fail to fit within the parent container will automatically be clipped out.

For objectPosition, let's assign each pet animal type a unique objectPosition that positions the pet animal within the image to the center of the parent container. Any pet animal type that is not assigned a unique objectPosition will by default have objectPosition set to center.

(enums/index.ts)

(pages/index.tsx)

(shared/interfaces/petfinder.interface.ts)

When you re-run the Next.js application, you will notice that the images are now correctly aligned within their parent containers.

When you check the terminal, you will encounter the following warning message being logged:

Since we specified the layout prop as fill for the <Image /> component within the <TypeCard /> component, we tell the <Image /> component to stretch the image until it fills the parent element. Therefore, the dimensions of the image don't have to be specified, which means the height and width props should be omitted.

Let's make the following changes to the <TypeCard /> component in components/TypeCard.tsx:

Resizing an Unsplash Image with Imgix's URL API#

Unsplash leverages Imgix to quickly process and deliver images to end users via a URL-based API. Based on the query parameters that you append to an Unsplash image's URL, you can apply various transformations to the image, such as face detection, focal point cropping and resizing.

Given the enormous base dimensions of the Next.js application's Unsplash images (e.g., the dog image is 5184px x 3456px), we can resize these images to smaller sizes so that Plaiceholder can fetch them faster.

Since the images occupy, at most, a parent container that's 160px x 160px, we can resize the initial Unsplash images so that they are, at most, 320px in width (and the height will be resized proportionally to this width based on the image's aspect ratio). Let's append the query parameter w=320 to the Unsplash image URLs in enums/index.ts.

(enums/index.ts)

When you re-run the Next.js application, you will notice that the Next.js application runs much faster now that Plaiceholder doesn't have to wait seconds to successfully fetch large Unsplash images.

Next Steps#

If you find yourself stuck at any point during this tutorial, then feel free to check out the project's repository for this part of the tutorial here.

Proceed to the next part of this tutorial series to learn how to create pages for dynamic routes in Next.js.

If you want to learn more advanced techniques with TypeScript, React and Next.js, then check out our Fullstack React with TypeScript Masterclass.

Sources#

Clap
1|2