ionicons-v5-a Back
Next.js website optimization: The road to 99% Google PageSpeed

5 min read

Next.js website optimization: The road to 99% Google PageSpeed

Discover valuable tips to enhance the Google PageSpeed ranking of your publication, built with Next.js using static export.

In today’s fast-paced digital world, website performance is paramount, and achieving a blazingly fast loading time is crucial to engage your audience and improve user experience. Here is my experience of achieving a high ranking on Google PageSpeed

Image optimizations

It’s difficult to imagine a publication without images, and it would be incredibly convenient to automate image optimization to prevent serving large-sized images.

Next-gen formats

The current method to utilize next/image with next export is by employing an external URL from an image provider. This is due to next/image’s default usage of the Node.js server-based image optimization API. There is a big discussion opened on github about this problem.

next/image does not currently perform image optimization during next build, and instead optimizes images on-demand.

Luckily, there is a next-export-optimize-images that addresses this issue for Next.js.

Step 1: Install next-export-optimize-images.

npm install -D next-export-optimize-images

Step 2: Update next.config.js.

// @ts-check
const { withContentlayer } = require('next-contentlayer');
const withExportImages = require('next-export-optimize-images');
 * @type {import('next').NextConfig}
const nextConfig = {
  output: 'export',
module.exports = withContentlayer(withExportImages(nextConfig));

Step 3: Change build script command in package.json.

  //"build": "next build",
  "build": "next build && next-export-optimize-images"

Step 4: Create export-images.config.js.

 * @type {import('next-export-optimize-images').Config}
const config = {
  // in my case I want all my images to be converted to webp format.
  // WebP is a modern image format developed by Google, known for its high-quality compression and smaller file sizes
  convertFormat: [['png', 'webp']],
  quality: 60, // default 75 but I didn't notice any differences
module.exports = config;

Step 5: Import and use next/image as usual.

import img from './img.png';
<Image src={img} alt="" />;

Use explicit width and height on image elements

Cumulative Layout Shift (CLS) is a metric used to measure the visual stability of a web page during the loading process. It quantifies how much the elements on a page shift or move around as the content loads and changes dynamically.

CLS is important because unexpected layout shifts can lead to a poor user experience, especially when elements move out of place while the user is trying to interact with the page.

That’s why it’s important to set the width and height attributes of the image element. Doing so allows the browser to reserve the appropriate space for the image based on its aspect ratio even before the image is fully loaded, thereby preventing content jumps.

<Image src={img} width="800" height="400" />

next-export-optimize-images will use this infromation for generating a set of images for different screens.

Example of generated HTML:

  class="rounded-lg mt-8 sm:mt-0"
    /_next/static/chunks/images/_next/static/media/kd.b2d93106_384.webp 1x,
    /_next/static/chunks/images/_next/static/media/kd.b2d93106_640.webp 2x

Images lazy loading

Lazy loading is a web development technique that defers the loading of images until they are about to be seen by the user. It improves page load times and user experience by reducing initial page payload.

Previously, two methods were employed to delay the loading of off-screen images:

  1. Utilizing the Intersection Observer API.
  2. Employing scroll, resize, or orientationchange event handlers.

Now, there is a browser-level loading attribute for images that allows us to utilize this feature. next/image uses loading="lazy" by default so nothing to do here.

Largest Contentful Paint (LCP) is a web performance metric that measures how quickly the main content of a webpage is loaded and displayed to users. It’s crucial for a positive user experience.

In my case there were a problem with LCP for my Homepage’s photo image being loading="lazy". So I needed to prioritize the loading of this asset.

  // the image will be considered high priority and preload
  className="rounded-lg mt-8 sm:mt-0"

P.S. loading="lazy" attribute can be used for iframes (can be useful for YouTube embed videos).

Fonts optimizations

Turned out next.js has it’s own mechanism to add Google Fonts.

Automatically self-host any Google Font. Fonts are included in the deployment and served from the same domain as your deployment. No requests are sent to Google by the browser.

import { Inter } from 'next/font/google'
// If loading a variable font, you don't need to specify the font weight
const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
export default function RootLayout({
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en" className={inter.className}>


By adhering to the recommendations provided by Google PageSpeed, I have successfully achieved high scores on my web pages. Here are some examples of the results I obtained through these optimizations:

Screenshot of Google PageSpeed results (mobile)
Screenshot of Google PageSpeed results (mobile)

Screenshot of Google PageSpeed results (desktop)
Screenshot of Google PageSpeed results (desktop)

In conclusion, page speed loading plays a critical role in enhancing user experience, user engagement, and overall website performance. Users expect fast-loading pages, and a slow website can lead to high bounce rates and lost opportunities. Google PageSpeed Insights is a valuable tool that offers data-driven recommendations to optimize website speed and performance.

Dzmitry Kozhukh

Written by Dzmitry Kozhukh

Frontend developer


Sunsetting Redux Saga
react Sunsetting Redux Saga

Uncovering reasons to move away from redux-saga complexity for simpler and more maintainable state management in React applications.

3 min read