Next.jsの本番環境でアップロードされた画像を動的に表示する方法

作成日2025年9月3日 7:08
"サムネイル画像"

どうも、@ryusei__46です。

最近はNext.jsをいじりまくっているので、しばらくNext.jsのネタが続くことになりそうです。

最近投稿機能付きのNext.js製のブログを作ったという経緯もありますが、画像のストリーミング配信を実装する機会があったので知見を書き留めておきたいと思います。

また、Next.jsでWebサービスを開発する際にも必要になることが多いと思います。

Next.jsで画像のストリーミング配信が必要になる時 

Next.jsでは、publicディレクトリーに静的リソースをホストすることができますが、これはサーバー稼働中にリアルタイムでアクセスできるというわけではありません。

publicディレクトリーに保存されている静的リソースは、Next.jsのビルド時に.nextディレクトリー配下にコンパイルされるので、プロダクションモードでサーバーを稼働させている状態で、publicディレクトリーに何かファイルをアップロードしてもファイル名で直接URLでアクセスすることができません。

私はNext.jsの学習を始めたころ、この部分が分かってなくてだいぶてこずりました。デベロップメントモードでは、逐次コンパイルされるので、publicディレクトリー内のファイルはサーバー起動中にもアクセスできたので、謎でした。

こういう場合は、Next.jsのAPIルートでアップロードされたファイルを読み込んでストリーミング配信する処理を書く必要が出てくると思います。

もしかすると、他にもっと良い方法があるかもしれません。

とりあえず、以下で私が解決した方法とソースコードを載せておきます。

Next.jsで画像をストリーミング配信するソースコード 

当サイトでもストリーミング配信を使用している個所があるので参考にしてみてください。

また、当サイトのソースコードは全て公開しています。

OGP Image
Next.jsで開発した投稿機能搭載のブログテンプレートを公開! | Ryusei.IO

WordPressのように、管理画面から記事の投稿やカテゴリー管理などを行えるようにした、Next.js版ブログシステムを作ったので配布します。Next.js + Prisma + MySQLで動作します。

faviconryusei.io

ストリーミング配信を実現するコードは以下のようになります。特に難しいコーディングが必要になるわけでもなく、Node.jsの組み込みモジュールだけでできます。

以下のコード例では、画像データのストリーミング配信を行っています。

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 }

これで画像データを直接ストリームとしてクライアントに配信できます。

私の場合は、apiをapp/[lang]/api/media-stream/icon/route.tsに設定しているので、URLhttps://ryusei.io/api/media-stream/icon?id=[icon_id]をブラウザーで開くと画像が表示されます。

このURLをHTMLのimgタグのsrc属性に設定すれば画像が表示されます。

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

おまけ:画像の配信を最適化する 

画像の配信を最適化したい場合は、Next.js組み込みのImageコンポーネントの使用をおすすめします。

typescript
import Image from 'next/image';

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

注意点として、プロップスのwidthheightは必ず指定しなければなりません。

また、next.config.jsファイルに、imagesの設定を追加し、画像を取得するURLのドメインを設定しておかなければなりません。

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