どうも、@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で画像をストリーミング配信するソースコード
当サイトでもストリーミング配信を使用している個所があるので参考にしてみてください。
また、当サイトのソースコードは全て公開しています。
ストリーミング配信を実現するコードは以下のようになります。特に難しいコーディングが必要になるわけでもなく、Node.jsの組み込みモジュールだけでできます。
以下のコード例では、画像データのストリーミング配信を行っています。
// 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
属性に設定すれば画像が表示されます。
<img src="https://ryusei.io/api/media-stream/icon?id=[icon_id]" alt="アイコン" loading="lazy" /?
おまけ:画像の配信を最適化する
画像の配信を最適化したい場合は、Next.js組み込みのImageコンポーネントの使用をおすすめします。
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" />
}
注意点として、プロップスのwidth
とheight
は必ず指定しなければなりません。
また、next.config.js
ファイルに、images
の設定を追加し、画像を取得するURLのドメインを設定しておかなければなりません。
const nextConfig = {
// .....
images: {
domains: [ process.env.APP_URL ],
},
// .....
}