Optimize image loading on Next.js

By default, built-in Next.js image optimization is disabled on App Hosting unless you explicitly set images.unoptimized to false or use a custom Image Loader.

You can configure a Next.js global image loader with the Image Processing Firebase Extension to enable on-demand optimization and delivery of images in your Next.js app on App Hosting.

Prerequisites

  • You have a Firebase project and an App Hosting backend.
  • Cloud Storage is enabled in your project.
  • Cloud Functions for Firebase is enabled in your project.

Install the extension

Navigate to the Image Processing Extension in the Firebase Extensions Hub and select Install in Firebase Console. Follow the on-screen instructions.

Configure local image optimization (optional)

If your application uses local images that you want to optimize using this extension, you need to configure the "Hostname" parameter during the extension installation process.

  1. During Extension Configuration: When you reach the "Configure extension" step, locate the "Hostname" parameter.

  2. Set the Hostname: Enter the default domain for your Firebase App Hosting backend. This domain typically ends with .hosted.app.

Once installation is complete, the Image Processing API should be deployed as a function in Cloud Functions. Navigate to the Functions dashboard in the Firebase console and copy the Image Processing API trigger URL.

Set up a custom image loader

Create an image loader that uses the deployed Image Processing API for every optimized image component. As an optimization, rewrite to it so that it's served under the same Firebase App Hosting domain and cached behind the same global CDN as your Next.js application.

First, add the following fields to your next.config.js file:

// @ts-check

/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    loader: "custom",
    loaderFile: "./loader.js",
  },
  async rewrites() {
    return [
      {
        source: "/_fah/image/:path*",
        destination:
          "<CLOUD_FUNCTIONS_URL>/:path*",
      },
    ];
  },
}

module.exports = nextConfig;

Then create a loader.js file in your root directory with the following contents:

"use client"

export default function myImageLoader({ src, width, quality }) {
  if (process.env.NODE_ENV === "development") {
    return src;
  }

  const operations = [
    {
      operation: "input",
      type: "url",
      url: src,
    },
    { operation: "resize", width: width },
    { operation: "output", format: "webp", quality: quality || 75 },
  ];

  const encodedOperations = encodeURIComponent(JSON.stringify(operations));

  return `/_fah/image/process?operations=${encodedOperations}`;
}

Create a commit that includes these changes and push it to your live branch. Then, wait for the App Hosting rollout to complete.