brand logo

ドキュメント

useAsyncData

useAsyncDataは、SSRに適したコンポーザブルで非同期に解決されるデータへのアクセスを提供します。

ページ、コンポーネント、プラグイン内で、useAsyncDataを使用して非同期に解決されるデータにアクセスできます。

useAsyncDataは、Nuxtコンテキストで直接呼び出すことを意図したコンポーザブルです。これはリアクティブなコンポーザブルを返し、ページがハイドレートされる際にクライアント側でデータを再取得することなく、サーバーからクライアントにレスポンスをNuxtペイロードに追加する処理を行います。

使用方法

pages/index.vue
const { data, status, error, refresh, clear } = await useAsyncData(
  'mountains',
  () => $fetch('https://api.nuxtjs.dev/mountains')
)

カスタムのuseAsyncDataラッパーを使用している場合、予期しない動作を引き起こす可能性があるため、コンポーザブル内でawaitしないでください。カスタム非同期データフェッチャーの作成方法についてはこのレシピを参照してください。

datastatuserrorはVueのrefであり、<script>内で使用する際には.valueでアクセスする必要があります。一方、refresh/executeclearは通常の関数です。

パラメータの監視

組み込みのwatchオプションを使用すると、変更が検出されたときにフェッチャー関数を自動的に再実行できます。

pages/index.vue
const page = ref(1)
const { data: posts } = await useAsyncData(
  'posts',
  () => $fetch('https://fakeApi.com/posts', {
    params: {
      page: page.value
    }
  }), {
    watch: [page]
  }
)

リアクティブキー

キーとしてcomputed ref、通常のref、またはゲッター関数を使用することで、キーが変更されたときに自動的に更新される動的なデータフェッチが可能です。

pages/[id\
const route = useRoute()
const userId = computed(() => `user-${route.params.id}`)

// ルートが変更されuserIdが更新されると、データは自動的に再フェッチされます
const { data: user } = useAsyncData(
  userId,
  () => fetchUserById(route.params.id)
)

useAsyncDataはコンパイラによって変換される予約関数名であるため、自分の関数にuseAsyncDataという名前を付けないでください。

こちらも参照 getting-started > data-fetching#useasyncdata

パラメータ

  • key: リクエスト間でデータフェッチを適切に重複排除するための一意のキー。キーを指定しない場合、useAsyncDataのインスタンスのファイル名と行番号に基づいて一意のキーが生成されます。
  • handler: 真値を返す必要がある非同期関数(例えば、undefinednullであってはならない)。そうでないと、クライアント側でリクエストが重複する可能性があります。

handler関数はSSRおよびCSRのハイドレーション中に予測可能な動作を保証するために副作用がないようにする必要があります。副作用をトリガーする必要がある場合は、callOnceユーティリティを使用してください。

  • options:
    • server: サーバーでデータをフェッチするかどうか(デフォルトはtrue
    • lazy: クライアント側のナビゲーションをブロックする代わりに、ルートの読み込み後に非同期関数を解決するかどうか(デフォルトはfalse
    • immediate: falseに設定すると、リクエストが即座に発火するのを防ぎます。(デフォルトはtrue
    • default: 非同期関数が解決される前にdataのデフォルト値を設定するためのファクトリ関数 - lazy: trueまたはimmediate: falseオプションと共に便利です
    • transform: 解決後にhandler関数の結果を変更するために使用できる関数
    • getCachedData: キャッシュされたデータを返す関数を提供します。nullまたはundefinedを返すとフェッチがトリガーされます。デフォルトでは次のようになっています:
      const getDefaultCachedData = (key, nuxtApp, ctx) => nuxtApp.isHydrating 
        ? nuxtApp.payload.data[key] 
        : nuxtApp.static.data[key]
      
      これはnuxt.configexperimental.payloadExtractionが有効な場合にのみデータをキャッシュします。
    • pick: handler関数の結果からこの配列内の指定されたキーのみを選択
    • watch: リアクティブなソースを監視して自動リフレッシュ
    • deep: データを深いrefオブジェクトで返す(デフォルトはtrue)。データが深くリアクティブである必要がない場合、パフォーマンスを向上させるためにfalseに設定できます。
    • dedupe: 同じキーを一度に複数回フェッチするのを避ける(デフォルトはcancel)。可能なオプション:
      • cancel - 新しいリクエストが行われたときに既存のリクエストをキャンセル
      • defer - 保留中のリクエストがある場合、新しいリクエストを全く行わない

内部的には、lazy: false<Suspense>を使用して、データがフェッチされる前にルートの読み込みをブロックします。よりスナッピーなユーザーエクスペリエンスのために、lazy: trueを使用し、ローディング状態を実装することを検討してください。

こちらも参照 api > composables > use-lazy-async-data

共有状態とオプションの一貫性

同じキーを使用して複数のuseAsyncData呼び出しを行うと、それらは同じdataerrorstatusのrefを共有します。これにより、コンポーネント間での一貫性が保証されますが、オプションの一貫性が必要です。

次のオプションは、同じキーでのすべての呼び出しで一貫している必要があります

  • handler関数
  • deepオプション
  • transform関数
  • pick配列
  • getCachedData関数
  • default

次のオプションは、警告をトリガーすることなく異なることができます

  • server
  • lazy
  • immediate
  • dedupe
  • watch
// ❌ これは開発警告をトリガーします
const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { deep: false })
const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { deep: true })

// ✅ これは許可されています
const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { immediate: true })
const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { immediate: false })

