3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Remix] Reactで書いたページをPDFで返すサーバーを作る

Posted at

はじめに

React で書いたページをそのまま PDF として返すサーバーを作ってみようと思いググったのですが、デファクトスタンダードな方法が見つかりませんでした。
そこで、色々試してみて動く形になったものを公開します。
もし他に良い方法があったらコメントで教えてくださると嬉しいです。

やりたいこと

やりたいこととしては、

  1. PDF 化するページを React で記述したい
    1. で作ったページを PDF でダウンロードできるようにしたい

です。
サーバーとして動作するものとし、/ へのアクセスで 1. のページを表示し、/pdf へのアクセスで 2. の PDF をダウンロードできるようにします。

手順と解説

プロジェクトを作成するところから解説します。

1. Remix サーバーの作成

まずはRemix 公式の Quick Startにある通り下記コマンドでプロジェクトを作成します。

npx create-remix@latest

対話形式でプロジェクトを作成します。
途中 npm を使うか聞かれるのですが、私は pnpm を使いたかったので No にしました。

> Install dependencies with npm?
> No

プロジェクトができたら、パッケージをインストールします。

pnpm i

そして開発サーバーを起動。

pnpm dev

これで、http://localhost:5173 にアクセスすれば初期ページが表示されます。

一応ビルドが通って本番サーバーが起動できるかも確認しておきます。

pnpm build
pnpm start

http://localhost:3000 にアクセスで動作確認できます。

2. 細かい設定

開発サーバーも 3000 ポートで起動したかったので、package.jsonscripts を下記のように書き換えました。

  "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"
  },

これで pnpm dev 時も http://localhost:3000 でアクセスできるようになります。

3. PDF を返すエンドポイントを作成

/pdf へアクセスすると、/ の内容を PDF 化したものをレスポンスするようにします。

まず、html を PDF に変換する puppeteer を dependencies に追加します。

pnpm add puppeteer

次に /app/routes/pdf.tsx を追加します。
routes ディレクトリ配下に追加するだけで Remix が検知して /pdf エンドポイントを作成してくれます。

/app/routes/pdf.tsx
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',
    },
  })
}

一旦これで完了です。
開発サーバーか本番サーバーを起動し、http://localhost:3000/pdf にアクセスしてみてください。
http://localhost:3000 にアクセスした時と同じ内容が PDF でダウンロードできたかと思います。

動作としては、http://localhost:3000/pdf にアクセスすると内部で http://localhost:3000 にリクエストを飛ばし、取得した html を puppeteer で PDF 化して返す、という流れになります。

4. フォント設定

自前のフォントファイルを使用したい場合の設定です。
まずフォントファイル(今回は NotoSerifJP.ttf)を /public ディレクトリの下におきます。
/public ディレクトリは、npx create-remix@latest を実行したときに自動で作成されているはずです)

次に、フォントを設定する css ファイルを作成し、/app ディレクトリの下におきます。
今回は /app/base.css とし、下記内容で作成しました。

/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;
}

最後に /app/root.tsx にインポートを追加します。

import "./tailwind.css";
+ import "./base.css";

おわりに

これで、http://localhost:3000 で画面表示を確認しつつ、http://localhost:3000/pdf で PDF をダウンロードできるようになりました。
ちなみに React-pdf も試したのですが、これは独自のタグを使って 1 から PDF を組み立てていくような感じで、設定できる style も限られていたので今回は見送りました

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?