DEV Community

ahyaemon
ahyaemon

Posted on

Creating a Remix Server to Return a React Page as a PDF

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

  1. Write the page to be converted to PDF in React.
  2. 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
Enter fullscreen mode Exit fullscreen mode

This will create the project in an interactive format. I opted to use pnpm instead of npm:

> Install dependencies with npm?
> No
Enter fullscreen mode Exit fullscreen mode

After the project is created, install the necessary packages:

pnpm i
Enter fullscreen mode Exit fullscreen mode

Next, start the development server:

pnpm dev
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
  },
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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',
    },
  })
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Finally, import the CSS in /app/root.tsx:

import "./tailwind.css";
+ import "./base.css";
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (1)

Collapse
 
mannuelf profile image
Mannuel

Neat idea