戻り値

  • data: 渡された非同期関数の結果。
  • refresh/execute: handler関数によって返されるデータをリフレッシュするために使用できる関数。
  • error: データフェッチが失敗した場合のエラーオブジェクト。
  • status: データリクエストのステータスを示す文字列:
    • idle: リクエストが開始されていないとき、例えば:
      • executeがまだ呼び出されておらず、{ immediate: false }が設定されているとき
      • サーバーでHTMLをレンダリングしており、{ server: false }が設定されているとき
    • pending: リクエストが進行中
    • success: リクエストが正常に完了
    • error: リクエストが失敗
  • clear: dataundefined(または提供されている場合はoptions.default()の値)に設定し、errornullに設定し、statusidleに設定し、現在保留中のリクエストをキャンセル済みとしてマークするために使用できる関数。

デフォルトでは、Nuxtはrefreshが完了するまで再度実行されるのを待ちます。

サーバーでデータをフェッチしていない場合(例えば、server: falseを使用している場合)、データはハイドレーションが完了するまでフェッチされません。つまり、クライアント側でuseAsyncDataをawaitしても、<script>内ではdatanullのままです。

Signature
function useAsyncData<DataT, DataE>(
  handler: (nuxtApp?: NuxtApp) => Promise<DataT>,
  options?: AsyncDataOptions<DataT>
): AsyncData<DataT, DataE>
function useAsyncData<DataT, DataE>(
  key: string | Ref<string> | ComputedRef<string>,
  handler: (nuxtApp?: NuxtApp) => Promise<DataT>,
  options?: AsyncDataOptions<DataT>
): Promise<AsyncData<DataT, DataE>>

type AsyncDataOptions<DataT> = {
  server?: boolean
  lazy?: boolean
  immediate?: boolean
  deep?: boolean
  dedupe?: 'cancel' | 'defer'
  default?: () => DataT | Ref<DataT> | null
  transform?: (input: DataT) => DataT | Promise<DataT>
  pick?: string[]
  watch?: WatchSource[] | false
  getCachedData?: (key: string, nuxtApp: NuxtApp, ctx: AsyncDataRequestContext) => DataT | undefined
}

type AsyncDataRequestContext = {
  /** このデータリクエストの理由 */
  cause: 'initial' | 'refresh:manual' | 'refresh:hook' | 'watch'
}

type AsyncData<DataT, ErrorT> = {
  data: Ref<DataT | null>
  refresh: (opts?: AsyncDataExecuteOptions) => Promise<void>
  execute: (opts?: AsyncDataExecuteOptions) => Promise<void>
  clear: () => void
  error: Ref<ErrorT | null>
  status: Ref<AsyncDataRequestStatus>
};

interface AsyncDataExecuteOptions {
  dedupe?: 'cancel' | 'defer'
}

type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
こちらも参照 getting-started > data-fetching

tips

このセクションは公式ドキュメントの翻訳ではなく、本サイト独自の補足記事です。

useAsyncDataの補足解説:Nuxtで非同期データ取得を効率化する方法

NuxtのuseAsyncDataは、サーバーサイドレンダリング(SSR)やクライアントサイドレンダリング(CSR)において、非同期データを簡単かつ効率的に扱うためのコンポーザブルです。公式ドキュメントで基本的な使い方は紹介されていますが、実務での活用や注意点を理解することで、より堅牢でパフォーマンスの高いアプリケーションを作成できます。

本記事では、useAsyncDataのメリットや使いどころ、実際のユースケース、そしてよくある落とし穴について詳しく解説します。


まず結論:useAsyncDataのポイントまとめ

  • SSRとCSRの両方でシームレスに非同期データを取得できる
    サーバーでデータを取得し、クライアントにハイドレーション時に再フェッチ不要な状態で渡せる。

  • リアクティブなデータ管理が可能
    dataはVueのrefで返され、リアクティブにUIに反映される。

  • キーによるデータ共有と重複排除
    同じキーを使うことで複数コンポーネント間でデータを共有し、無駄なリクエストを防止。

  • オプションで細かい挙動を制御可能
    lazyimmediatewatchなどのオプションでフェッチタイミングや再取得条件を柔軟に設定できる。

  • 副作用のない非同期関数を使うことが重要
    SSRとCSRで同じ結果を返す純粋な関数である必要があり、副作用は避けるべき。


いつ使うべきか?使わない方がよいケースは?

