Nuxt 4 ライフサイクルフック完全ガイド
before系フック(beforeCreate/onBeforeMount/onBeforeUpdate/onBeforeUnmount等)は除外しています。
Nuxt 4 のコンポーネントは SSR(サーバー) と CSR(クライアント) の 2 つのフェーズで動作します。
各フックがどのタイミング・どのコンテキストで実行されるかを理解することが、パフォーマンスとデータフェッチ最適化の鍵です。
実行コンテキスト凡例
| マーク | 説明 |
|---|---|
| 🟢 クライアント | ブラウザのみで実行 |
| 🟡 サーバー | SSR 時のみ実行 |
| 🔵 両方 | SSR・CSR どちらでも実行 |
実行フロー
SERVER SIDE (SSR) CLIENT SIDE (CSR / Hydration)
───────────────────────── ─────────────────────────────
app:created 🔵 app:created 🔵
↓ ↓
setup() 🔵 setup() 🔵
↓ ↓
app:rendered 🟡 onMounted() 🟢
↓
app:mounted 🟢
↓
page:start 🟢
↓
page:finish 🟢
↓
onUpdated() ← リアクティブ変更
↓
onUnmounted()
Vue 3 ライフサイクルフック(Composition API)
setup() 🔵 両方
コンポーネントライフサイクルの起点。Options API の beforeCreate / created に相当し、SSR・CSR 両方で実行されます。
ref・reactive・computed の定義や、他のフックの登録はここで行います。DOM にはまだアクセスできません。
<script setup>
import { ref, computed } from "vue";
// setup は SSR・CSR 両方で実行される
const count = ref(0);
const double = computed(() => count.value * 2);
</script>
⚠️ SSR でも実行されるため、
windowやdocumentへのアクセスはここでは行わないでください。
onMounted() 🟢 クライアント
コンポーネントの DOM ツリーが作成され、ブラウザへ挿入された後に呼ばれます。
DOM 操作・外部ライブラリの初期化・ブラウザ API へのアクセス に最適なフックです。
<script setup>
import { onMounted } from "vue";
const chartEl = useTemplateRef("chart");
onMounted(() => {
// DOM が確実に存在するため安全に操作できる
initChart(chartEl.value);
});
</script>
<template>
<canvas ref="chart" />
</template>
onUpdated() 🟢 クライアント
リアクティブな状態が変化し、DOM が更新された直後に呼ばれます。更新後の DOM 状態を読み取る必要がある場合に使用します。
<script setup>
import { onUpdated } from "vue";
const listEl = useTemplateRef("list");
onUpdated(() => {
// 追加後のリストにスクロールする例
listEl.value.scrollTo({ top: listEl.value.scrollHeight, behavior: "smooth" });
});
</script>
⚠️ このフック内でリアクティブ状態を変更すると無限ループになります。条件チェックが必要な場合は
watchEffectを検討してください。
onUnmounted() 🟢 クライアント
コンポーネントが DOM から取り外された後に呼ばれます。
タイマー・イベントリスナー・外部サブスクリプション など、コンポーネント外に影響するリソースのクリーンアップに使用します。
// app/composables/useResize.ts
import { onMounted, onUnmounted } from "vue";
export function useResize(callback: () => void) {
onMounted(() => {
window.addEventListener("resize", callback);
});
onUnmounted(() => {
// リスナーを確実に解除してメモリリークを防ぐ
window.removeEventListener("resize", callback);
});
}
onActivated() / onDeactivated() 🟢 クライアント
<KeepAlive> でラップされたコンポーネント専用のフックです。
onActivated: キャッシュから復元されて表示されたときonDeactivated: キャッシュに入ったとき(非表示になったとき)
<script setup>
import { onActivated, onDeactivated } from "vue";
onActivated(() => {
// タブに戻ったタイミングでデータを再フェッチ
refreshData();
});
onDeactivated(() => {
// バックグラウンドになったとき処理を一時停止
pausePolling();
});
</script>
onErrorCaptured() 🔵 両方
子孫コンポーネントから伝播したエラーをキャッチします。false を返すとエラーの伝播を止めます。エラーバウンダリの実装に使用します。
<script setup>
import { ref, onErrorCaptured } from "vue";
const error = (ref < Error) | (null > null);
onErrorCaptured(err => {
error.value = err;
return false; // エラーの伝播を止める
});
</script>
<template>
<div v-if="error">エラー: {{ error.message }}</div>
<slot v-else />
</template>
Nuxt アプリフック(useNuxtApp().hook())
Nuxt 固有のフックは useNuxtApp().hook() で登録します。プラグイン・ミドルウェア・コンポーザブル内から呼び出せます。
app:created 🔵 両方
Nuxt が Vue アプリインスタンスを作成した直後に発火します。Vue プラグインの追加やグローバル設定など、アプリ全体に影響する初期化処理に使用します。
// app/plugins/global.ts
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.hook("app:created", vueApp => {
// Vue アプリへグローバルプロパティを追加
vueApp.config.globalProperties.$format = formatCurrency;
});
});
app:mounted 🟢 クライアント
Vue アプリが DOM へマウントされた後(Hydration 完了後)に発火します。アプリ全体で一度だけ必要な初期化処理に最適です。
// app/plugins/analytics.client.ts
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.hook("app:mounted", () => {
// ブラウザのみで動作する分析ツールを初期化
initAnalytics({ id: "GA-XXXXXXXX" });
});
});
💡 プラグインファイル名に
.client.tsを付けると、そのプラグイン自体がクライアントサイドのみで実行されます。
app:rendered 🟡 サーバー
サーバーサイドレンダリングが完了し、HTML が確定した後に発火します。レスポンスを送出する前の最終処理に使用します。
// app/plugins/ssr-only.server.ts
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.hook("app:rendered", renderContext => {
renderContext.ssrContext!.event.node.res.setHeader(
"X-Rendered-At",
new Date().toISOString()
);
});
});
app:error / app:error:cleared 🔵 両方
app:error: 致命的なエラーが発生したとき(createError()で投げたエラーを含む)app:error:cleared: エラーがクリアされたとき(ユーザーが正常ページに戻ったとき)
// app/plugins/error-reporting.ts
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.hook("app:error", error => {
// Sentry などの外部サービスへ報告
reportToSentry(error);
});
nuxtApp.hook("app:error:cleared", ({ redirect }) => {
redirect("/");
});
});
app:suspense:resolve 🟢 クライアント
すべての非同期データが解決され、ページが表示可能な状態になったタイミングで発火します。ページ遷移のローディングインジケーター制御に適しています。
// app/plugins/loading.client.ts
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.hook("app:suspense:resolve", () => {
useLoadingState().value = false;
});
});
ページ遷移フック 🟢 クライアント
<NuxtPage> コンポーネントによるページ遷移のライフサイクルを制御します。すべてクライアントサイドのみで実行されます。
page:start / page:finish
page:start: ページコンポーネントの解決が始まったときpage:finish: 解決が完了した(または失敗した)とき
// app/plugins/page-loading.client.ts
export default defineNuxtPlugin(nuxtApp => {
const loading = useLoadingIndicator();
nuxtApp.hook("page:start", () => {
loading.start();
});
nuxtApp.hook("page:finish", () => {
loading.finish();
});
});
💡 Nuxt には組み込みの
useLoadingIndicator()コンポーザブルがあります。
page:transition:finish
Vue の <Transition> によるページ遷移アニメーションが完了した後に発火します(onAfterLeave イベント後)。
// app/plugins/scroll.client.ts
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.hook("page:transition:finish", () => {
// アニメーション完了後にページ先頭へスクロール
window.scrollTo({ top: 0, behavior: "instant" });
});
});
フック一覧まとめ
| フック | コンテキスト | 主な用途 |
|---|---|---|
setup() | 🔵 両方 | リアクティブ定義・フック登録 |
onMounted() | 🟢 クライアント | DOM 操作・ライブラリ初期化 |
onUpdated() | 🟢 クライアント | 更新後の DOM 参照 |
onUnmounted() | 🟢 クライアント | リソースのクリーンアップ |
onActivated() | 🟢 クライアント | KeepAlive 復元時 |
onDeactivated() | 🟢 クライアント | KeepAlive キャッシュ時 |
onErrorCaptured() | 🔵 両方 | エラーバウンダリ |
app:created | 🔵 両方 | アプリ全体の初期化 |
app:mounted | 🟢 クライアント | Hydration 完了後の処理 |
app:rendered | 🟡 サーバー | SSR レスポンス送出前の処理 |
app:error | 🔵 両方 | エラー報告 |
app:error:cleared | 🔵 両方 | エラー解消後の処理 |
app:suspense:resolve | 🟢 クライアント | 非同期解決後の処理 |
page:start | 🟢 クライアント | ページ遷移開始 |
page:finish | 🟢 クライアント | ページ遷移完了 |
page:transition:finish | 🟢 クライアント | アニメーション完了後 |