はじめに
株式会社エブリーでソフトウェアエンジニアをしている桝村です。
本記事では、Nuxt 3 へのアップデートに向けて、Nuxt Bridge を使用して Nuxt 2 のアプリケーションへサーバーエンジン Nitro を導入したので、実施内容やそれによって得られた知見について紹介します。
この記事のゴールは、以下を想定しています。
- Nitro の概要や、Nuxt 2 への Nitro 導入のメリットを把握する
- Nuxt 2 への Nitro 導入における変更点や考慮すべきポイントを把握する
Nuxt 3 へのアップデートに関連して、Vuex の Pinia への移行については、以下の記事で詳しく紹介しています。
サーバーエンジン Nitro とは
サーバーエンジン Nitro とは、様々な環境で軽量な Web サーバーを構築できるライブラリのことです。
Vue や Nuxt 開発メンバーが中心のプロジェクト unjs が開発・メンテナンスしており、Nuxt 3 へデフォルトで組み込まれています。
UnJS は Unified JavaScript Tools の略で、JavaScript の開発をより効率的かつ柔軟に行うために設計された、一連のオープンソースライブラリおよびツールを提供しているプロジェクトです。
同様のライブラリとして、Nuxt 2 との互換性がある Express.js や Koa.js, Fastify などがあります。
詳しくは、以下のリンクをご参照ください。
Nuxt での Nitro の採用
ここでは、Nuxt での Nitro の採用について、概要やメリットを整理します。
Nuxt での Server の構成や役割
Nitro が採用された Nuxt の Server の構成は以下のようになっています。
- Server Engine: アプリケーションのサーバーを動作させるための基盤となる技術
- Nuxt: Vue.js や SSR、状態管理 などの機能を提供する高レベルのフレームワーク
- Nitro: 軽量でポータブルな出力を生成するライブラリ
- h3: 軽量で高速な HTTP サーバーのライブラリ。Nitro の基盤技術
また、Nuxt の Server 側では以下をはじめとした責務を担っています。
- サーバーのビルド・起動設定
- API のルーティング初期化
- HTTP リクエストの処理
- 初期 HTML のレンダリング
- 静的なサーバーサイドコンテンツの生成 (ex. サイトマップ)
- etc...
上記を踏まえると、Nuxt での Nitro の採用は、大きな変更であることが想定できます。
Nuxt への Nitro 導入のメリット
Nuxt への Nitro 導入には以下のようなメリットがあります。
- 高速なサーバーレスポンス
- ハイブリッドレンダリングのサポート
- ホットリロードが高速
詳しくは、以下のリンクをご参照ください。
実際の 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 が 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
では server オプションを利用していましたが、コマンド nuxi
では devServer
オプションの利用が必要なのもポイントでした。
export default defineNuxtConfig({ - server: { + devServer: { port: 3002, } })
加えて、コマンド 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 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 });
以下は、具体的な書き換え例になります。
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 では getRequestHost
や getRequestURL
のような関数を使用してリクエストから情報を取得しています。
それにより、関数の抽象化を通じてコードの可読性と保守性を向上させることに重きを置いていたり、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 3 への Nitro 導入した結果、体感の部分もありますが、今回のアプリケーションでは以下のような効果が得られました。
#・ホットリロード: 約 20% 高速化 #・開発サーバーの起動時間:約 30% 高速化 #・サーバーのレスポンスタイム: 約 5% 高速化
サーバーのレスポンスタイムについて、今回は開発スピードを優先して主に express のコードをそのまま利用する方針で進めたので、モジュールの変換に伴うオーバーヘッドが影響している可能性があります。
なので、Nitro (h3) へ完全移行できるとさらに改善が見込めるかもしれません。
振り返り
サーバーエンジン Nitro の導入に際して、Nuxt のサーバーの基盤技術の刷新でもあり、変更点が多くありました。
一方で、開発サーバーの起動時間やホットリロードの高速化など、開発効率の向上が期待できることもわかりました。
今後は、Nitro (h3) への完全移行を進めることで、更なるパフォーマンス向上や開発効率の向上を図っていきたいと考えています。
おわりに
今回は、Nuxt 3 へのアップデートに向けて、Nuxt Bridge を使用して Nuxt 2 のアプリケーションへサーバーエンジン Nitro を導入したので、実施内容やそれによって得られた知見について紹介しました。
これから Nuxt Bridge を使用して Nuxt 2 のアプリケーションへ Nitro の導入を検討している方にとって、参考になれば幸いです。