nuxt logo

문서 번역(비공식)

데이터 가져오기

Nuxt는 애플리케이션 내에서 데이터 가져오기를 처리하기 위한 composables를 제공합니다.

Nuxt는 브라우저 또는 서버 환경에서 데이터 가져오기를 수행하기 위한 두 가지 composables와 내장 라이브러리를 제공합니다: useFetch, useAsyncData$fetch.

간단히 말해서:

  • $fetch는 네트워크 요청을 수행하는 가장 간단한 방법입니다.
  • useFetch유니버설 렌더링에서 데이터를 한 번만 가져오는 $fetch의 래퍼입니다.
  • useAsyncDatauseFetch와 유사하지만 더 세밀한 제어를 제공합니다.

useFetchuseAsyncData는 공통의 옵션과 패턴을 공유하며, 마지막 섹션에서 자세히 설명할 것입니다.

useFetchuseAsyncData의 필요성

Nuxt는 서버와 클라이언트 환경 모두에서 동작할 수 있는 이소모픽(또는 유니버설) 코드를 실행할 수 있는 프레임워크입니다. Vue 컴포넌트의 setup 함수에서 $fetch 함수를 사용하여 데이터를 가져오면, 서버에서 한 번(HTML을 렌더링하기 위해)과 클라이언트에서 한 번(HTML이 하이드레이션될 때) 두 번 데이터를 가져오게 될 수 있습니다. 이는 하이드레이션 문제를 일으키고, 상호작용 시간을 증가시키며 예측할 수 없는 동작을 초래할 수 있습니다.

useFetchuseAsyncData composables는 서버에서 API 호출이 이루어질 경우, 데이터를 페이로드로 클라이언트에 전달하여 이 문제를 해결합니다.

페이로드는 useNuxtApp().payload를 통해 접근할 수 있는 JavaScript 객체입니다. 이는 하이드레이션 동안 브라우저에서 코드가 실행될 때 동일한 데이터를 다시 가져오는 것을 방지하기 위해 클라이언트에서 사용됩니다.

이 데이터를 Payload 탭에서 검사하려면 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: {
      // 내 폼 데이터
    }
  })
}
</script>

<template>
  <div v-if="data == null">
    데이터 없음
  </div>
  <div v-else>
    <form @submit="handleFormSubmit">
      <!-- 폼 입력 태그 -->
    </form>
  </div>
</template>

위의 예에서 useFetch는 요청이 서버에서 발생하고 브라우저로 적절히 전달되도록 보장합니다. $fetch는 이러한 메커니즘이 없으며, 요청이 브라우저에서만 수행될 때 사용하는 것이 더 좋습니다.

Suspense

Nuxt는 Vue의 <Suspense> 컴포넌트를 사용하여 모든 비동기 데이터가 뷰에 제공되기 전에 탐색을 방지합니다. 데이터 가져오기 composables는 이 기능을 활용하여 호출별로 가장 적합한 것을 사용할 수 있도록 도와줍니다.

페이지 탐색 간에 진행 바를 추가하려면 <NuxtLoadingIndicator>를 추가할 수 있습니다.

$fetch

Nuxt는 ofetch 라이브러리를 포함하고 있으며, 애플리케이션 전역에서 $fetch 별칭으로 자동으로 가져옵니다.

pages/todos.vue
async function addTodo() {
  const todo = await $fetch('/api/todos', {
    method: 'POST',
    body: {
      // 내 할 일 데이터
    }
  })
}

$fetch만 사용하면 네트워크 호출 중복 제거 및 탐색 방지를 제공하지 않습니다. :br 초기 컴포넌트 데이터를 가져올 때는 $fetch를 클라이언트 측 상호작용(이벤트 기반)이나 useAsyncData와 결합하여 사용하는 것이 좋습니다.

이것도 참고 api > utils > dollarfetch

클라이언트 헤더를 API에 전달하기

서버에서 useFetch를 호출할 때, Nuxt는 useRequestFetch를 사용하여 클라이언트 헤더와 쿠키를 프록시합니다(전달되지 않아야 하는 헤더는 제외, 예: host).

const { data } = await useFetch('/api/echo');
// /api/echo.ts
export default defineEventHandler(event => parseCookies(event))

