every Tech Blog

株式会社エブリーのTech Blogです。

Nuxt Bridge を使用して Nuxt 2 のアプリケーションへサーバーエンジン Nitro を導入した話

Nuxt Bridge を使用して Nuxt 2 のアプリケーションへサーバーエンジン Nitro を導入した話

はじめに

株式会社エブリーでソフトウェアエンジニアをしている桝村です。

本記事では、Nuxt 3 へのアップデートに向けて、Nuxt Bridge を使用して Nuxt 2 のアプリケーションへサーバーエンジン Nitro を導入したので、実施内容やそれによって得られた知見について紹介します。

この記事のゴールは、以下を想定しています。

  • Nitro の概要や、Nuxt 2 への Nitro 導入のメリットを把握する
  • Nuxt 2 への Nitro 導入における変更点や考慮すべきポイントを把握する

Nuxt 3 へのアップデートに関連して、Vuex の Pinia への移行については、以下の記事で詳しく紹介しています。

tech.every.tv

サーバーエンジン Nitro とは

サーバーエンジン Nitro とは、様々な環境で軽量な Web サーバーを構築できるライブラリのことです。

Vue や Nuxt 開発メンバーが中心のプロジェクト unjs が開発・メンテナンスしており、Nuxt 3 へデフォルトで組み込まれています。

UnJS は Unified JavaScript Tools の略で、JavaScript の開発をより効率的かつ柔軟に行うために設計された、一連のオープンソースライブラリおよびツールを提供しているプロジェクトです。

同様のライブラリとして、Nuxt 2 との互換性がある Express.js や Koa.js, Fastify などがあります。

詳しくは、以下のリンクをご参照ください。

nitro.unjs.io

Nuxt での Nitro の採用

ここでは、Nuxt での Nitro の採用について、概要やメリットを整理します。

Nuxt での Server の構成や役割

Nitro が採用された Nuxt の Server の構成は以下のようになっています。

Nuxt Server Structure
Nuxt の Server 構成

nuxt.com

  • Server Engine: アプリケーションのサーバーを動作させるための基盤となる技術
  • Nuxt: Vue.js や SSR、状態管理 などの機能を提供する高レベルのフレームワーク
  • Nitro: 軽量でポータブルな出力を生成するライブラリ
  • h3: 軽量で高速な HTTP サーバーのライブラリ。Nitro の基盤技術

また、Nuxt の Server 側では以下をはじめとした責務を担っています。

  • サーバーのビルド・起動設定
  • API のルーティング初期化
  • HTTP リクエストの処理
  • 初期 HTML のレンダリング
  • 静的なサーバーサイドコンテンツの生成 (ex. サイトマップ)
  • etc...

上記を踏まえると、Nuxt での Nitro の採用は、大きな変更であることが想定できます。

Nuxt への Nitro 導入のメリット

Nuxt への Nitro 導入には以下のようなメリットがあります。

  • 高速なサーバーレスポンス
  • ハイブリッドレンダリングのサポート
  • ホットリロードが高速

詳しくは、以下のリンクをご参照ください。

nuxt.com

実際の Nitro 導入による効果については、後述の結果と振り返りで紹介しております。

Nuxt 2 への Nitro の導入における変更点

前提

今回は、以下の技術スタックを持つ本番運用中のアプリケーションへの導入を想定しております。

  • Node.js v20.14.0
  • nuxt v2.17.2
  • @nuxt/bridge v3.0.1
  • express v4.17.1

本アプリケーションは、Nuxt Bridge を利用して Nuxt 2 の状態で Nuxt 3 への移行を進めており、今回は移行の一つであるサーバーエンジン Nitro の導入を行いました。

Nuxt Bridge とは、Nuxt 3 と上方互換性があり、Nuxt 3 の機能の一部を Nuxt 2 で利用できるようにするためのライブラリです。

nuxt.com

以降の内容は、公式の移行ガイドを参考にしながらも、実際に対応を進める中でハマったポイントや気づきを中心に紹介していきます。

開発サーバーの起動

Nuxt が Nitro を利用して開発サーバーを起動するには、CLI コマンド nuxi のインストール・利用が必要です。

前提として、nuxi のインストール・利用には、Node.js のバージョン 18.0.0 以上が必要そうでした。

WARN Current version of Node. js (16.18.0) is unsupported and might cause issues.
Please upgrade to a compatible version >= 18.0.0.

その上で、基本的には、以下のリンクを参考に進めていくことができます。

nuxt.com

また、開発サーバーの起動設定について、コマンド nuxt では server オプションを利用していましたが、コマンド nuxi では devServer オプションの利用が必要なのもポイントでした。

