Send file as response #15453
-
As titled, is this possible? Using nodejs + express, i can do it like this
But i see that Next's response doesn't have sendFile method, and i tried the above like the following
But it didn't work. My idea is to serve an image file from a folder located on same level as public folder in NextJS directory and return it as response to the user. But im not sure if res.send() can do that. |
Beta Was this translation helpful? Give feedback.
Replies: 12 comments 32 replies
-
Hi @Corouna. The var filePath = path.join(__dirname, 'myfile.mp3');
var stat = fileSystem.statSync(filePath);
response.writeHead(200, {
'Content-Type': 'audio/mpeg',
'Content-Length': stat.size
});
var readStream = fileSystem.createReadStream(filePath);
// We replaced all the event handlers with a simple call to readStream.pipe()
readStream.pipe(response); Also be sure to access the file via |
Beta Was this translation helpful? Give feedback.
-
It's technically possible, but I'm not sure if it will work on all deployment platforms. As @jamesmosier said, you can send a buffer using Example: /*
Project structure:
.
├── images_folder
│ └── next.jpg
├── package.json
├── pages
│ ├── api
│ │ └── image.js
│ └── index.js
├── README.md
└── yarn.lock
*/
// pages/api/image.js
import fs from 'fs'
import path from 'path'
const filePath = path.resolve('.', 'images_folder/next.jpg')
const imageBuffer = fs.readFileSync(filePath)
export default function(req, res) {
res.setHeader('Content-Type', 'image/jpg')
res.send(imageBuffer)
} Out of curiosity, why do you need to serve files outside of Next's |
Beta Was this translation helpful? Give feedback.
-
@jamesmosier @cristiand391 thanks for the reply. Im still on vacation now but will definitely give that a try once im back. Many thanks again 😄 |
Beta Was this translation helpful? Give feedback.
-
For future googlers, Here's the full working code. Live demo: https://stackblitz.com/edit/nextjs-image-api?file=pages/api/[id].js import fs from 'fs';
import path from 'path';
export default function(req, res) {
const id = req.query.id;
const filePath = path.join(process.cwd(), `/public/images/${id}.svg`);
try {
const imageBuffer = fs.readFileSync(filePath);
res.setHeader('Content-Type', 'image/svg+xml');
res.send(imageBuffer);
} catch (e) {
res.status(400).json({ error: true, message: 'Image not found' });
}
} |
Beta Was this translation helpful? Give feedback.
-
Could someone please show a working example of how to save this data on the client side pages. i keep getting a corrupted file with this method const data = await axios.get("/api/shipments/downloadManifest");
const pdfBlob = new Blob([data.data], { type: "application/xlsx" });
return saveAs(pdfBlob, "manifest.xlsx"); |
Beta Was this translation helpful? Give feedback.
-
Hi all, been trying to get an API route to send an image based on a URL within a database object. code is like this: export default async function handler(req, res) {
const { id } = req.query;
let canSeeImage = false; // there is more behind this, but its not relevant.
const image = await get(id);
if(canSeeImage) {
const imageRes = fetch(image.url);
// how to send it back?
// tried this:
// const blob = await imageResponse.blob();
// const blobUrl = URL.createObjectURL(blob);
// const imageBlob = new Blob([blob], { type: 'image/jpeg' });
// res.setHeader('Content-Type', imageBlob.type);
// res.setHeader('Content-Length', imageBlob.size);
// res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
// res.status(200).send(imageBlob);
}
} |
Beta Was this translation helpful? Give feedback.
-
Does anyone know a solution that works with the newer App router? API routes are different and don’t give you an res object and as far as i can tell, require you to return a NextReponse object. I need to pipe a video through an api route and I can’t figure out how to do it with this App router API routes… |
Beta Was this translation helpful? Give feedback.
-
Below is an updated answer for Next 13 Route Handlers. This answer uses streams, in order to avoid bloating your server RAM for big files or high request volumes. You may get more information about the conversion from Node.js logic (Next.js API routes) to web platform logic used by route handlers (needed so they work both in Node.js and the Edge runtime) in this Stack Overflow answer. See an example usage in the State of JavaScript codebase, to download a CSV+JSON zip out of a Mongo database. In particular, you'll want to set the "Content-Size" header properly with the file size. Bonus: we have also tried to convert Regarding file download: // @see https://github.com/vercel/next.js/discussions/15453#discussioncomment-6226391
import fs from "fs"
/**
* Took this syntax from https://github.com/MattMorgis/async-stream-generator
* Didn't find proper documentation: how come you can iterate on a Node.js ReadableStream via "of" operator?
* What's "for await"?
*/
async function* nodeStreamToIterator(stream: fs.ReadStream) {
for await (const chunk of stream) {
yield chunk;
}
}
/**
* Taken from Next.js doc
* https://nextjs.org/docs/app/building-your-application/routing/router-handlers#streaming
* Itself taken from mozilla doc
* https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream#convert_async_iterator_to_stream
*/
function iteratorToStream(iterator): ReadableStream {
return new ReadableStream({
async pull(controller) {
const { value, done } = await iterator.next()
if (done) {
controller.close()
} else {
// conversion to Uint8Array is important here otherwise the stream is not readable
// @see https://github.com/vercel/next.js/issues/38736
controller.enqueue(new Uint8Array(value))
}
},
})
}
export function streamFile(path: string): ReadableStream {
const downloadStream = fs.createReadStream(path);
const data: ReadableStream = iteratorToStream(nodeStreamToIterator(downloadStream))
return data
} Usage: const stats = await fsPromises.stat(zipFilePath);
const data: ReadableStream = streamFile(zipFilePath)
const res = new NextResponse(data, {
status: 200,
headers: new Headers({
"content-disposition": `attachment; filename=${path.basename(
zipFilePath
)}`,
"content-type": "application/zip",
"content-length": stats.size + "",
})
})
return res Thanks @rohithandique for the feedback and the many people whose code I had to copy paste and merge to produce this beauty. |
Beta Was this translation helpful? Give feedback.
-
Yoo, Wall_Naive here from the reddit question (https://www.reddit.com/r/nextjs/comments/13toeob/nextjs_response_with_a_stream/ Heres how I setup the streaming from the file. I just added the events when we got dada, when we ended, and when we got an error. It works with the given .iso file. The given example is with NextJS 13 with Typescript. Credit to eric-burel I couldn't figure out the NextJS Response headers. Still trying to figure out etags import fs, { Stats } from "fs";
import { NextRequest, NextResponse } from "next/server";
import path from "path";
import { ReadableOptions } from "stream";
function streamFile(path: string, options?: ReadableOptions): ReadableStream<Uint8Array> {
const downloadStream = fs.createReadStream(path, options);
return new ReadableStream({
start(controller) {
downloadStream.on("data", (chunk: Buffer) => controller.enqueue(new Uint8Array(chunk)));
downloadStream.on("end", () => controller.close());
downloadStream.on("error", (error: NodeJS.ErrnoException) => controller.error(error));
},
cancel() {
downloadStream.destroy();
},
});
}
export async function GET(req: NextRequest): Promise<NextResponse> {
const uri = req.nextUrl.searchParams.get("uri");
const file = "/home/manjaro/Downloads/manjaro-kde-22.1.3-230529-linux61.iso"; // req.nextUrl.searchParams.get("file");
const stats: Stats = await fs.promises.stat(file);
const data: ReadableStream<Uint8Array> = streamFile(file); //Stream the file with a 1kb chunk
const res = new NextResponse(data, {
status: 200,
headers: new Headers({
"content-disposition": `attachment; filename=${path.basename(file)}`,
"content-type": "application/iso",
"content-length": stats.size + "",
}),
});
return res;
}
|
Beta Was this translation helpful? Give feedback.
-
I recently ran into the same issue. I want an equivalent to |
Beta Was this translation helpful? Give feedback.
-
@eric-burel - Thank you very much for highlighting the difference between the Please help me with the last part of this query...
In the current usage, |
Beta Was this translation helpful? Give feedback.
-
Hey folks, I've worked up a simpler solution thanks to @karlhorky feedback, Node.js has a Live demo: https://nextpatterns.dev/p/gems/route-handlers-file-streaming import fs from "fs/promises"
import path from "path"
export async function GET(request: Request) {
const filePath = path.resolve("/path-to-your-image.jpg")
const stats = await fs.stat(filePath);
const fileHandle = await fs.open(filePath)
const stream = fileHandle.readableWebStream(
{ type: "bytes" }
)
return new Response(stream, {
status: 200,
headers: new Headers({
"content-type": "image/jpeg",
"content-length": stats.size + "",
})
})
// needs next 15 with next/after
after(() => { fileHandle.close() })
} In case you have a doubt about perf impact, the first spike on the left is the RAM impact of the proper solution (small spike), the second is the RAM impact of the wrong solution (big spike). Screenshot is actually from an Astro app and without proper file closing but you get the point, in the stream implementation, the big file is not copied in memory, in the copy then stream implementation, it bloats the RAM. See nodejs/node#54041 and #68109 |
Beta Was this translation helpful? Give feedback.
Hi @Corouna. The
res
object is Node's built in response object, so if you wanted to send a file you could follow something like this exampleAlso be sure to access the file via
process.cwd()
as described here https://nextjs.org/docs/basic-features/data-fetching#reading-files-use-processcwd