Nuxt Nation カンファレンス開催! 11月12日-13日、ご参加ください。

データ取得

Nuxtは、アプリケーション内でデータ取得を処理するためのコンポーザブルを提供します。

Nuxtには、ブラウザまたはサーバー環境でデータ取得を実行するための2つのコンポーザブルと組み込みライブラリが用意されています。useFetchuseAsyncData、および$fetchです。

要約すると

useFetchuseAsyncDataは、共通のオプションとパターンを共有しており、最後のセクションで詳しく説明します。

useFetchuseAsyncDataが必要な理由

Nuxtは、サーバーとクライアントの両方の環境で同型(またはユニバーサル)コードを実行できるフレームワークです。$fetch関数を使用してVueコンポーネントのsetup関数でデータ取得を実行すると、データが2回取得される可能性があります。1回目はサーバーで(HTMLをレンダリングするため)、2回目はクライアントで(HTMLがハイドレートされるとき)です。これにより、ハイドレーションの問題が発生し、インタラクティブになるまでの時間が増加し、予期しない動作が発生する可能性があります。

useFetchuseAsyncDataコンポーザブルは、サーバーでAPIコールが行われた場合に、データがペイロードにクライアントに転送されるようにすることで、この問題を解決します。

ペイロードは、useNuxtApp().payloadを通じてアクセスできるJavaScriptオブジェクトです。これは、ハイドレーション中にブラウザでコードが実行されるときに、同じデータを再取得しないようにクライアントで使用されます。

このデータを**ペイロードタブ**で検査するには、Nuxt DevToolsを使用してください。
app.vue
<script setup lang="ts">
const { data } = await useFetch('/api/data')

async function handleFormSubmit() {
  const res = await $fetch('/api/submit', {
    method: 'POST',
    body: {
      // My form data
    }
  })
}
</script>

<template>
  <div v-if="data == null">
    No data
  </div>
  <div v-else>
    <form @submit="handleFormSubmit">
      <!-- form input tags -->
    </form>
  </div>
</template>

上記の例では、useFetchはリクエストがサーバーで発生し、ブラウザに適切に転送されることを保証します。$fetchにはそのようなメカニズムがなく、リクエストがブラウザからのみ行われる場合はより適切なオプションです。

サスペンス

Nuxtは、すべての非同期データがビューで使用可能になる前にナビゲーションを防止するために、内部的にVueの<Suspense>コンポーネントを使用します。データ取得コンポーザブルを使用すると、この機能を活用し、コールごとに最適なものを利用できます。

ページ間のナビゲーションにプログレスバーを追加するには、<NuxtLoadingIndicator>を追加できます。

$fetch

Nuxtにはofetchライブラリが含まれており、アプリケーション全体で$fetchエイリアスとしてグローバルに自動インポートされます。

