【Nuxt 3 vs Nuxt 4】useAsyncDataの進化について
はじめに
2025年7月16日にNuxt 4がリリースされました。
今回はNuxt 4でパフォーマンスが改善されたuseAsyncDataについて、フォーカスしていきたいと思います。
公式ドキュメントや関連記事を調べて、自身が理解しやすいように例と合わせてメモ書きしてみました。
※引用内の日本語は Google 翻訳で訳したものです。
useAsyncDataとは
Nuxt公式には以下のように書かれています。
useAsyncData is a composable meant to be called directly in the Nuxt context. It returns reactive composables and handles adding responses to the Nuxt payload so they can be passed from server to client without re-fetching the data on client side when the page hydrates.
useAsyncDataはNuxtコンテキスト内で直接呼び出されるコンポーザブルです。
リアクティブコンポーザブルを返し、Nuxtペイロードへのレスポンスの追加を処理します。
これにより、ページのハイドレート時にクライアント側でデータを再取得することなく、サーバーからクライアントにレスポンスを渡すことができます。
https://nuxt.com/docs/4.x/api/composables/use-async-data
つまり、サーバーからデータを取得し、ページにあらかじめセットしてくれる機能です。
また、こちらの記事によると、今回の更新で特に注目すべき点はいくつかあります。
下記、実際にNuxt 3とどう違うか、例を交えて、確認していきたいと思います。
Nuxt 4のuseAsyncDataアップデート
1、shallowRefによる高速レンダリング
New Optimization: shallowRef for data
From Nuxt 4 (and optionally in compatible Nuxt 3), data is now a shallowRef rather than a a deep reactive ref. This avoids high overhead on nested data structures, boosting performance dramatically.
Nuxt 4(および互換性のあるNuxt 3ではオプション)から、deep reactive ではなく data になりました。
これにより、ネストされたデータ構造における高いオーバーヘッドが回避され、パフォーマンスが劇的に向上します。
https://www.debugbear.com/blog/nuxt-useasyncdata#new-optimization-shallowref-for-data
Nuxt 3
const { data, pending } = await useAsyncData('posts', () =>
$fetch('/api/posts')
)
dataはdeep reactive(深いリアクティブ)で管理されます。
Nuxt 4
// デフォルト(shallowRef)
const { data } = await useAsyncData('posts', () =>
$fetch('/api/posts')
)
// deep reactive が必要な場合
const { data } = await useAsyncData('posts', () =>
$fetch('/api/posts'), { deep: true }
)
dataはshallowRef(浅いリアクティブ)で管理されます。
データの深い部分までVueが監視しないので描画が速くなります。
もし「深いreactiveが必要」な場合はオプションでdeep: trueに設定可能です。
2、watch / lazy / key/ serverオプション設定
Usage of key, watch, lazy, and server options
We can pass various options to the useAsyncData composable to make it more performant.Here we are fetching data from the API endpoint but we are also passing in some extra options:
watch makes sure to handle parameter changes and refetch data
lazy when set to false it makes sure to not block the client side navigation waiting for the request to resolve
server when set to false lets us instruct the composable to fetch data only on the client side
パフォーマンスを向上させるために、さまざまなオプションを useAsyncData composable に渡すことができます。
ここでは、API エンドポイントからデータを取得していますが、いくつかの追加オプションも渡しています。
watchパラメータの変更とデータの再取得を確実に処理します。
lazy設定すると、falseリクエストの解決を待つクライアント側のナビゲーションをブロックしないようにします。
serverに設定すると、falseコンポーザブルにクライアント側でのみデータを取得するように指示できます。
https://www.debugbear.com/blog/nuxt-useasyncdata#usage-of-key-watch-lazy-and-server-options
const { data } = await useAsyncData(
`item-${id.value}`, // ← key(指定なしの場合は自動生成)
() => $fetch(`/api/items/${id.value}`), // ← 実際のデータ取得処理
{
watch: [id], // id が変わったら自動でデータを再取得する
lazy: false, // ページ遷移をブロックせず裏でデータを取る
server: true // SSR(サーバー側)でもデータを取得する
}
)
key
取得データの名前兼キャッシュの識別子です。
watch
監視対象のrefが変化すると、自動で再フェッチされます。
lazy
falseにすると、ページが遷移した直後にAPI呼び出しを裏で実行します。
trueの場合は、コンポーネントが表示されるまでAPI呼び出しを待ちます。
server
trueの場合は、サーバーが先にデータを準備してHTMLに埋め込んで送ります。
ページを開いた瞬間にコンテンツが表示され、ブラウザで再取得する必要なしです。
falseにすると、ページが開いたらブラウザがAPIにアクセスしてデータを取得します。
初期表示は空で、取得後に描画されます。
例えば:パラメータ監視の違い
Nuxt 3
const page = ref(1)
const { data: posts, refresh } = await useAsyncData('posts', () =>
$fetch('/api/posts', { params: { page: page.value } })
)
watch(page, () => refresh())
watchを自分で書いて、pageが変わったらrefresh()を呼んで再取得します。
Nuxt 4
const page = ref(1)
const { data: posts } = await useAsyncData(
'posts',
() => $fetch('/api/posts', { params: { page: page.value } }),
{ watch: [page] }
)
Nuxt 4では、watch: [page]と書くだけで、pageが変わるたびに自動で再取得されます。
watch オプションで完結し、コードがシンプルです。
3、カスタムキャッシュ(getCachedData)
Custom Client Caching with getCachedData
getCachedData is an option available in Nuxt's data fetching composables like useFetch and useAsyncData. It allows you to control how and when cached data is returned instead of triggering a new fetch request.
The getCachedData option is used primarily to avoid unnecessary network requests by reusing data already fetched and stored in the payload or static data:
カスタムクライアントキャッシュ
getCachedDataは、Nuxtのデータ取得コンポーザブルで利用できるオプションです。
これにより、新しい取得リクエストをトリガーする代わりに、キャッシュされたデータを返す方法とタイミングを制御できます。
このgetCachedDataオプションは主に、すでに取得されてペイロードまたは静的データに保存されているデータを再利用することで、不要なネットワーク要求を回避するために使用されます。
https://www.debugbear.com/blog/nuxt-useasyncdata#custom-client-caching-with-getcacheddata
const { data: posts } = await useAsyncData(
"posts",
() => $fetch("/api/posts"),
{
getCachedData(key, ctx) {
// まず SSR ペイロードを優先
if (ctx.nuxtApp.payload.data[key]) return ctx.nuxtApp.payload.data[key]
// 次に静的データを確認(静的サイト生成時など)
if (ctx.nuxtApp.static.data[key]) return ctx.nuxtApp.static.data[key]
// どちらもなければ null を返して fetch 実行
return null
}
}
)
getCachedDataは、「すでに取得したデータを再利用するか、新しくAPIを叩くか」を自分で制御できる仕組みです。
ページを行き来すると、同じAPIを何度も呼びがちですが、カスタマイズしてキャッシュを返すのがgetCachedDataです。
(「すでにサーバーで取得してHTMLに埋め込まれているデータ(SSRペイロード)」や「静的データ」があれば、再取得せずに使えます。)
先ほどのkeyオプション(キャッシュの名前)を使います。
複数のコンポーネントやページで同じデータを共有したい場合は、keyに同じ名前を付けます。
この設定をすると、同じkeyのデータはすでに取得済みのものを優先して使うため、API呼び出しが減ります。
パフォーマンス比較: Nuxt 3 vs Nuxt 4
いくつか更新された内容を紹介してきたが、実際にGoogle ChromeのDeveloper Toolsを使って、具体的にパフォーマンスの違いを見ていきたいと思います。
共通API
150,000件程度で重いネストデータを用意します。
(10,000件のpost x 各5件のcomment x 各3件のreply)
export default defineEventHandler(() => {
// 例:大量データを生成して返す
return Array.from({ length: 10000 }, (_, postIndex) => ({
id: postIndex,
title: `Post ${postIndex}`,
comments: Array.from({ length: 5 }, (_, commentIndex) => ({
text: `Comment ${commentIndex} of Post ${postIndex}`,
replies: Array.from({ length: 3 }, (_, replyIndex) => ({
text: `Reply ${replyIndex} of Comment ${commentIndex} in Post ${postIndex}`,
})),
})),
}));
});
Nuxt 3の例
<script setup lang="ts">
const page = ref(1) // 現在のページ番号
// useAsyncDataで非同期データを取得
const { data: posts, refresh } = await useAsyncData(
'posts', // キャッシュキー
() => $fetch('/api/posts', { params: { page: page.value } }) // APIリクエスト
)
// pageが変わったら refresh() を呼んで再取得
watch(page, () => refresh())
// ページ遷移用の関数
function previous() { if (page.value > 1) page.value-- }
function next() { page.value++ }
</script>
<template>
<div>
<h2>Nuxt 3 Sample</h2>
<!-- 投稿一覧を表示 -->
<ul>
<li v-for="post in posts" :key="post.id">
{{ post.title }}
<!-- comments -->
<ul>
<li v-for="(comment, commentIndex) in post.comments" :key="commentIndex">
{{ comment.text }}
<!-- replies -->
<ul>
<li v-for="(reply, replyIndex) in comment.replies" :key="replyIndex">
{{ reply.text }}
</li>
</ul>
</li>
</ul>
</li>
</ul>
<!-- ページ切り替えボタン -->
<button @click="previous">Previous</button>
<button @click="next">Next</button>
</div>
</template>
Nuxt 4の例
<script setup lang="ts">
const page = ref(1) // 現在のページ番号
// useAsyncDataで非同期データを取得
const { data: posts } = await useAsyncData(
'posts', // キャッシュキー
() => $fetch('/api/posts', { params: { page: page.value } }), // APIリクエスト
{
watch: [page], // pageが変わったら自動で再フェッチ
refreshOnWindowFocus: true // タブ切り替え時に自動で再取得
}
)
// ページ遷移用の関数
function previous() { if (page.value > 1) page.value-- }
function next() { page.value++ }
</script>
<template>
<div>
<h2>Nuxt 4 Sample</h2>
<!-- 投稿一覧を表示 -->
<ul>
<li v-for="post in posts" :key="post.id">
{{ post.title }}
<!-- comments -->
<ul>
<li v-for="(comment, commentIndex) in post.comments" :key="commentIndex">
{{ comment.text }}
<!-- replies -->
<ul>
<li v-for="(reply, replyIndex) in comment.replies" :key="replyIndex">
{{ reply.text }}
</li>
</ul>
</li>
</ul>
</li>
</ul>
<!-- ページ切り替えボタン -->
<button @click="previous">Previous</button>
<button @click="next">Next</button>
<!-- タブ切り替え時の自動更新についての注意書き -->
<p>※タブを切り替えると自動で最新データに更新されます</p>
</div>
</template>
結果
Google ChromeのDeveloper Tools>NetworkタブでHTMLを確認し、初期ロード速度(SSR)の違いは下記通りです。
見ている通り、初期ロード速度(SSR)だけでなく、LCP(最大コンテンツ描画時間)や INP(応答性指標)もNuxt 4のパフォーマンスが優れていることが確認されます。
Nuxt 3


Nuxt 4


まとめ
-
shallowRef デフォルト化
ネストが深いデータでも速く描画でき、遅延を大幅に軽減します。 -
オプション強化(watch / lazy / server / key)
よく書いていたwatch+refreshの定型処理が不要になり、コードがシンプルになります。 -
カスタムキャッシュ(getCachedData)
「すでに取得したデータを再利用するか、新しくAPIを叩くか」を自分で制御できます。
実際に100,000件以上のデータを扱ったテストでは、Nuxt 4の方がLCP(最大コンテンツ描画時間)や INP(応答性指標)が改善されていることも確認できました。
あとがき
Nuxt 3からNuxt 4へのアップデートは、ぱっと見は「マイナー改善」のように見えるかもしれません。
ですが、実際に大量データを扱うと、しっかりメリットを感じられると思います。
特にwatchオプションやshallowRefの導入などで、コードのわかりやすさとパフォーマンスの両立ができるようになった点は大きいです。
また、最初ページを読み込む際、複雑なAPIフェッチを扱う時に「なんとなく遅いな」と感じていたストレスが減るのは、デベロッパーにとってもユーザーにとっても嬉しいと思います。
Discussion