使うべきケース

  • ページやコンポーネントで初期データをSSRで取得したいとき
    SEOや初期表示速度を重視し、サーバーでデータを取得してからレンダリングしたい場合に最適。

  • クライアント側での再フェッチを自動制御したいとき
    ルートパラメータの変更やユーザー操作に応じてデータを自動更新したい場合。

  • 複数コンポーネントで同じデータを共有したいとき
    キーを共有することで、状態管理を簡素化できる。

使わない方がよいケース

  • 副作用を伴う非同期処理が必要な場合
    例えば、ログ記録や外部APIへの副作用的な書き込みはuseAsyncDatahandlerには不向き。

  • クライアント専用の非同期処理をしたい場合
    SSRでの処理が不要な場合はserver: falseオプションを使うか、別の手段を検討。

  • 非常に頻繁に更新されるデータを扱う場合
    過剰な再フェッチがパフォーマンスに悪影響を与える可能性があるため、dedupeオプションやキャッシュ戦略を検討。


実務でよくあるユースケースとサンプルコード

1. ページの初期データをSSRで取得し、クライアントで再フェッチ不要にする

<script setup lang="ts">
const { data: posts, error, refresh } = await useAsyncData('posts', () =>
  $fetch('https://api.example.com/posts')
)
</script>

<template>
  <div v-if="error">データ取得に失敗しました。</div>
  <ul v-else>
    <li v-for="post in posts" :key="post.id">{{ post.title }}</li>
  </ul>
</template>

この例では、サーバーで投稿一覧を取得し、クライアント側で再フェッチせずに表示します。


2. ルートパラメータに応じて動的にデータを取得し、パラメータ変更時に自動更新

<script setup lang="ts">
const route = useRoute()
const userId = computed(() => `user-${route.params.id}`)

const { data: user } = useAsyncData(
  userId,
  () => $fetch(`https://api.example.com/users/${route.params.id}`),
  { watch: [route.params.id] }
)
</script>

<template>
  <div v-if="user">
    <h1>{{ user.name }}</h1>
    <p>{{ user.email }}</p>
  </div>
</template>

ルートのidが変わるたびに自動でユーザーデータを再取得します。


3. クライアント側で遅延フェッチ(lazy)を使い、初期表示を高速化

<script setup lang="ts">
const { data: comments, status } = await useAsyncData(
  'comments',
  () => $fetch('https://api.example.com/comments'),
  { lazy: true }
)

onMounted(() => {
  if (status.value === 'idle') {
    refresh()
  }
})
</script>

<template>
  <div v-if="status === 'pending'">コメントを読み込み中...</div>
  <ul v-else>
    <li v-for="comment in comments" :key="comment.id">{{ comment.text }}</li>
  </ul>
</template>

lazy: trueにより、ページの初期ロードをブロックせずにコメントを非同期取得します。


よくある落とし穴・注意点

1. SSRとCSRでの副作用に注意

handler関数はSSRとCSRで同じ結果を返す純粋関数である必要があります。副作用(ログ出力や状態変更など)を含むと、ハイドレーション時に不整合が発生し、UIの再レンダリングやエラーの原因になります。


2. ハイドレーション時のデータ重複フェッチを防ぐ

Nuxtはサーバーで取得したデータをペイロードに含め、クライアントでの再フェッチを防ぎます。しかし、handlernullundefinedを返すとキャッシュされず、クライアントで再度リクエストが発生します。必ず有効な値を返すようにしましょう。


3. 同じキーで呼び出す際のオプションの一貫性

同じキーを使う複数のuseAsyncData呼び出しでは、handlerdeeptransformなどのオプションはすべて一致させる必要があります。異なる設定をすると開発時に警告が出たり、予期せぬ動作を招きます。


4. パフォーマンス面の考慮

  • watchオプションで監視対象が頻繁に変わる場合、リクエストが多発しパフォーマンス低下の原因に。dedupeオプションでリクエストのキャンセルや抑制を検討しましょう。
  • lazy: false(デフォルト)では、データ取得完了までページの読み込みがブロックされます。ユーザー体験を優先するならlazy: trueを使い、ローディングUIを実装するのがおすすめです。

まとめ

useAsyncDataはNuxtで非同期データをSSR/CSR両面で効率的に扱うための強力なツールです。リアクティブなデータ管理やキーによる共有、オプションによる細かな制御が可能で、実務でのデータ取得を大幅に簡素化します。

ただし、副作用のない純粋な非同期関数を使うこと、同じキーでのオプションの一貫性を保つこと、ハイドレーション時のデータ重複フェッチを防ぐことなど、いくつかの注意点を守る必要があります。

これらを理解し適切に使いこなすことで、Nuxtアプリケーションのパフォーマンスと信頼性を高めることができます。


useAsyncDataの挙動をより細かく制御したい場合は、lazywatchdedupeなどのオプションを積極的に活用し、ユーザー体験とパフォーマンスのバランスを調整しましょう。

SSRでデータを取得しない設定(server: false)の場合、クライアント側でのデータ取得タイミングに注意が必要です。dataがnullのままになることがあるため、UIの条件分岐を適切に行いましょう。