[カスタムサーバ不要] Next.jsでお手軽にSSLを使ったローカル開発をする

2022/07/10

ローカルで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 devnext build && next startの挙動がちょっと違って困ったことあるし、開発環境をSSLにするためだけにそこの懸念が増えるのもなぁっていう。

出来ればserver.jsを書かなくてもnext devnext startだけでhttpsに出来ると嬉しいよね?

そんな時にいい感じのやり方があるから紹介するよ。

Next.jsのバージョン

12.1.6

リバースプロキシを使えば良さそう

今回のやり方を簡単に説明するとこんな感じ。

  1. nextを普通にnext devで立ち上げる
  2. 別でhttps接続可能なリバースプロキシサーバを立てる
  3. 2のリクエストを全部1にバイパスする

開発環境にDocker使ってればnginxと組み合わせて簡単に実現できるから、この構成でdocker composeしてるところも多いんじゃないかな?

でも実際ローカルでSSL環境を実現したいだけなのにDockernginxなどの登場人物が増えてしまうのもちょっと大変だよね。

これをnpmとかNode.jsの世界だけで実現できるようになりたい。

と、その前に。ローカルで使うSSL証明書を作っておくのを忘れずに。

ローカル用のSSL証明書を作る

今回は超簡単にローカル専用の証明書を作れるmkcertを使っていくよ。

mkcert自体のインストールがまだだったらやっておこう。

MacOS + Google ChromeならHomebrewで一発なんだけど、環境が違う場合は他に手順が必要だったりするからGitHubのリポジトリを参考にやってみてね。

mkcertのインストール
brew install mkcert
ローカルCA(認証局)のインストール
mkcert -install

インストールが済んだらプロジェクトのディレクトリに移動してlocalhost用の証明書を生成しよう。

証明書の生成
mkcert localhost

カレントディレクトリにlocalhost-key.pem localhost.pem2種類のファイルが生成されていればOK。

リバースプロキシを書く

証明書が出来たらいよいよリバースプロキシを準備していくよ。

ゆーて単にリクエストをバイパス出来ればいいだけだから自分でちょろっと書いてしまえそうだよね。

node-http-proxyという簡単にリバースプロキシが書けるおあつらえ向きなモジュールがあるから今回はこれを使っていくよ。

node-http-proxyのインストール
npm install http-proxy
reverse-proxy.js
const 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 devhttp://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 -D

http://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.pem

devDependenciesdev-rev-proxyを入れて起動コマンドをpackage.jsonに登録しておけばSSLな環境構築をNode.jsのプロジェクトで完結できて便利!

package.json (一部抜粋)
{
  "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環境作れると超便利で捗るのでかなりおすすめ。

もし使ってみて不具合があったりこういう場合はうまくいかないよとかあったりしたらGitHubissueとかTwitter(@YuhsakInoue)とかで教えてね。