Nuxt ライフサイクル
Nuxt アプリケーションのライフサイクルを理解することで、特にサーバーサイドとクライアントサイドのレンダリングにおいて、フレームワークの動作をより深く理解することができます。
この章の目的は、フレームワークの異なる部分、それらの実行順序、およびそれらがどのように連携するかについての概要を提供することです。
サーバー
サーバーでは、アプリケーションへの初回リクエストごとに次のステップが実行されます。
ステップ 1: Nitro サーバーと Nitro プラグインのセットアップ(1回のみ)
Nuxt は、モダンなサーバーエンジンである Nitro によって動作しています。
Nitro が起動すると、/server/plugins ディレクトリ内のプラグインを初期化して実行します。これらのプラグインは以下を行うことができます:
- アプリケーション全体のエラーをキャプチャして処理する。
- Nitro がシャットダウンする際に実行されるフックを登録する。
- レスポンスの修正など、リクエストライフサイクルイベントのフックを登録する。
Nitro プラグインはサーバー起動時に一度だけ実行されます。サーバーレス環境では、各リクエストのたびにサーバーが起動し、Nitro プラグインも同様に起動します。ただし、これらは待機されません。
ステップ 2: Nitro サーバーミドルウェア
Nitro サーバーを初期化した後、server/middleware/ 内のミドルウェアが各リクエストに対して実行されます。ミドルウェアは、認証、ログ記録、リクエストの変換などのタスクに使用できます。
ミドルウェアから値を返すと、リクエストが終了し、返された値がレスポンスとして送信されます。この動作は、適切なリクエスト処理を確保するために一般的には避けるべきです!
ステップ 3: Nuxt の初期化と Nuxt アプリプラグインの実行
まず、Vue と Nuxt のインスタンスが作成されます。その後、Nuxt はサーバープラグインを実行します。これには以下が含まれます:
- Vue Router や
unheadなどの組み込みプラグイン。 plugins/ディレクトリにあるカスタムプラグイン(例:myPlugin.ts)や.serverサフィックスを持つプラグイン(例:myServerPlugin.server.ts)。
プラグインは特定の順序で実行され、互いに依存関係を持つことがあります。実行順序や並行性を含む詳細については、プラグインのドキュメントを参照してください。
このステップの後、Nuxt は app:created フックを呼び出し、追加のロジックを実行することができます。
ステップ 4: ルートのバリデーション
プラグインの初期化後、ミドルウェアを実行する前に、definePageMeta 関数で定義されている場合、Nuxt は validate メソッドを呼び出します。validate メソッドは同期または非同期であり、動的ルートパラメータのバリデーションによく使用されます。
- パラメータが有効な場合、
validate関数はtrueを返すべきです。 - バリデーションが失敗した場合、リクエストを終了するために
falseまたはstatusCodeやstatusMessageを含むオブジェクトを返すべきです。
詳細については、ルートバリデーションのドキュメントを参照してください。
こちらも参照 getting-started > routing#route-validationステップ 5: Nuxt アプリミドルウェアの実行
ミドルウェアを使用すると、特定のルートに移動する前にコードを実行できます。認証、リダイレクト、ログ記録などのタスクによく使用されます。
Nuxt には、次の3種類のミドルウェアがあります:
- グローバルルートミドルウェア
- 名前付きルートミドルウェア
- 匿名(またはインライン)ルートミドルウェア
Nuxt は、アプリケーションへの初回アクセス時とルートナビゲーションの前に毎回グローバルミドルウェアを自動的に実行します。名前付きおよび匿名ミドルウェアは、対応するページコンポーネントで定義されたページ(ルート)メタのミドルウェアプロパティで指定されたルートでのみ実行されます。
各タイプの詳細と例については、ミドルウェアのドキュメントを参照してください。
サーバー上でのリダイレクトは、Location: ヘッダーがブラウザに送信され、新しい場所への新しいリクエストが行われることになります。この際、アプリケーションの状態はクッキーに保存されない限りリセットされます。
ステップ 6: ページとコンポーネントのレンダリング
このステップでは、Nuxt がページとそのコンポーネントをレンダリングし、useFetch や useAsyncData を使用して必要なデータを取得します。サーバー上では動的な更新や DOM 操作が行われないため、onBeforeMount、onMounted などの Vue ライフサイクルフックは SSR 中には実行されません。
デフォルトでは、Vue はパフォーマンス向上のために SSR 中の依存関係追跡を一時停止します。
サーバーサイドではリアクティビティがありません。これは、Vue SSR がアプリを静的 HTML としてトップダウンでレンダリングするため、すでにレンダリングされたコンテンツを戻って修正することができないためです。
<script> のルートスコープでクリーンアップが必要な副作用を生じるコードは避けるべきです。例えば、setInterval を使用してタイマーを設定することが挙げられます。クライアントサイドのみのコードでは、タイマーを設定し、onBeforeUnmount や onUnmounted で解除することができます。しかし、SSR 中にはアンマウントフックが呼び出されないため、タイマーは永遠に残ります。これを避けるために、副作用のあるコードを onMounted 内に移動してください。
Daniel Roe によるサーバーレンダリングとグローバルステートの説明ビデオを視聴してください。
ステップ 7: HTML 出力の生成
必要なデータがすべて取得され、コンポーネントがレンダリングされた後、Nuxt は unhead の設定とレンダリングされたコンポーネントを組み合わせて完全な HTML ドキュメントを生成します。この HTML と関連データがクライアントに送信され、SSR プロセスが完了します。
Vue アプリケーションを HTML にレンダリングした後、Nuxt は app:rendered フックを呼び出します。
HTML を最終化して送信する前に、Nitro は render:html フックを呼び出します。このフックを使用して、生成された HTML を操作し、追加のスクリプトを挿入したり、メタタグを変更したりできます。
クライアント(ブラウザ)
このライフサイクルの部分は、選択した Nuxt モードに関係なく、ブラウザで完全に実行されます。
ステップ 1: Nuxt の初期化と Nuxt アプリプラグインの実行
このステップはサーバーサイドの実行と似ており、組み込みプラグインとカスタムプラグインの両方が含まれます。
plugins/ ディレクトリ内のカスタムプラグイン(例:myPlugin.ts)や .client サフィックスを持つプラグイン(例:myClientPlugin.client.ts)はクライアントサイドで実行されます。
このステップの後、Nuxt は app:created フックを呼び出し、追加のロジックを実行することができます。
ステップ 2: ルートのバリデーション
このステップはサーバーサイドの実行と同じで、definePageMeta 関数で定義されている場合、validate メソッドが含まれます。
ステップ 3: Nuxt アプリミドルウェアの実行
Nuxt ミドルウェアはサーバーとクライアントの両方で実行されます。特定の環境でコードを実行したい場合は、クライアント用に import.meta.client、サーバー用に import.meta.server を使用して分割することを検討してください。
ステップ 4: Vue アプリケーションのマウントとハイドレーション
app.mount('#__nuxt') を呼び出すことで、Vue アプリケーションが DOM にマウントされます。アプリケーションが SSR または SSG モードを使用している場合、Vue はクライアントサイドアプリケーションをインタラクティブにするためにハイドレーションステップを実行します。ハイドレーション中、Vue はアプリケーションを再作成し(サーバーコンポーネントを除く)、各コンポーネントを対応する DOM ノードにマッチさせ、DOM イベントリスナーをアタッチします。
適切なハイドレーションを確保するためには、サーバーとクライアントのデータの一貫性を維持することが重要です。API リクエストには、useAsyncData、useFetch、または他の SSR フレンドリーなコンポーザブルを使用することをお勧めします。これらの方法は、サーバーサイドで取得したデータがハイドレーション中に再利用され、リクエストの繰り返しを避けることを保証します。新しいリクエストはハイドレーション後にのみトリガーされ、ハイドレーションエラーを防ぎます。
Vue アプリケーションをマウントする前に、Nuxt は app:beforeMount フックを呼び出します。
Vue アプリケーションをマウントした後、Nuxt は app:mounted フックを呼び出します。
ステップ 5: Vue ライフサイクル
サーバーとは異なり、ブラウザでは完全な Vue ライフサイクル が実行されます。
tips
このセクションは公式ドキュメントの翻訳ではなく、本サイト独自の補足記事です。
はじめに:Nuxtライフサイクルを理解するメリット
NuxtはVue.jsをベースにした強力なフレームワークであり、サーバーサイドレンダリング(SSR)や静的サイト生成(SSG)を簡単に実現できます。しかし、Nuxtの内部でどのように処理が進むのか、特にサーバーとクライアントでの動作の違いを理解していないと、思わぬバグやパフォーマンス問題に直面することがあります。
本記事では、Nuxtのライフサイクルの流れを丁寧に解説し、実務でよくあるユースケースや注意点を交えながら、より効果的にNuxtを活用するための知識を提供します。これにより、開発効率の向上やトラブルシューティングの迅速化が期待できます。
まず結論:Nuxtライフサイクルの要点
- Nuxtはサーバーとクライアントで異なるライフサイクルを持ち、処理の流れや実行されるフックが異なる
- サーバー側ではNitroサーバーの初期化、プラグイン実行、ルートバリデーション、ミドルウェア実行、ページレンダリング、HTML生成が順に行われる
- クライアント側ではプラグイン実行、ルートバリデーション、ミドルウェア実行、Vueアプリのマウントとハイドレーションが行われる
- SSR時はVueのマウント前に副作用のあるコードを避けることが重要(例:タイマーの設定など)
- ミドルウェアやプラグインは用途に応じてサーバー専用・クライアント専用を使い分けるべき
- ハイドレーションの失敗を防ぐために、サーバーとクライアントでデータの一貫性を保つことが必須
いつ使うべきか・使わない方がよいケース
使うべきケース
-
SSRやSSGで初期表示を高速化したいとき
Nuxtのサーバーサイド処理により、初回アクセス時に完全なHTMLを生成し、SEOやUXを向上させたい場合に有効です。 -
認証やリダイレクトなどのルート制御を行いたいとき
ミドルウェアを活用して、アクセス制限や条件付きリダイレクトをサーバー・クライアント両方で実装できます。 -
プラグインで共通機能を初期化したいとき
APIクライアントのセットアップやグローバルな状態管理の初期化など、アプリ全体で使う処理をまとめるのに便利です。
避けるべきケース
-
SSR中にクライアント専用の副作用を発生させるコードを実行する場合
例として、setIntervalやwindowオブジェクトを直接操作するコードはSSRでは動作せず、エラーやメモリリークの原因になります。 -
サーバーとクライアントで状態が異なる処理を無理に同期させようとする場合
ハイドレーションエラーやUIの不整合を招くため、データ取得はuseAsyncDataやuseFetchなどSSR対応の仕組みを使うべきです。
実務でよくあるユースケースとサンプルコード
1. 認証チェックをミドルウェアで実装する
ログインしていないユーザーを特定のページにリダイレクトする処理は、サーバー・クライアント両方で動作させる必要があります。
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
const user = useAuthUser()
if (!user.value && to.path !== '/login') {
return navigateTo('/login')
}
})
middlewareディレクトリに置き、ページのdefinePageMetaで指定します。
2. APIクライアントをプラグインで初期化する
共通のAPIクライアントをプラグインでセットアップし、どこからでも利用可能にします。
// plugins/apiClient.ts
export default defineNuxtPlugin(() => {
const apiClient = $fetch.create({
baseURL: 'https://api.example.com'
})
return {
provide: {
apiClient
}
}
})
コンポーネント内でconst { $apiClient } = useNuxtApp()として利用可能です。
3. クライアント専用の処理をプラグインで分離する
ブラウザのAPIを使う処理は.client.tsサフィックスを付けたプラグインに分けます。
// plugins/analytics.client.ts
export default defineNuxtPlugin(() => {
if (window.ga) {
window.ga('send', 'pageview')
}
})
よくある落とし穴・注意点
SSRとCSRの違いによる副作用の問題
SSR中はDOMが存在しないため、onMountedやonBeforeMountなどのVueのクライアント専用ライフサイクルフック内で副作用を扱う必要があります。<script>のルートスコープでタイマーやイベントリスナーを設定すると、SSR時に解除されずメモリリークの原因になります。
ハイドレーションエラーの原因と対策
サーバーでレンダリングしたHTMLとクライアントでの初期状態が異なると、Vueのハイドレーションが失敗します。これを防ぐために、APIデータの取得はuseAsyncDataやuseFetchを使い、サーバーとクライアントで同じデータを共有しましょう。
ミドルウェアの返却値に注意
サーバーミドルウェアで値を返すとリクエストがそこで終了します。意図しないレスポンス送信を防ぐため、ミドルウェアは基本的に副作用のみを行い、値は返さない設計が望ましいです。
まとめ
Nuxtのライフサイクルを正しく理解することは、SSRやCSRの違いを踏まえた適切なコード設計に不可欠です。プラグインやミドルウェアの使い分け、データ取得の方法、Vueのライフサイクルフックの特性を押さえることで、パフォーマンスの高い安定したアプリケーションを構築できます。実務でのユースケースを参考にしつつ、落とし穴を避けて効率的に開発を進めましょう。
※このページは Nuxt.js 公式ドキュメントの翻訳ページです。
公式ドキュメントの該当ページはこちら:
https://nuxt.com/docs/3.x/guide/concepts/nuxt-lifecycle