pages/todos.vue
<script setup lang="ts">
async function 
addTodo
() {
const
todo
= await
$fetch
('/api/todos', {
method
: 'POST',
body
: {
// My todo data } }) } </script>
$fetchのみを使用すると、ネットワーク呼び出しの重複排除とナビゲーションの防止が提供されませんのでご注意ください。
$fetchはクライアント側のインタラクション(イベントベース)に使用するか、初期コンポーネントデータの取得時にuseAsyncDataと組み合わせて使用することをお勧めします。
$fetchの詳細については、こちらをご覧ください。

クライアントヘッダーをAPIに渡す

サーバーサイドレンダリング中、$fetchリクエストはサーバー内で「内部的に」行われるため、ユーザーのブラウザクッキーは含まれません。

useRequestHeadersを使用して、サーバーサイドからAPIにクッキーにアクセスし、プロキシすることができます。

以下の例では、APIエンドポイントがユーザーによって最初に送信されたのと同じcookieヘッダーにアクセスできるように、同型$fetch呼び出しにリクエストヘッダーを追加します。

<script setup lang="ts">
const headers = useRequestHeaders(['cookie'])

async function getCurrentUser() {
  return await $fetch('/api/me', { headers: headers.value })
}
</script>
ヘッダーを外部APIにプロキシする前に十分に注意し、必要なヘッダーのみを含めてください。すべてのヘッダーが安全にバイパスできるわけではなく、望ましくない動作を引き起こす可能性があります。プロキシしてはならない一般的なヘッダーのリストを以下に示します。
  • hostaccept
  • content-lengthcontent-md5content-type
  • x-forwarded-hostx-forwarded-portx-forwarded-proto
  • cf-connecting-ipcf-ray
useRequestFetchを使用して、ヘッダーを呼び出しに自動的にプロキシすることもできます。::

useFetch

useFetchコンポーザブルは、setup関数でSSR対応のネットワーク呼び出しを行うために、内部的に$fetchを使用します。
app.vue
<script setup lang="ts">
const { 
data
:
count
} = await
useFetch
('/api/count')
</script> <template> <
p
>Page visits: {{
count
}}</
p
>
</template>
このコンポーザブルは、useAsyncDataコンポーザブルと$fetchユーティリティのラッパーです。
Alexander Lichterによるビデオをご覧ください。useFetchを間違った方法で使用しないようにしましょう!
ドキュメント > API > コンポーザブル > Use Fetchで詳細をご覧ください。
ドキュメント > 例 > 機能 > データ取得でライブ例を参照、編集できます。

useAsyncData

useAsyncDataコンポーザブルは、非同期ロジックをラップし、解決されると結果を返す役割を担います。
useFetch(url)は、ほぼuseAsyncData(url, () => event.$fetch(url))と同等です。
最も一般的なユースケースのための開発者エクスペリエンスのための糖衣構文です。(event.fetchの詳細については、useRequestFetchをご覧ください。)
Alexander Lichterによるビデオをご覧ください。useFetchuseAsyncDataの違いについて詳しく説明します。
useFetchコンポーザブルが適切でない場合もあります。たとえば、CMSやサードパーティが独自のクエリレイヤーを提供する場合などです。この場合、useAsyncDataを使用して呼び出しをラップし、コンポーザブルによって提供される利点を維持できます。
pages/users.vue
<script setup lang="ts">
const { data, error } = await useAsyncData('users', () => myGetFunction('users'))

// This is also possible:
const { data, error } = await useAsyncData(() => myGetFunction('users'))
</script>
useAsyncDataの最初の引数は、2番目の引数であるクエリ関数の応答をキャッシュするために使用される一意のキーです。クエリ関数を直接渡すことでこのキーを無視できます。キーは自動生成されます。

自動生成されたキーはuseAsyncDataが呼び出されたファイルと行のみを考慮するため、独自のコンポーザブルを作成してuseAsyncDataをラップする場合など、予期しない動作を避けるために、常に独自のキーを作成することをお勧めします。

キーを設定すると、useNuxtDataを使用してコンポーネント間で同じデータを共有したり、特定のデータを更新したりするのに役立ちます。
pages/users/[id].vue
<script setup lang="ts">
const { id } = useRoute().params

const { data, error } = await useAsyncData(`user:${id}`, () => {
  return myGetFunction('users', { id })
})
</script>
useAsyncDataコンポーザブルは、複数の$fetchリクエストが完了するのを待ってから結果を処理するのに最適な方法です。
<script setup lang="ts">
const { data: discounts, status } = await useAsyncData('cart-discount', async () => {
  const [coupons, offers] = await Promise.all([
    $fetch('/cart/coupons'),
    $fetch('/cart/offers')
  ])

  return { coupons, offers }
})
// discounts.value.coupons
// discounts.value.offers
</script>
useAsyncDataの詳細については、こちらをご覧ください。

戻り値

useFetchuseAsyncData は、以下に示す同じ戻り値を持ちます。
  • data: 渡された非同期関数の結果。
  • refresh/execute: handler 関数によって返されたデータを更新するために使用できる関数。
  • clear: dataundefined に設定し、errornull に設定し、statusidle に設定し、現在保留中のリクエストをすべてキャンセルマークを付けるために使用できる関数。
  • error: データ取得に失敗した場合のエラーオブジェクト。
  • status: データリクエストのステータスを示す文字列("idle""pending""success""error")。
dataerror、および status は、<script setup> 内で .value を使用してアクセスできる Vue ref です。
デフォルトでは、Nuxt は refresh が完了するまで、再度実行できません。
サーバー側でデータを取得していない場合(たとえば、server: false を使用した場合)、データはハイドレーションが完了するまで取得されません。これは、クライアント側で useFetch を await したとしても、<script setup> 内の data は null のままになることを意味します。

オプション

useAsyncDatauseFetch は同じオブジェクト型を返し、共通のオプションセットを最後の引数として受け取ります。これらは、ナビゲーションのブロック、キャッシング、または実行など、コンポーザブルの動作を制御するのに役立ちます。

遅延読み込み (Lazy)

デフォルトでは、データ取得コンポーザブルは、Vue の Suspense を使用して新しいページに移動する前に、非同期関数の解決を待機します。lazy オプションを使用すると、クライアント側のナビゲーションではこの機能を無視できます。その場合、status 値を使用して、読み込み状態を手動で処理する必要があります。
app.vue
<script setup lang="ts">
const { 
status
,
data
:
posts
} =
useFetch
('/api/posts', {
lazy
: true
}) </script> <template> <!-- you will need to handle a loading state --> <
div
v-if="
status
=== 'pending'">
Loading ... </
div
>
<
div
v-else>
<
div
v-for="
post
in
posts
">
<!-- do something --> </
div
>
</
div
>
</template>
代わりに、useLazyFetchuseLazyAsyncData を使用して、同じ操作を実行できます。
<script setup lang="ts">
const { 
status
,
data
:
posts
} =
useLazyFetch
('/api/posts')
</script>
useLazyFetch の詳細を読む。
useLazyAsyncData の詳細を読む。

クライアントのみのフェッチ

デフォルトでは、データ取得コンポーザブルは、クライアント環境とサーバー環境の両方で非同期関数を実行します。server オプションを false に設定すると、クライアント側でのみ呼び出しを実行します。初回読み込み時には、ハイドレーションが完了する前にデータはフェッチされませんが、その後のクライアント側のナビゲーションでは、ページを読み込む前にデータが待機されます。lazy オプションと組み合わせることで、初回レンダリングでは必要ないデータ(例:SEOに影響しないデータ)に役立ちます。
/* This call is performed before hydration */
const 
articles
= await
useFetch
('/api/article')
/* This call will only be performed on the client */ const {
status
,
data
:
comments
} =
useFetch
('/api/comments', {
lazy
: true,
server
: false
})
useFetch コンポーザブルは、setup メソッド内で呼び出すか、ライフサイクルフックの関数の最上位レベルで直接呼び出すことを意図しています。それ以外の場合は、$fetch メソッド を使用する必要があります。

ペイロードサイズの最小化

pick オプションは、コンポーザブルから返されるフィールドのみを選択することで、HTML ドキュメントに保存されるペイロードサイズを最小限に抑えるのに役立ちます。
<script setup lang="ts">
/* only pick the fields used in your template */
const { data: mountain } = await useFetch('/api/mountains/everest', {
  pick: ['title', 'description']
})
</script>

<template>
  <h1>{{ mountain.title }}</h1>
  <p>{{ mountain.description }}</p>
</template>
より詳細な制御が必要な場合、または複数のオブジェクトをマップする必要がある場合は、transform 関数を使用してクエリの結果を変更できます。
const { data: mountains } = await useFetch('/api/mountains', {
  transform: (mountains) => {
    return mountains.map(mountain => ({ title: mountain.title, description: mountain.description }))
  }
})
picktransform のどちらも、不要なデータの最初のフェッチを妨げません。ただし、サーバーからクライアントに転送されるペイロードへの不要なデータの追加は防ぎます。

キャッシングと再フェッチ

キー

useFetchuseAsyncData は、同じデータの再フェッチを防ぐためにキーを使用します。
  • useFetch は、提供された URL をキーとして使用します。あるいは、最後の引数として渡される options オブジェクトに key 値を指定することもできます。
  • useAsyncData は、最初の引数が文字列の場合、それをキーとして使用します。最初の引数がクエリを実行するハンドラー関数である場合、useAsyncData のインスタンスのファイル名と行番号を一意に識別するキーが自動的に生成されます。
キーでキャッシュされたデータを取得するには、useNuxtData を使用できます。

更新と実行

データを手動でフェッチまたは更新する場合は、コンポーザブルによって提供される execute 関数または refresh 関数を使用します。
<script setup lang="ts">
const { 
data
,
error
,
execute
,
refresh
} = await
useFetch
('/api/users')
</script> <template> <
div
>
<
p
>{{
data
}}</
p
>
<
button
@
click
="() =>
refresh
()">Refresh data</
button
>
</
div
>
</template>
execute 関数は refresh のエイリアスであり、まったく同じように動作しますが、フェッチが 即時ではない 場合に、より意味のあるものになります。
キャッシュされたデータをグローバルに再フェッチまたは無効にするには、clearNuxtDatarefreshNuxtData を参照してください。

クリア

clearNuxtData に渡す特定のキーを知る必要なく、何らかの理由で提供されたデータをクリアする必要がある場合は、コンポーザブルによって提供される clear 関数を使用できます。
<script setup lang="ts">
const { 
data
,
clear
} = await
useFetch
('/api/users')
const
route
=
useRoute
()
watch
(() =>
route
.
path
, (
path
) => {
if (
path
=== '/')
clear
()
}) </script>

監視 (Watch)

アプリケーション内の他のリアクティブな値が変更されるたびにフェッチ関数を再実行するには、watch オプションを使用します。1つまたは複数の「監視可能な」要素に使用できます。
<script setup lang="ts">
const 
id
=
ref
(1)
const {
data
,
error
,
refresh
} = await
useFetch
('/api/users', {
/* Changing the id will trigger a refetch */
watch
: [
id
]
}) </script>
リアクティブな値を監視しても、**フェッチされる URL は変更されません**。たとえば、URL は関数が呼び出された時点で構築されるため、ユーザーの同じ初期 ID が引き続きフェッチされます。
<script setup lang="ts">
const id = ref(1)

const { data, error, refresh } = await useFetch(`/api/users/${id.value}`, {
  watch: [id]
})
</script>
リアクティブな値に基づいて URL を変更する必要がある場合は、代わりに 計算された URL を使用することをお勧めします。

計算された URL

リアクティブな値から URL を計算し、それらが変更されるたびにデータを更新する必要がある場合があります。複雑な処理をする代わりに、各パラメーターをリアクティブな値として添付できます。Nuxt は自動的にリアクティブな値を使用し、変更されるたびに再フェッチします。
<script setup lang="ts">
const id = ref(null)

const { data, status } = useLazyFetch('/api/user', {
  query: {
    user_id: id
  }
})
</script>
より複雑な URL の構築の場合、計算されたゲッター として URL 文字列を返すコールバックを使用できます。依存関係が変更されるたびに、新しく構築された URL を使用してデータがフェッチされます。即時ではない と組み合わせることで、リアクティブな要素が変更されるまで待機できます。
<script setup lang="ts">
const id = ref(null)

const { data, status } = useLazyFetch(() => `/api/users/${id.value}`, {
  immediate: false
})

const pending = computed(() => status.value === 'pending');
</script>

<template>
  <div>
    <!-- disable the input while fetching -->
    <input v-model="id" type="number" :disabled="pending"/>

    <div v-if="status === 'idle'">
      Type an user ID
    </div>

    <div v-else-if="pending">
      Loading ...
    </div>

    <div v-else>
      {{ data }}
    </div>
  </div>
</template>
他のリアクティブな値が変更されたときに更新を強制する必要がある場合は、他の値を監視することもできます。

即時ではない

useFetch コンポーザブルは、呼び出された瞬間にデータのフェッチを開始します。immediate: false を設定することで、ユーザーの操作を待つなど、これを防ぐことができます。これにより、フェッチのライフサイクルを処理するための status と、データフェッチを開始するための execute の両方が必要になります。
<script setup lang="ts">
const { data, error, execute, status } = await useLazyFetch('/api/comments', {
  immediate: false
})
</script>

<template>
  <div v-if="status === 'idle'">
    <button @click="execute">Get data</button>
  </div>

  <div v-else-if="status === 'pending'">
    Loading comments...
  </div>

  <div v-else>
    {{ data }}
  </div>
</template>
status 変数は、より細かい制御のために使用できます。
  • idle: フェッチが開始されていない場合
  • pending: フェッチが開始されたが、まだ完了していない場合
  • error: フェッチが失敗した場合
  • success: フェッチが正常に完了した場合

ヘッダーとCookieの渡す

ブラウザで $fetch を呼び出すと、cookie のようなユーザーヘッダーは API に直接送信されます。通常、サーバーサイドレンダリング中は、$fetch リクエストはサーバー内で「内部的に」行われるため、ユーザーのブラウザCookieは含まれず、フェッチレスポンスからのCookieも渡されません。ただし、サーバーで useFetch を呼び出すと、Nuxt は useRequestFetch を使用して、ヘッダーとCookieをプロキシします(host のように転送されないことを意図したヘッダーを除く)。

SSR レスポンスでのサーバーサイド API 呼び出しからの Cookie の渡す

内部リクエストからクライアントへのCookieを反対方向に渡す/プロキシする必要がある場合は、自分で処理する必要があります。
composables/fetch.ts
import { appendResponseHeader } from 'h3'
import type { H3Event } from 'h3'

export const fetchWithCookie = async (event: H3Event, url: string) => {
  /* Get the response from the server endpoint */
  const res = await $fetch.raw(url)
  /* Get the cookies from the response */
  const cookies = res.headers.getSetCookie()
  /* Attach each cookie to our incoming Request */
  for (const cookie of cookies) {
    appendResponseHeader(event, 'set-cookie', cookie)
  }
  /* Return the data of the response */
  return res._data
}
<script setup lang="ts">
// This composable will automatically pass cookies to the client
const event = useRequestEvent()

const { data: result } = await useAsyncData(() => fetchWithCookie(event!, '/api/with-cookie'))

onMounted(() => console.log(document.cookie))
</script>

Options API のサポート

Nuxt は、Options API 内で asyncData フェッチを実行する方法を提供します。動作させるには、コンポーネント定義を defineNuxtComponent でラップする必要があります。
<script>
export default defineNuxtComponent({
  /* Use the fetchKey option to provide a unique key */
  fetchKey: 'hello',
  async asyncData () {
    return {
      hello: await $fetch('/api/hello')
    }
  }
})
</script>
<script setup> または <script setup lang="ts"> は、Nuxt 3 で Vue コンポーネントを宣言する推奨方法です。
詳細については、ドキュメント > API > ユーティリティ > Define Nuxt Component を参照してください。

サーバーからクライアントへのデータのシリアライズ

useAsyncDatauseLazyAsyncData を使用して、サーバーでフェッチされたデータをクライアントに転送する場合(Nuxt ペイロード を使用するその他のものも含む)、ペイロードは devalue でシリアライズされます。これにより、基本的な JSON だけではなく、正規表現、日付、Map と Set、refreactiveshallowRefshallowReactiveNuxtError など、より高度な種類のデータをシリアライズおよび復元/デシリアライズできます。Nuxt でサポートされていない型のシリアライザー/デシリアライザーを独自に定義することも可能です。useNuxtApp のドキュメントで詳細を読むことができます。
これは、$fetch または useFetch でフェッチされた場合、サーバールートから渡されたデータには**適用されません**。詳細については、次のセクションを参照してください。

API ルートからのデータのシリアライズ

server ディレクトリからデータを取得する場合、レスポンスは JSON.stringify を使用してシリアライズされます。ただし、シリアライズは JavaScript プリミティブ型に限定されているため、Nuxt は $fetchuseFetch の戻り値型を実際の値と一致するように変換するよう最善を尽くします。
JSON.stringify の制限事項について詳しく学ぶ。

server/api/foo.ts
export default defineEventHandler(() => {
  return new Date()
})
app.vue
<script setup lang="ts">
// Type of `data` is inferred as string even though we returned a Date object
const { data } = await useFetch('/api/foo')
</script>

カスタムシリアライザ関数

シリアライゼーションの動作をカスタマイズするには、返されるオブジェクトに toJSON 関数を定義します。toJSON メソッドを定義した場合、Nuxt は関数の戻り値の型を尊重し、型の変換を試みません。
server/api/bar.ts
export default defineEventHandler(() => {
  const data = {
    createdAt: new Date(),

    toJSON() {
      return {
        createdAt: {
          year: this.createdAt.getFullYear(),
          month: this.createdAt.getMonth(),
          day: this.createdAt.getDate(),
        },
      }
    },
  }
  return data
})
app.vue
<script setup lang="ts">
// Type of `data` is inferred as
// {
//   createdAt: {
//     year: number
//     month: number
//     day: number
//   }
// }
const { data } = await useFetch('/api/bar')
</script>

代替シリアライザの使用

Nuxt は現在、JSON.stringify の代替シリアライザをサポートしていません。ただし、ペイロードを通常の文字列として返し、toJSON メソッドを使用して型安全性を維持することができます。以下の例では、superjson をシリアライザとして使用しています。
server/api/superjson.ts
import superjson from 'superjson'

export default defineEventHandler(() => {
  const data = {
    createdAt: new Date(),

    // Workaround the type conversion
    toJSON() {
      return this
    }
  }

  // Serialize the output to string, using superjson
  return superjson.stringify(data) as unknown as typeof data
})
app.vue
<script setup lang="ts">
import superjson from 'superjson'

// `date` is inferred as { createdAt: Date } and you can safely use the Date object methods
const { data } = await useFetch('/api/superjson', {
  transform: (value) => {
    return superjson.parse(value as unknown as string)
  },
})
</script>

レシピ

POSTリクエストによるSSE(サーバー・セント・イベント)の消費

GETリクエストでSSEを消費する場合は、EventSource または VueUse のコンポーザブル useEventSource を使用できます。
POSTリクエストでSSEを消費する場合は、接続を手動で処理する必要があります。方法は次のとおりです。
// Make a POST request to the SSE endpoint
const response = await $fetch<ReadableStream>('/chats/ask-ai', {
  method: 'POST',
  body: {
    query: "Hello AI, how are you?",
  },
  responseType: 'stream',
})

// Create a new ReadableStream from the response with TextDecoderStream to get the data as text
const reader = response.pipeThrough(new TextDecoderStream()).getReader()

// Read the chunk of data as we get it
while (true) {
  const { value, done } = await reader.read()

  if (done)
    break

  console.log('Received:', value)
}