Creating a Remix Server to Return a React Page as a PDF
ahyaemon
Posted on August 25, 2024
I wanted to create a server that returns a page written in React as a PDF, but I couldn't find a standard method for doing so. Therefore, I tried various approaches and am sharing the one that worked for me. If you know of a better way, I'd appreciate your comments.
What I Want to Do
- Write the page to be converted to PDF in React.
- Allow the page created in step 1 to be downloadable as a PDF.
The server will work as follows: access to / will display the page from step 1, and access to /pdf will allow downloading the page as a PDF.
Steps and Explanation
Here's a step-by-step guide, starting from project creation.
1. Creating a Remix Server
First, create the project using the command from Remix's official Quick Start guide:
npx create-remix@latest
This will create the project in an interactive format. I opted to use pnpm
instead of npm
:
> Install dependencies with npm?
> No
After the project is created, install the necessary packages:
pnpm i
Next, start the development server:
pnpm dev
Now, if you access http://localhost:5173
, you should see the initial page.
To ensure that the build passes and the production server can start, run:
pnpm build
pnpm start
You can confirm the operation by accessing http://localhost:3000
.
2. Configuration Adjustments
I wanted the development server to run on port 3000, so I modified the scripts
in package.json
:
"scripts": {
"build": "remix vite:build",
- "dev": "remix vite:dev",
+ "dev": "remix vite:dev --port 3000",
"lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .",
"start": "remix-serve ./build/server/index.js",
"typecheck": "tsc"
},
With this change, pnpm dev
will allow access via http://localhost:3000
.
3. Creating the PDF Endpoint
To make the content of /
available as a PDF at /pdf
, I added puppeteer
to the dependencies to convert HTML to PDF:
pnpm add puppeteer
Next, I added /app/routes/pdf.tsx
. By placing it under the routes
directory, Remix automatically detects it and creates the /pdf
endpoint:
import puppeteer from "puppeteer";
export async function loader() {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto("http://localhost:3000", { waitUntil: 'networkidle0' });
const pdf = await page.pdf({ format: 'A4' });
await browser.close();
return new Response(pdf, {
status: 200,
headers: {
"Content-Type": "application/pdf",
'Content-Disposition': 'attachment; filename=remix.pdf',
},
})
}
Now, if you access http://localhost:3000/pdf
on either the development or production server, the same content as http://localhost:3000
will be downloaded as a PDF.
4. Font Configuration
To use custom fonts (e.g., NotoSerifJP.ttf
), place the font file in the /public directory, which is automatically created by npx create-remix@latest
.
Next, create a CSS file to configure the font and place it under /app
. Here’s the content of /app/base.css
:
@font-face {
font-family: 'NotoSerifJP';
src: url('/NotoSerifJP.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
body {
font-family: 'NotoSerifJP', sans-serif;
}
Finally, import the CSS in /app/root.tsx
:
import "./tailwind.css";
+ import "./base.css";
Conclusion
Now, you can check the page display at http://localhost:3000
and download the PDF at http://localhost:3000/pdf
. By the way, I also tried React-pdf, but since it required building the PDF from scratch with limited styling options, I decided to go with the current method.
Posted on August 25, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.