대안으로, 아래 예는 서버 측 요청(클라이언트에서 시작된)에서 쿠키를 API에 접근하고 전송하기 위해 useRequestHeaders를 사용하는 방법을 보여줍니다. 이소모픽 $fetch 호출을 사용하여 API 엔드포인트가 사용자의 브라우저에서 원래 전송된 동일한 cookie 헤더에 접근할 수 있도록 보장합니다. useFetch를 사용하지 않는 경우에만 필요합니다.

const headers = useRequestHeaders(['cookie'])

async function getCurrentUser() {
  return await $fetch('/api/me', { headers })
}

헤더를 자동으로 호출에 프록시하려면 useRequestFetch를 사용할 수도 있습니다.

외부 API에 헤더를 프록시하기 전에 매우 주의하고 필요한 헤더만 포함하세요. 모든 헤더가 안전하게 우회될 수 있는 것은 아니며 원치 않는 동작을 초래할 수 있습니다. 프록시되지 말아야 할 일반적인 헤더 목록은 다음과 같습니다:

  • host, accept
  • content-length, content-md5, content-type
  • x-forwarded-host, x-forwarded-port, x-forwarded-proto
  • cf-connecting-ip, cf-ray

useFetch

useFetch composable은 setup 함수에서 SSR 안전한 네트워크 호출을 수행하기 위해 $fetch를 내부적으로 사용합니다.

app.vue
<script setup lang="ts">
const { data: count } = await useFetch('/api/count')
</script>

<template>
  <p>페이지 방문 수: {{ count }}</p>
</template>

이 composable은 useAsyncData composable과 $fetch 유틸리티의 래퍼입니다.

이것도 참고 api > composables > use-fetch
샘플 코드 편집 및 미리보기examples > features > data-fetching

useAsyncData

useAsyncData composable은 비동기 로직을 래핑하고 해결되면 결과를 반환하는 역할을 합니다.

useFetch(url)은 거의 useAsyncData(url, () => event.$fetch(url))와 동일합니다. :br 가장 일반적인 사용 사례를 위한 개발자 경험 설탕입니다. (event.fetch에 대해 더 알고 싶다면 useRequestFetch를 참조하세요.)

useFetch composable을 사용하는 것이 적절하지 않은 경우가 있습니다. 예를 들어, CMS나 타사에서 자체 쿼리 레이어를 제공하는 경우입니다. 이 경우, useAsyncData를 사용하여 호출을 래핑하고 composable이 제공하는 이점을 계속 유지할 수 있습니다.

pages/users.vue
const { data, error } = await useAsyncData('users', () => myGetFunction('users'))

// 이것도 가능합니다:
const { data, error } = await useAsyncData(() => myGetFunction('users'))

useAsyncData의 첫 번째 인수는 두 번째 인수인 쿼리 함수의 응답을 캐시하는 데 사용되는 고유 키입니다. 쿼리 함수를 직접 전달하여 이 키를 무시할 수 있으며, 키는 자동으로 생성됩니다. :br :br 자동 생성된 키는 useAsyncData가 호출된 파일과 줄만 고려하므로, 원치 않는 동작을 피하기 위해 항상 고유한 키를 생성하는 것이 좋습니다. 예를 들어, useAsyncData를 래핑하는 사용자 정의 composable을 생성할 때입니다. :br :br 키를 설정하면 useNuxtData를 사용하여 구성 요소 간에 동일한 데이터를 공유하거나 특정 데이터를 새로 고침하는 데 유용할 수 있습니다.