export default defineNuxtConfig({
- server: {
+ devServer: {
    port: 3002,
  }
})

nuxt.com

加えて、コマンド nuxt ではファイルの内容をバッファとして読み込んで渡す仕様でしたが、コマンド nuxi ではファイルのパスを文字列として直接渡す仕様に変更になっていました。より設定が簡潔になったと言えるでしょう。

export default defineNuxtConfig({
  devServer: {
    port: 3002,
    https: {
-     key: fs.readFileSync(path.resolve(__dirname, 'server.key')),
-     cert: fs.readFileSync(path.resolve(__dirname, 'server.crt'))
+     key: './server.key',
+     cert: './server.crt'
    }
  }
})

デプロイメント

nuxi では build コマンドを実行することで、.output ディレクトリにアプリケーションのビルド成果物を出力します。この成果物がサーバーを起動するためのエントリーポイントとなります。

また、デフォルトではポート 3000 でサーバーが起動するのと、上述の devServer オプションを参照しないので カスタマイズでポートを変更したい場合は、以下のように環境変数を利用してポートを指定する必要がありました。

PORT=3002 node .output/server/index.mjs

nuxt.com

エンドポイント・ミドルウェアの設定

前提として、Nuxt 2 では express を利用してエンドポイントやミドルウェアを設定していたので、Nitro の導入にあたっては Nitro (h3) の API を利用するように書き換えることが基本的な方針でした。

Express.js から Nitro (h3) へ書き換えする場合

Nitro では h3 の defineEventHandler によりアプリケーションロジックを定義します。

コンテキストにあたる event インスタンスを受け取って、ロジックを実行する関数を定義することができます。

Express.js
// server/api/test.ts
export default (req, res, next) => {
  // ... Do whatever you want here
  next();
}
Nitro (h3)
// server/api/test.ts
import { defineEventHandler } from "h3";

export default defineEventHandler(async (event) => {
  // ... Do whatever you want here
});

nuxt.com

以下は、具体的な書き換え例になります。

Express.js
import urlParse from "url-parse";

export default (req, res, next) => {
  const host = req.headers.host;
  const parsedUrl = new URL(`https://${host}${req.originalUrl}`);
  const pathname = parsedUrl.pathname;

  if (pathname.match(/.+\/$/)) {
    parsedUrl.pathname = pathname.replace(/\/$/, "");
    res.writeHead(301, { Location: urlParse(parsedUrl).href });
    res.end();
  } else {
    next();
  }
};
Nitro (h3)
import { defineEventHandler, sendRedirect, getRequestURL, getRequestHost } from "h3";

export default defineEventHandler((event) => {
  const host = getRequestHost(event);
  const parsedUrl = new URL(getRequestURL(event), `https://${host}`);
  const pathname = parsedUrl.pathname;

  if (pathname.match(/.+\/$/)) {
    parsedUrl.pathname = pathname.replace(/\/$/, "");
    return sendRedirect(event, `https://${host}${parsedUrl.pathname}`, 301);
  }
});

express では、リクエストオブジェクトから直接情報を取得しているのに対して、h3 では getRequestHostgetRequestURL のような関数を使用してリクエストから情報を取得しています。

それにより、関数の抽象化を通じてコードの可読性と保守性を向上させることに重きを置いていたり、Nuxt 3 で設計を刷新しようとしていることが垣間見えます。

Express.js のコードをそのまま利用する場合

express のコードでも、fromNodeMiddleware() で変換することで Nitro (h3) でそのまま利用することができました。

Express.js
// server/api/index.js
import express from "express";
const app = express();

app.get("/api/test", (req, res) => {
  res.send("Hello World!");
});

export default app;
Nitro (h3)
// server-middleware/api/index.js
import express from "express";
import { fromNodeMiddleware } from "h3";

app.get("/api/test", (req, res) => {
  res.send("Hello World!");
});

export default fromNodeMiddleware(app);

これにより、徐々に express から h3 ベースへコードの書き換えを進めることが可能です。

ただし、Nuxt として推奨されている機能ではないことは留意しておくと良さそうです。

nuxt.com

結果と振り返り

結果

Nuxt 3 への Nitro 導入した結果、体感の部分もありますが、今回のアプリケーションでは以下のような効果が得られました。

#・ホットリロード: 約 20% 高速化
#・開発サーバーの起動時間:約 30% 高速化
#・サーバーのレスポンスタイム: 約 5% 高速化

サーバーのレスポンスタイムについて、今回は開発スピードを優先して主に express のコードをそのまま利用する方針で進めたので、モジュールの変換に伴うオーバーヘッドが影響している可能性があります。

なので、Nitro (h3) へ完全移行できるとさらに改善が見込めるかもしれません。

振り返り

サーバーエンジン Nitro の導入に際して、Nuxt のサーバーの基盤技術の刷新でもあり、変更点が多くありました。

一方で、開発サーバーの起動時間やホットリロードの高速化など、開発効率の向上が期待できることもわかりました。

今後は、Nitro (h3) への完全移行を進めることで、更なるパフォーマンス向上や開発効率の向上を図っていきたいと考えています。

おわりに

今回は、Nuxt 3 へのアップデートに向けて、Nuxt Bridge を使用して Nuxt 2 のアプリケーションへサーバーエンジン Nitro を導入したので、実施内容やそれによって得られた知見について紹介しました。

これから Nuxt Bridge を使用して Nuxt 2 のアプリケーションへ Nitro の導入を検討している方にとって、参考になれば幸いです。