I've been tinkering around with Next.js lately, so it looks like there will be a lot of Next.js stuff going on for a while.
Partly because I recently created a Next.js-made blog with a posting function, but also because I had the opportunity to implement streaming images, and I want to write down my findings.
I also think that I will need this when developing web services in Next.js.
When you need to stream images in Next.js
Next.js allows static resources to be hosted in the public
directory, but this does not mean that they can be accessed in real time while the server is running.
Static resources stored in the public
directory are compiled under the .next directory when Next.js is built, so if you upload any files to the public
directory while the server is running in production mode, you will not be able to access them directly by URL by file name The file name cannot be directly accessed by URL in the .next directory when the server is running in production mode.
When I first started learning Next.js, I had a lot of trouble understanding this part. It was a mystery because in development mode, files in the public
directory were accessible while the server was running because they were compiled sequentially.
In a case like this, I think I would need to write a process to read the uploaded files in the Next.js API route and stream them.
Perhaps there is another, better way to do this.
In the meantime, I've included my solution and the source code below.
Source code for streaming images with Next.js
Please refer to the streaming distribution as it is used in some parts of this site as well.
Also, all source code for this site is available to the public.
The code to realize streaming delivery is as follows. It does not require particularly difficult coding, and can be done using only the Node.js built-in module.
In the code example below, image data streaming delivery is performed.
// app/[lang]/api/media-stream/icon/route.ts
import type { NextRequest } from 'next/server';
import { NextResponse } from "next/server";
import { createReadStream } from 'fs'; // Streaming module for reading
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 ) {
// Set mime type for images in response header
responseHeader.set( 'Content-Type', category.icon_mime );
// Specify image path in stream for reading
const stream = createReadStream( MEDIA_ROOT_PATH + category.icon );
// Set the responseHeader and stream in the Node.js built-in Response class and send it to the client.
return new Response( stream as any, { headers: responseHeader });
}
}
return NextResponse.json({ message: 'メディアの取得に失敗しました。' }, { status: 401 });
}
export { MediaStreamIcon as GET }
This will deliver the image data directly to the client as a stream.
In my case, I have set the api to app/[lang]/api/media-stream/icon/route.ts
, so when I open the URL https://ryusei.io/api/media-stream/icon?id=[icon_id]
in my browser, the image The image will be displayed.
Set this URL to the src
attribute of the img
tag in HTML to display the image.
<img src="https://ryusei.io/api/media-stream/icon?id=[icon_id]" alt="icon" loading="lazy" /?
Plus: Optimize image delivery
If you want to optimize the delivery of images, we recommend using the Next.js built-in Image component.
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" />
}
As a reminder, the width
and height
of the props must be specified.
You must also add the images
setting to the next.config.js
file and set the domain of the URL from which the images will be retrieved.
const nextConfig = {
// .....
images: {
domains: [ process.env.APP_URL ],
},
// .....
}