pages/users/[id\
const { id } = useRoute().params

const { data, error } = await useAsyncData(`user:${id}`, () => {
  return myGetFunction('users', { id })
})

useAsyncData composable은 여러 $fetch 요청이 완료될 때까지 래핑하고 기다린 다음 결과를 처리하는 훌륭한 방법입니다.

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

useAsyncData는 데이터를 가져오고 캐시하는 데 사용되며, Pinia 액션 호출과 같은 부작용을 트리거하는 데 사용되지 않습니다. 이는 nullish 값으로 반복 실행되는 등의 의도치 않은 동작을 초래할 수 있습니다. 부작용을 트리거해야 하는 경우, callOnce 유틸리티를 사용하세요.

const offersStore = useOffersStore()

// 이렇게 하면 안 됩니다
await useAsyncData(() => offersStore.getOffer(route.params.slug))
이것도 참고 api > composables > use-async-data

반환 값

useFetchuseAsyncData는 아래에 나열된 동일한 반환 값을 가집니다.

  • data: 전달된 비동기 함수의 결과입니다.
  • refresh/execute: handler 함수에 의해 반환된 데이터를 새로 고치는 데 사용할 수 있는 함수입니다.
  • clear: dataundefined(또는 제공된 경우 options.default()의 값)로 설정하고, errornull로 설정하고, statusidle로 설정하며, 현재 보류 중인 요청을 취소된 것으로 표시하는 데 사용할 수 있는 함수입니다.
  • error: 데이터 가져오기가 실패한 경우의 오류 객체입니다.
  • status: 데이터 요청의 상태를 나타내는 문자열입니다("idle", "pending", "success", "error").

data, errorstatus<script setup>에서 .value로 접근할 수 있는 Vue refs입니다.

기본적으로 Nuxt는 refresh가 완료될 때까지 기다린 후 다시 실행할 수 있습니다.

서버에서 데이터를 가져오지 않은 경우(예: server: false), 데이터는 하이드레이션이 완료될 때까지 가져오지 않습니다. 이는 클라이언트 측에서 useFetch를 기다리더라도 <script setup> 내에서 data가 null로 남아 있음을 의미합니다.

옵션

useAsyncDatauseFetch는 동일한 객체 유형을 반환하고 마지막 인수로 공통의 옵션 세트를 수락합니다. 이들은 탐색 차단, 캐싱 또는 실행과 같은 composables의 동작을 제어하는 데 도움이 될 수 있습니다.

Lazy

기본적으로 데이터 가져오기 composables는 Vue의 Suspense를 사용하여 비동기 함수의 해결을 기다린 후 새 페이지로 탐색합니다. 이 기능은 클라이언트 측 탐색에서 lazy 옵션으로 무시할 수 있습니다. 이 경우, status 값을 사용하여 로딩 상태를 수동으로 처리해야 합니다.

app.vue
<script setup lang="ts">
const { status, data: posts } = useFetch('/api/posts', {
  lazy: true
})
</script>

<template>
  <!-- 로딩 상태를 처리해야 합니다 -->
  <div v-if="status === 'pending'">
    로딩 중 ...
  </div>
  <div v-else>
    <div v-for="post in posts">
      <!-- 무언가를 수행 -->
    </div>
  </div>
</template>

대안으로 useLazyFetchuseLazyAsyncData를 사용하여 동일한 작업을 수행할 수 있습니다.

const { status, data: posts } = useLazyFetch('/api/posts')
이것도 참고 api > composables > use-lazy-fetch 이것도 참고 api > composables > use-lazy-async-data

클라이언트 전용 가져오기

기본적으로 데이터 가져오기 composables는 클라이언트와 서버 환경 모두에서 비동기 함수를 수행합니다. server 옵션을 false로 설정하여 클라이언트 측에서만 호출을 수행합니다. 초기 로드 시, 데이터는 하이드레이션이 완료될 때까지 가져오지 않으므로 보류 상태를 처리해야 하지만, 이후 클라이언트 측 탐색에서는 데이터를 기다린 후 페이지를 로드합니다.

lazy 옵션과 결합하여, 첫 번째 렌더링에 필요하지 않은 데이터(예: SEO에 민감하지 않은 데이터)에 유용할 수 있습니다.

/* 이 호출은 하이드레이션 전에 수행됩니다 */
const articles = await useFetch('/api/article')

/* 이 호출은 클라이언트에서만 수행됩니다 */
const { status, data: comments } = useFetch('/api/comments', {
  lazy: true,
  server: false
})

useFetch composable은 setup 메소드에서 호출되거나 라이프사이클 훅의 함수 상단에서 직접 호출되도록 설계되었습니다. 그렇지 않으면 $fetch 메소드를 사용해야 합니다.

페이로드 크기 최소화

pick 옵션은 composables에서 반환되는 필드만 선택하여 HTML 문서에 저장되는 페이로드 크기를 최소화하는 데 도움이 됩니다.

<script setup lang="ts">
/* 템플릿에서 사용되는 필드만 선택 */
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를 사용할 수 있습니다.

공유 상태 및 옵션 일관성

여러 구성 요소가 useAsyncData 또는 useFetch와 동일한 키를 사용할 때, 동일한 data, errorstatus refs를 공유합니다. 이는 구성 요소 간의 일관성을 보장하지만 일부 옵션은 일관성을 유지해야 합니다.

다음 옵션은 동일한 키로 모든 호출에서 일관성을 유지해야 합니다:

  • handler 함수
  • deep 옵션
  • transform 함수
  • pick 배열
  • getCachedData 함수
  • default
// ❌ 개발 경고를 트리거합니다
const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { deep: false })
const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { deep: true })

