🔫

Next.js のローカル開発時に https を使う

2020/12/09に公開
4

この記事は YAMAP エンジニア Advent Calendar 2020 の 10日目の記事となります。

https://qiita.com/advent-calendar/2020/yamap-engginers

はじめに

弊社のプロダクトの中のひとつに Next.js フレームワーク(以下 Next.js)を用いて実装しているプロダクトがあります。
このプロダクトで SNS 認証を実装する際、ローカル開発時に HTTPS を使いたかったため、これを調査しました。

本記事ではこの Next.js のローカル開発時に HTTPS + localhost、つまり https://localhost:3000(port はなんでもよい)で開発をするための Tips を紹介いたします。

環境

  • Mac OS 10.15.7 Catalina

本題

Certificates を生成する

下記のコマンドをターミナルに貼り付けてエンターキーを押します。

openssl req -x509 -out localhost.crt -keyout localhost.key \
  -newkey rsa:2048 -nodes -sha256 \
  -subj '/CN=localhost' -extensions EXT -config <( \
   printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")

これで ~/ 直下に以下の2ファイルが生成されます。

  • localhost.key
  • localhost.crt

注意点として、シェルに fish を使用している場合、上記の文字列をそのまま使うと文法エラーになります。これは、fish が bash や zsh とは少しだけ文法が異なるためです。

なので、zsh か bash に切り替えてコマンドを実行するとよいでしょう(筆者は面倒くさがりなのでそうしました)。

生成したファイルを Next.js のプロジェクトへ移動する

後述しますが、生成されたファイルは next.config.js から fs.readFileSync などで読むため、ファイルをプロジェクト内に置くようにします。

$ mkdir ~/{YOUR_PROJECT}/certificates
$ mv ~/localhost.key ~ ~/{YOUR_PROJECT}/certificates/localhost.key
$ mv ~/localhost.crt ~ ~/{YOUR_PROJECT}/certificates/localhost.crt

Next.js のカスタムサーバを使う

通常、Next.js で開発するにあたって別途サーバのコードを書く必要はありません。いわゆる zero config ですね。

ただ、今回やりたいことを実現するには Next.js の Custom Server を使わなくてはなりません。注意点としてはこれを利用することで、一部パフォーマンス最適化の恩恵を受けれなくなってしまうようです。

A custom server will remove important performance optimizations, like serverless functions and Automatic Static Optimization.

カスタムサーバを使うには、まずプロジェクトディレクトリに server.js を作成します。

$ touch ~/{YOUR_PROJECT}/server.js

server.js はこんな感じ。

const express = require('express');
const next = require('next');

const port = parseInt(process.env.PORT || '3000');
const host = '0.0.0.0';

const app = next({
  dev: process.env.NODE_ENV !== 'production',
});
const handle = app.getRequestHandler();

(async () => {
  await app.prepare();
  const server = express();
  server.get('*', (req, res) => handle(req, res));
  server.listen(port, host);
  console.log(`> Ready on http://localhost:${port}`);
})();

そして、package.json も修正します。

{
  "scripts": {
-   "dev": "next dev",
+   "dev": "node ./server.js",
-   "start": "next start",
+   "start": "NODE_ENV=production node ./server.js",
    "build": "next build"
  }
}

Express + https モジュールで HTTPS サーバーを立てる

上記の server.js のままでは http://localhost:3000 でサーバーが立ち上がるため、HTTPS を利用するにあたって以下のように server.js を修正します。

const express = require('express');
const next = require('next');
const https = require('https');
const fs = require('fs');

const port = parseInt(process.env.PORT || '4300');
const host = '0.0.0.0';

const app = next({
  dev: process.env.NODE_ENV !== 'production',
});
const handle = app.getRequestHandler();

(async () => {
  await app.prepare();
  const expressApp = express();

  expressApp.get('*', (req, res) => handle(req, res));

  // Use HTTPS if HTTPS option enabled
  const hasCertificates =
    fs.existsSync('./certificates/localhost.key') &&
    fs.existsSync('./certificates/localhost.crt');
  const useHttps =
    process.env.HTTPS === 'true' &&
    hasCertificates;

  if (useHttps) {
    const options = {
      key: fs.readFileSync('./certificates/localhost.key'),
      cert: fs.readFileSync('./certificates/localhost.crt'),
    };
    const server = https.createServer(options, expressApp);
    server.listen(port, host);
    console.log(`> Ready on https://localhost:${port}`);
  } else {
    expressApp.listen(port, host);
    console.log(`> Ready on http://localhost:${port}`);
  }
})();

package.json はこうなります。

{
  "dev": "node ./server.js"
+ "dev:https": "HTTPS=true node ./server.js"
  "start": "NODE_ENV=production node ./server.js"
  "build": "next build"
}

おわりに

以上です。
少しでもみなさまの参考になれば幸いです。お読み頂きありがとうございました。

参考URL

Discussion

nitakingnitaking

参考にさせていただきました!

コピペしたときに一部修正が必要だったのでコメントしておきます・・・!
👇

(async () => {
  await app.prepare();
  const server = express();
  server.get('*', (req, res) => handle(req, res));
  server.listen(port, host);
  console.log(`> Ready on http://localhost:${port}`);
-  }
})();

melodyclue_routermelodyclue_router

単純に ngrok を使う場合と何か違いってありますか?

suzukisotasuzukisota

@melodyclue さん
コメントありがとうございます。

なるほど、ngrok というツールがあるのですね。
調べてみたところ、ngrok を使えばこの記事で書いてあるようなややこしい設定は不要そうです。ローカル環境で立ち上がっているサーバを HTTPS へフォーワードして動作させることも可能のようなので。

ローカルサーバの一時的な外部公開用途でも便利そうなので、必要があれば今度使ってみます。