I want to allow my users to download a text file.
How would I go about this in Remix?
I tried exposing the txt file via a resource route, but then the user navigates away from the page. But I'd the file to just download, or maybe have them pick a download location. In any case, I want the user to stay on the page.
2 Answers 2
I just had to figure out how to do this for an app that I am building.
In short, you will need to set Content-Disposition header to "attachment". This forces the download.
Then you will to construct a response that streams the document that you want to be dowloaded.
Here is an end to end example:
import { createReadableStreamFromReadable } from '@remix-run/node';
import { Readable } from 'node:stream';
export const loader = async () => {
const file = createReadableStreamFromReadable(
Readable.from(['Hello, World!']),
);
return new Response(file, {
headers: {
'Content-Disposition': 'attachment; filename="hello.md"',
'Content-Type': 'text/markdown',
},
});
};
Here I use Readable.from(['Hello, World!']) to construct the file, but this could be fs.readFile, etc.
You place all of this in a _route.ts of your choice.
When linking to this resource route from the app, you will need to add reloadDocument attribute to the link, e.g.
<Link to="/download" reloadDocument />
This forces the use of browser navigation instead of client-navigation.
1 Comment
const filename = "example.txt"; const content = "Hello, this is a test file."; // Create a zip file const zip = new JSZip(); zip.file(filename, content); const zipContent = await zip.generateAsync({ type: "blob" }); return new Response(zipContent, { headers: { "Content-Type": "application/zip", "Content-Disposition": 'attachment; filename="files.zip"', }, });If you use a standard a tag, you can trigger a download if you add the download attribute to it => https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-download
You can then link to the resource route that produces the txt file and the download should work as expected.
Maybe this even works with the Remix/ReactRouter Link component, but I’m just on the phone currently so cannot test this. Anyways, a standard anchor tag should work.