다음 옵션은 경고를 트리거하지 않고 안전하게 다를 수 있습니다:

  • server
  • lazy
  • immediate
  • dedupe
  • watch
// ✅ 허용됩니다
const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { immediate: true })
const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { immediate: false })

독립적인 인스턴스가 필요한 경우, 다른 키를 사용하세요:

// 이들은 완전히 독립적인 인스턴스입니다
const { data: users1 } = useAsyncData('users-1', () => $fetch('/api/users'))
const { data: users2 } = useAsyncData('users-2', () => $fetch('/api/users'))

반응형 키

키로 계산된 refs, 일반 refs 또는 getter 함수를 사용할 수 있어, 종속성이 변경될 때 자동으로 업데이트되는 동적 데이터 가져오기를 허용합니다:

// 계산된 속성을 키로 사용
const userId = ref('123')
const { data: user } = useAsyncData(
  computed(() => `user-${userId.value}`),
  () => fetchUser(userId.value)
)

// userId가 변경되면 데이터가 자동으로 다시 가져오고
// 다른 구성 요소에서 사용하지 않는 경우 이전 데이터가 정리됩니다
userId.value = '456'

새로 고침 및 실행

데이터를 수동으로 가져오거나 새로 고치려면, composables에서 제공하는 execute 또는 refresh 함수를 사용하세요.

<script setup lang="ts">
const { data, error, execute, refresh } = await useFetch('/api/users')
</script>

<template>
  <div>
    <p>{{ data }}</p>
    <button @click="() => refresh()">데이터 새로 고침</button>
  </div>
</template>

execute 함수는 즉시가 아닌 경우 더 의미 있는 경우에 대해 refresh와 동일한 방식으로 작동하는 별칭입니다.

캐시된 데이터를 전역적으로 다시 가져오거나 무효화하려면 clearNuxtDatarefreshNuxtData를 참조하세요.

정리

특정 키를 clearNuxtData에 전달할 필요 없이 제공된 데이터를 어떤 이유로든 정리하려면, composables에서 제공하는 clear 함수를 사용할 수 있습니다.

const { data, clear } = await useFetch('/api/users')

const route = useRoute()
watch(() => route.path, (path) => {
  if (path === '/') clear()
})

감시

애플리케이션의 다른 반응형 값이 변경될 때마다 가져오기 함수를 다시 실행하려면, watch 옵션을 사용하세요. 하나 이상의 감시 가능한 요소에 사용할 수 있습니다.

const id = ref(1)

const { data, error, refresh } = await useFetch('/api/users', {
  /* id가 변경되면 다시 가져옵니다 */
  watch: [id]
})

반응형 값을 감시해도 가져온 URL은 변경되지 않습니다. 예를 들어, 이 경우 함수가 호출될 때 URL이 구성되므로 동일한 초기 ID의 사용자를 계속 가져옵니다.

const id = ref(1)

const { data, error, refresh } = await useFetch(`/api/users/${id.value}`, {
  watch: [id]
})

반응형 값을 기반으로 URL을 변경해야 하는 경우, 대신 계산된 URL을 사용할 수 있습니다.

계산된 URL

때때로 반응형 값에서 URL을 계산하고, 이러한 값이 변경될 때마다 데이터를 새로 고쳐야 할 수 있습니다. 복잡한 방법을 사용하지 않고 각 매개변수를 반응형 값으로 연결할 수 있습니다. Nuxt는 반응형 값을 자동으로 사용하고 변경될 때마다 다시 가져옵니다.

