Allow newly added images to be loaded after a build in Next.js

I’ve recently rewritten a React app in Next.js (version 14.1.4). It’s a website that has a page with a list of products, each product consisting of some text and an image. The content is managed by the owner through an admin panel, which is a separate application, so products can be changed, deleted and added at any time.

After building the app on my server, I noticed that newly added images weren’t being loaded by Next.js. This made sense to me as I was using the Image component from next/image. Since my images are already optimized in the backend with the appropriate formats and sizes, I decided to take care of it myself and avoid using the Image component. However, this didn’t work as I still had the problem of new images not loading.

I couldn’t find any information in the official documentation on how to handle this properly, so I created a custom request handler that returns the appropriate image as a response:

// api/images/[itemId]/[imageName]/route.ts

import { type NextRequest } from "next/server";
import path from "path";
import fs from "fs";

export async function GET(
  request: NextRequest,
  { params }: { params: { itemId: string; imageName: string; } }
) {
  const searchParams = request.nextUrl.searchParams;
  const size = searchParams.get("size");
  const type = searchParams.get("type");
  const productType = searchParams.get("productType");

  const { itemId, imageName } = params;

  const imagePath = path.join(
    process.cwd(), "public", "uploads", `${productType}`, itemId, `${imageName}_${size}.${type}`
  );

  if (!fs.existsSync(imagePath)) {
    return new Response("Image not found", { status: 404 });
  }

  let contentType: string;
  switch (type) {
    case "jpg":
      contentType = "image/jpeg";
      break;
    case "webp":
      contentType = "image/webp";
      break;
    default:
      contentType = "image/jpeg";
  }

  const headers = {
    "Content-Type": contentType
  };

  // Create a read stream from the image file and return it as the response
  const stream = fs.createReadStream(imagePath);

  // @ts-ignore
  return new Response(stream, { headers });
}

I also created a custom Image component for this:

import clsx from "clsx";

type Props = React.ComponentPropsWithoutRef<"img"> & {
  src: string;
  alt: string;
  productType: "products" | "gallery";
  className?: string;
};

export default function CustomImage({
  src,
  alt,
  productType,
  className,
  ...rest
}: Props) {
  return !src ? <p>No image</p> : (

    <picture>
      <source
        type="image/webp"
        srcSet={`${src}?size=sm&type=webp&productType=${productType} 450w, ${src}?size=md&type=webp&productType=${productType} 900w, ${src}?size=lg&type=webp&productType=${productType} 1350w`}
        sizes="(min-width: 61.25em) 500px, 100vw" // 980px
      />
      <img
        loading="lazy"
        src={`${src}?size=sm&type=jpg&productType=${productType}`}
        alt={alt ?? ""}
        width="400"
        height="300"
        className={clsx("product-image", className)}
        {...rest}
      />
    </picture>
  );
}

This seemed to be working fine, but I noticed that the images were not always loading on my mobile device. When visiting the /products web page, the images are displayed without any problems. However, when I navigate from the product detail page /products/slug back to /products using the link provided in the breadcrumbs (it’s basically just a Link component), I’m back on the same page but without the images loading. Again, this behavior somehow only occurs on my mobile device, in all major browsers. Also worth noting is that I have the same Link component in my main navigation and everything works fine when I click on the /products link, very strange.

I find this kind of bug hard to track down, because I don’t know what’s really going on and what Next.js is doing under the hood that could be causing this error.

So, despite my attempts to gain control over the images, it seems that Next.js is still messing with my images, and I don’t want that.

Which brings me to my question: Am I on the right track with my approach to handling the images myself, or am I completely missing something and there is a much easier way to handle this?