Next.js+TypeScriptでマルチプロセス対応カスタムサーバ作成
- リポジトリ
https://github.com/SoraKumo001/nextjs-custom - 原文リンク
https://ttis.croud.jp/?uuid=46c0f2f8-7db3-4ec4-ab86-5054aea70f49
カスタムサーバ
Next.jsはWebServer機能を標準で内蔵していますが、マルチプロセスや特殊なセッション処理などを組み込む場合には、カスタムサーバという形でWebServer部分を自分で実装する必要があります。
公式にサンプルはある物の、以外に日本語の情報が少ない、それどころかマルチプロセスやfastifyでの実装記事は皆無だったので、書いていきたいと思います。
マルチプロセス化について
Next.jsを動かしているNode.jsは基本的にシングルスレッドで動作します。シングルスレッドといってもI/Oアクセスに関しては非同期で行われているため、無駄なブロックは起こらず、実用的な速度で動作することが可能です。
ところが計算処理などをしている間は当然他の仕事は出来ません。マルチコアCPUなどでハードウエア的に余裕があっても、シングルスレッドである限りはせっかくのリソースが活用できないのです。
これに対処するにはNext.jsをマルチスレッドではなく、マルチプロセス化するのが有効な手段となります。ありがたいことにNode.jsには、マルチプロセス化を簡単に実装するライブラリが標準提供されているので、カスタムサーバ化のコードを少し書くだけで、その恩恵を受けることが出来ます。
Fastifyに関して
Node.jsでWebServer機能を実装するフレームワークとして有名なのはExpressです。しかし古い実装を引きずっているため、応答速度が遅いといわれています。今回はベンチマークで上位に位置するFastifyを使ってカスタムサーバを作ります。
インストールが必要な最低限のパッケージ
yarn add cross-env fastify next react react-dom
yarn add -D @types/node @types/react @types/react-dom ts-node-dev typescript
カスタムサーバの実装コード
以下の二つのファイルを用意します。
ちなみに環境変数でINDEXというのを子プロセスに渡していますが、workerにIDが振られるので実は無くてもかまいません
import next from "next";
import * as os from "os";
import * as cluster from "cluster";
import { parse } from "url";
import fastify from "fastify";
const dev = process.env.NODE_ENV !== "production";
const clusterSize = Math.min(os.cpus().length, 4);
const portNumber = 3000;
if (cluster.isMaster) {
for (let i = 0; i < clusterSize; i++) cluster.fork({ INDEX: i });
} else {
const app = next({ dev });
const handle = app.getRequestHandler();
const server = fastify();
app.prepare().then(() => {
server.all("*", (req, res) => {
return handle(req.raw, res.raw, parse(req.url, true));
});
server.listen(portNumber).then(() => {
console.log(`[${process.env.INDEX}]:http://localhost:${portNumber}`);
});
});
}
{
"compilerOptions": {
"module": "commonjs",
"outDir": "../.next",
"esModuleInterop": true
}
}
tsconfig.jsonを作成しているのは、Next.js管理下のpagesファイルなどとはTypeScriptのビルドの扱いが異なるからです。
スクリプト関係
devはカスタムサーバ自体の自動リロードのため、ts-node-devを使っています。ただし.nextの中身はNext.js側が調整するので、無視指定が必要です。
{
"name": "nextjs-custom",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"dev": "ts-node-dev --ignore-watch \\.next -P server/tsconfig.json server/index.ts",
"build": "tsc -b server && next build",
"start": "cross-env NODE_ENV=production node .next/index.js",
"export": "next export"
},
"devDependencies": {
"@types/node": "^14.14.22",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"ts-node-dev": "^1.1.1",
"typescript": "^4.1.3"
},
"dependencies": {
"cross-env": "^7.0.3",
"fastify": "^3.11.0",
"next": "^10.0.5",
"react": "^17.0.1",
"react-dom": "^17.0.1"
}
}
まとめ
大したコード量も必要なくカスタムサーバが実装できました。マルチプロセスとFastifyのパワーによって、きっと快適SSRライフが送れることでしょう。
ただしベンチマークを取った結果、それなりの負荷をかけても効果が顕著に出るのは2プロセスまでというオチでした。ベンチマークに関しては別記事を書く予定です。