How to Dynamically Display Images Uploaded in a Production Next.js Environment

Created onSeptember 3, 2025 at 7:08 AM
thumbnail Image

This is @ryusei__46 for you.

I've been diving deep into Next.js lately, so expect several posts about Next.js in the coming days.

Part of this comes from recently developing a Next.js-based blog with post functionality, but I've also encountered an opportunity to implement image streaming, so I'd like to document what I've learned here.

This knowledge will also be highly relevant when developing web services with Next.js.

When Image Streaming Becomes Necessary in Next.js 

In Next.js, you can host static resources in the public directory, but this doesn't mean they become immediately accessible during server operation. Static files stored in the public directory are compiled into the .next directory during Next.js build, so even if you upload files to the public directory while the server is running in production mode, you can't directly access them by file name via URL.

When I first started learning Next.js, I didn't understand this aspect and struggled quite a bit. In development mode, files in the public directory are compiled incrementally, so they remain accessible while the server is running, which made it particularly confusing.

For such cases, you'll likely need to implement logic in Next.js's API routes to read uploaded files and stream them to users.

There might be better solutions available though.

For now, I'll share the method I implemented along with the source code below.

Source code for streaming images with Next.js 

Our site also uses streaming in some sections, so please refer to them for reference.

Additionally, all source code for this site is publicly available.

OGP Image
Blog template with posting functionality developed in Next.js! | Ryusei.IO

I'm distributing a Next.js version of a blog system that allows you to post articles and manage categories from the admin screen, just like WordPress.

faviconryusei.io

The code to implement streaming functionality would look something like this. It doesn't require particularly complex coding - everything can be done using Node.js's built-in modules alone.

In the code example below, we're demonstrating how to stream image data.

typescript
// app/[lang]/api/media-stream/icon/route.ts

import type { NextRequest } from 'next/server';
import { NextResponse } from "next/server";
import { createReadStream } from 'fs'; // 読み取り用ストリーミングモジュール
import { PrismaClient } from '@prisma/client';

const APP_PATH = process.env.APP_ROOT_PATH;
const MEDIA_ROOT_PATH = `${ APP_PATH }/public`;

const prisma = new PrismaClient();

async function MediaStreamIcon( request: NextRequest ) {

  const responseHeader = new Headers( request.headers );
  const requestUrl = new URL( request.url ).searchParams;
  const id = requestUrl.get('id');

  if ( id && id.match( /^[0-9]+$/g ) ) {
    const category = await prisma.category.findFirst({
      where: { id: Number( id ) }
    });

    if ( category && category.icon && category.icon_mime ) {
      // レスポンスヘッダーに画像用のマイムタイプを設定
      responseHeader.set( 'Content-Type', category.icon_mime );
      // 読み込み用ストリームに画像パスを指定
      const stream = createReadStream( MEDIA_ROOT_PATH + category.icon );
	 // Node.js組み込みのResponseクラスに、設定したresponseHeaderとstreamを設定しクライアントに送信
      return new Response( stream as any, { headers: responseHeader });
    }
  }

  return NextResponse.json({ message: 'メディアの取得に失敗しました。' }, { status: 401 });
}

export { MediaStreamIcon as GET }

This allows you to directly stream image data to the client. In my implementation, I've configured the API endpoint at app/[lang]/api/media-stream/icon/route.ts. When you open the URL https://ryusei.io/api/media-stream/icon?id=[icon_id] in a browser, the image will be displayed. Setting this URL as the src attribute in an HTML img tag will also render the image.

http
<img src="https://ryusei.io/api/media-stream/icon?id=[icon_id]" alt="icon" loading="lazy" /?

Bonus: Optimizing Image Delivery 

For optimized image delivery, we recommend using the built-in Image component in Next.js.

typescript
import Image from 'next/image';

export default function ImageComponent() {
	return <Image src="/api/media-stream/icon?id=[icon_id]" alt="icon" loading="lazy" width="300" height="250" />
}

Important notes: You must always specify both width and height for props.

Additionally, you need to add image configuration to your next.config.js file and set the domain for the image retrieval URL.

javascript
const nextConfig = {
	 // .....
	images: {
    domains: [ process.env.APP_URL ],
    },
	// .....
}