はじめに
React で書いたページをそのまま PDF として返すサーバーを作ってみようと思いググったのですが、デファクトスタンダードな方法が見つかりませんでした。
そこで、色々試してみて動く形になったものを公開します。
もし他に良い方法があったらコメントで教えてくださると嬉しいです。
やりたいこと
やりたいこととしては、
- PDF 化するページを React で記述したい
-
- で作ったページを 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.json
の scripts
を下記のように書き換えました。
"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
エンドポイントを作成してくれます。
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
とし、下記内容で作成しました。
@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 も限られていたので今回は見送りました