const id = ref(null)

const { data, status } = useLazyFetch('/api/user', {
  query: {
    user_id: id
  }
})

더 복잡한 URL 구성이 필요한 경우, URL 문자열을 반환하는 계산된 getter로 콜백을 사용할 수 있습니다.

종속성이 변경될 때마다 새로 구성된 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>
    {/* 가져오는 동안 입력을 비활성화 */}
    <input v-model="id" type="number" :disabled="pending"/>

    <div v-if="status === 'idle'">
      사용자 ID를 입력하세요
    </div>

    <div v-else-if="pending">
      로딩 중 ...
    </div>

    <div v-else>
      {{ data }}
    </div>
  </div>
</template>

다른 반응형 값이 변경될 때 새로 고침을 강제하려면, 다른 값을 감시할 수도 있습니다.

즉시가 아님

useFetch composable은 호출되는 순간 데이터를 가져오기 시작합니다. 예를 들어 사용자 상호작용을 기다리기 위해 immediate: false로 설정하여 이를 방지할 수 있습니다.

이 경우, fetch 라이프사이클을 처리하기 위해 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">데이터 가져오기</button>
  </div>

  <div v-else-if="status === 'pending'">
    댓글 로딩 중...
  </div>

  <div v-else>
    {{ data }}
  </div>
</template>

더 세밀한 제어를 위해, status 변수는 다음과 같습니다:

  • idle: 가져오기가 시작되지 않았을 때
  • pending: 가져오기가 시작되었지만 아직 완료되지 않았을 때
  • error: 가져오기가 실패했을 때
  • success: 가져오기가 성공적으로 완료되었을 때

헤더 및 쿠키 전달

브라우저에서 $fetch를 호출할 때, cookie와 같은 사용자 헤더는 API에 직접 전송됩니다.

일반적으로 서버 측 렌더링 동안 보안 고려 사항으로 인해 $fetch는 사용자의 브라우저 쿠키를 포함하지 않으며, fetch 응답에서 쿠키를 전달하지 않습니다.

그러나 서버에서 상대 URL로 useFetch를 호출할 때, Nuxt는 useRequestFetch를 사용하여 헤더와 쿠키를 프록시합니다(전달되지 않아야 하는 헤더는 제외, 예: host).

SSR 응답에서 서버 측 API 호출의 쿠키 전달

내부 요청에서 클라이언트로 쿠키를 전달하거나 프록시하려면, 이를 직접 처리해야 합니다.

composables/fetch.ts
import { appendResponseHeader } from 'h3'
import type { H3Event } from 'h3'

export const fetchWithCookie = async (event: H3Event, url: string) => {
  /* 서버 엔드포인트에서 응답을 가져옵니다 */
  const res = await $fetch.raw(url)
  /* 응답에서 쿠키를 가져옵니다 */
  const cookies = res.headers.getSetCookie()
  /* 각 쿠키를 들어오는 요청에 첨부합니다 */
  for (const cookie of cookies) {
    appendResponseHeader(event, 'set-cookie', cookie)
  }
  /* 응답의 데이터를 반환합니다 */
  return res._data
}
// 이 composable은 자동으로 쿠키를 클라이언트에 전달합니다
const event = useRequestEvent()

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

onMounted(() => console.log(document.cookie))

Options API 지원

Nuxt는 Options API 내에서 asyncData 가져오기를 수행하는 방법을 제공합니다. 이를 위해 defineNuxtComponent 내에서 구성 요소 정의를 래핑해야 합니다.

export default defineNuxtComponent({
  /* 고유 키를 제공하려면 fetchKey 옵션을 사용하세요 */
  fetchKey: 'hello',
  async asyncData () {
    return {
      hello: await $fetch('/api/hello')
    }
  }
})

<script setup> 또는 <script setup lang="ts">는 Nuxt에서 Vue 컴포넌트를 선언하는 권장 방법입니다.

이것도 참고 api > utils > define-nuxt-component

서버에서 클라이언트로 데이터 직렬화

