ローカルでhttpsな環境作りたくなる時あるよね。
ソーシャル系など外部連携の実装をしたりセキュアな環境でしか動かないブラウザのAPIを使ったりとか。
Next.js使ってる時ってみんなどうしてる?server.js書いてる?
server.js書きたくない問題
Next.jsでもserver.jsとか書いてサーバ部分を自前で用意すれば普通に実現できるんだけど、公式ドキュメントにはカスタムサーバを使うと重要なパフォーマンス最適化が無効化されるから本当に必要な時だけにしてねって不穏なことが書かれてて。
Before deciding to use a custom server, please keep in mind that it should only be used when the integrated router of Next.js can’t meet your app requirements. A custom server will remove important performance optimizations, like serverless functions and Automatic Static Optimization.
だからカスタムサーバを使うのは開発時だけで本番ではvercelにお任せもしくはnext startになると思うんだけど、そうするとそこの差分で動作に差異があったらハマりそうじゃない?
こういう内部で色々やってくれてるお任せ系フレームワークで脱出ハッチ的な機構を利用するとその後は自己責任的な感じになるから、やっていくぞ!っていう覚悟が要るんだよね…
実際next devとnext build && next startの挙動がちょっと違って困ったことあるし、開発環境をSSLにするためだけにそこの懸念が増えるのもなぁっていう。
出来ればserver.jsを書かなくてもnext devやnext startだけでhttpsに出来ると嬉しいよね?
そんな時にいい感じのやり方があるから紹介するよ。
Next.jsのバージョン
12.1.6
リバースプロキシを使えば良さそう
今回のやり方を簡単に説明するとこんな感じ。
- nextを普通にnext devで立ち上げる
- 別でhttps接続可能なリバースプロキシサーバを立てる
- 2のリクエストを全部1にバイパスする
開発環境にDocker使ってればnginxと組み合わせて簡単に実現できるから、この構成でdocker composeしてるところも多いんじゃないかな?
でも実際ローカルでSSL環境を実現したいだけなのにDockerやnginxなどの登場人物が増えてしまうのもちょっと大変だよね。
これをnpmとかNode.jsの世界だけで実現できるようになりたい。
と、その前に。ローカルで使うSSL証明書を作っておくのを忘れずに。
ローカル用のSSL証明書を作る
今回は超簡単にローカル専用の証明書を作れるmkcertを使っていくよ。
mkcert自体のインストールがまだだったらやっておこう。
MacOS + Google ChromeならHomebrewで一発なんだけど、環境が違う場合は他に手順が必要だったりするからGitHubのリポジトリを参考にやってみてね。
brew install mkcertmkcert -installインストールが済んだらプロジェクトのディレクトリに移動してlocalhost用の証明書を生成しよう。
mkcert localhostカレントディレクトリにlocalhost-key.pem localhost.pem2種類のファイルが生成されていればOK。
リバースプロキシを書く
証明書が出来たらいよいよリバースプロキシを準備していくよ。
ゆーて単にリクエストをバイパス出来ればいいだけだから自分でちょろっと書いてしまえそうだよね。
node-http-proxyという簡単にリバースプロキシが書けるおあつらえ向きなモジュールがあるから今回はこれを使っていくよ。
npm install http-proxyconst fs = require('fs')
const https = require('https')
const httpProxy = require('http-proxy')
const proxy = httpProxy.createProxyServer({
  target: {
    host: '0.0.0.0',
    port: 3000,
  },
})
const web = (req, res) => {
  proxy.web(req, res)
}
const ws = (req, socket, head) => {
  proxy.ws(req, socket, head)
}
const server = https.createServer(
  {
    key: fs.readFileSync('localhost-key.pem'),
    cert: fs.readFileSync('localhost.pem'),
  },
  web,
)
server.on('upgrade', ws)
server.listen(3100, 'localhost', () => {
  console.log(
    `proxy server has started listening on https://localhost:3100, forwarding to http://0.0.0.0:3000`,
  )
})next devでhttp://0.0.0.0:3000に開発サーバが立っている時にnode reverse-proxy.jsでこのスクリプトを起動しよう。
そうするとhttps://localhost:3100でnextの開発サーバにアクセスできる!簡単だね。
WebSocketもバイパスするようにしてあるからもちろんFast Refresh系もちゃんと動くし、next devに限らずnext startした時でも同様にアクセスできる。
node-http-proxy
https://github.com/http-party/node-http-proxy
このモジュール、簡単にリバースプロキシが書けて超便利なんだけど2018年4月のアップデートを最後にそれ以降放置されてるっぽいのがちょっと不安ではある…。
実際中でやってることはかなりシンプルだしモジュール自体の依存も少ないので今回みたく開発用途でちょこっと使うぐらいなら問題ないと思うけど、プロダクション環境で使うのはやめておいた方が良さそう。
出来上がったのがこちら
というわけで上記のスクリプトをベースにエラーハンドリングを追加したりコマンドラインで各種オプションの指定を出来るようにしたものをnpmに登録しておいたよ。
その名もdev-rev-proxy。npmのURLはこちら。
使い方はほぼ同じ。
npm install dev-rev-proxy -Dhttp://0.0.0.0:3000で起動してる開発サーバにhttps://localhost:3100でアクセスしたい時はこんな感じ。
npx dev-rev-proxy \
  --host localhost \
  --port 3100 \
  --target-host 0.0.0.0 \
  --target-port 3000 \
  --cert localhost.pem \
  --key localhost-key.pemdevDependenciesにdev-rev-proxyを入れて起動コマンドをpackage.jsonに登録しておけばSSLな環境構築をNode.jsのプロジェクトで完結できて便利!
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "proxy": "dev-rev-proxy -h localhost -p 3100 -c localhost.pem -k localhost-key.pem -H 0.0.0.0 -P 3000"
  },
  "devDependencies": {
    "dev-rev-proxy": "^0.1.0"
  }
}あとはmkcertでの証明書生成のやり方をREADME.mdにでも書いておけば完璧だね。
自分は実際にこの構成で業務を始めて何ヶ月か経つんだけど今のところ全く問題なくNext.jsでWeb開発を進められているよ。
やっぱりカスタムサーバもDockerも不要でコマンド一発でSSL環境作れると超便利で捗るのでかなりおすすめ。
もし使ってみて不具合があったりこういう場合はうまくいかないよとかあったりしたらGitHubのissueとかTwitter(@YuhsakInoue)とかで教えてね。