서버에서 가져온 데이터를 클라이언트로 전송하기 위해 useAsyncDatauseLazyAsyncData를 사용할 때(및 Nuxt 페이로드를 활용하는 기타 모든 것), 페이로드는 devalue로 직렬화됩니다. 이를 통해 기본 JSON뿐만 아니라 정규 표현식, 날짜, Map 및 Set, ref, reactive, shallowRef, shallowReactiveNuxtError와 같은 더 고급 데이터 유형을 직렬화하고 복원/역직렬화할 수 있습니다.

Nuxt에서 지원하지 않는 유형에 대해 자체 직렬화/역직렬화기를 정의할 수도 있습니다. useNuxtApp 문서에서 더 읽어볼 수 있습니다.

이것은 $fetch 또는 useFetch로 가져온 서버 경로에서 전달된 데이터에는 적용되지 않습니다 - 더 많은 정보는 다음 섹션을 참조하세요.

API 경로에서 데이터 직렬화

server 디렉토리에서 데이터를 가져올 때, 응답은 JSON.stringify를 사용하여 직렬화됩니다. 그러나 직렬화는 JavaScript 기본 유형으로만 제한되므로, Nuxt는 $fetchuseFetch의 반환 유형을 실제 값과 일치하도록 최선을 다합니다.

이것도 참고 developer.mozilla.org > en-US > docs > Web > JavaScript > Reference > Global_Objects > JSON > stringify

예제

server/api/foo.ts
export default defineEventHandler(() => {
  return new Date()
})
app.vue
// `data`의 유형은 Date 객체를 반환했음에도 불구하고 문자열로 추론됩니다
const { data } = await useFetch('/api/foo')

사용자 정의 직렬화 함수

직렬화 동작을 사용자 정의하려면, 반환된 객체에 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
// `data`의 유형은 다음과 같이 추론됩니다
// {
//   createdAt: {
//     year: number
//     month: number
//     day: number
//   }
// }
const { data } = await useFetch('/api/bar')

대체 직렬화기 사용

Nuxt는 현재 JSON.stringify에 대한 대체 직렬화기를 지원하지 않습니다. 그러나 페이로드를 일반 문자열로 반환하고 유형 안전성을 유지하기 위해 toJSON 메소드를 활용할 수 있습니다.

아래 예제에서는 superjson을 직렬화기로 사용합니다.

server/api/superjson.ts
import superjson from 'superjson'

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

    // 유형 변환을 우회합니다
    toJSON() {
      return this
    }
  }

  // superjson을 사용하여 출력을 문자열로 직렬화합니다
  return superjson.stringify(data) as unknown as typeof data
})
app.vue
import superjson from 'superjson'

// `date`는 { createdAt: Date }로 추론되며 Date 객체 메소드를 안전하게 사용할 수 있습니다
const { data } = await useFetch('/api/superjson', {
  transform: (value) => {
    return superjson.parse(value as unknown as string)
  },
})

레시피

POST 요청을 통한 SSE(서버 전송 이벤트) 소비

GET 요청을 통해 SSE를 소비하는 경우, EventSource 또는 VueUse composable useEventSource를 사용할 수 있습니다.

POST 요청을 통해 SSE를 소비할 때, 연결을 수동으로 처리해야 합니다. 다음은 그 방법입니다:

// SSE 엔드포인트에 POST 요청을 보냅니다
const response = await $fetch<ReadableStream>('/chats/ask-ai', {
  method: 'POST',
  body: {
    query: "Hello AI, how are you?",
  },
  responseType: 'stream',
})

// TextDecoderStream을 사용하여 데이터를 텍스트로 가져오기 위해 응답에서 새로운 ReadableStream을 생성합니다
const reader = response.pipeThrough(new TextDecoderStream()).getReader()

// 데이터를 받을 때마다 청크를 읽습니다
while (true) {
  const { value, done } = await reader.read()

  if (done)
    break

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

병렬 요청 수행

요청이 서로 의존하지 않는 경우, Promise.all()을 사용하여 병렬로 수행하여 성능을 향상시킬 수 있습니다.

const { data } = await useAsyncData(() => {
  return Promise.all([
    $fetch("/api/comments/"), 
    $fetch("/api/author/12")
  ]);
});

const comments = computed(() => data.value?.[0]);
const author = computed(() => data.value?.[1]);