Nuxt에는 브라우저나 서버 환경에서 데이터 가져오기를 수행하기 위한 두 개의 composable과 내장 라이브러리가 함께 제공됩니다: useFetch, useAsyncData, $fetch.
간단히 말하면:
$fetch는 네트워크 요청을 보내는 가장 단순한 방법입니다.useFetch는 $fetch를 감싼 래퍼로, 유니버설 렌더링에서 데이터를 한 번만 가져옵니다.useAsyncData는 useFetch와 비슷하지만 더 세밀한 제어를 제공합니다.useFetch와 useAsyncData는 공통된 옵션과 패턴을 공유하며, 이는 마지막 섹션에서 자세히 설명합니다.
useFetch와 useAsyncData가 필요한 이유Nuxt는 서버와 클라이언트 환경 모두에서 동형(isomorphic) (또는 유니버설) 코드를 실행할 수 있는 프레임워크입니다. 만약 Vue 컴포넌트의 setup 함수에서 데이터 가져오기를 수행하기 위해 $fetch 함수를 사용한다면, 이는 데이터를 두 번 가져오게 만들 수 있습니다. 한 번은 서버에서 HTML을 렌더링하기 위해, 또 한 번은 HTML이 하이드레이션될 때 클라이언트에서입니다. 이는 하이드레이션 문제를 일으키고, 상호작용 가능 시점을 늦추며, 예측 불가능한 동작을 유발할 수 있습니다.
useFetch와 useAsyncData composable은 API 호출이 서버에서 이루어진 경우, 그 데이터를 페이로드를 통해 클라이언트로 전달되도록 보장함으로써 이 문제를 해결합니다.
페이로드는 useNuxtApp().payload를 통해 접근할 수 있는 JavaScript 객체입니다. 이는 하이드레이션 중 브라우저에서 코드가 실행될 때 동일한 데이터를 다시 가져오는 것을 피하기 위해 클라이언트에서 사용됩니다.
<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 == undefined">
No data
</div>
<div v-else>
<form @submit="handleFormSubmit">
<!-- form input 태그들 -->
</form>
</div>
</template>
위 예시에서 useFetch는 요청이 서버에서 발생하고 브라우저로 올바르게 전달되도록 보장합니다. $fetch에는 이러한 메커니즘이 없으며, 요청이 오직 브라우저에서만 이루어질 때 사용하는 것이 더 좋은 선택입니다.
Nuxt는 Vue의 <Suspense> 컴포넌트를 내부적으로 사용하여 모든 비동기 데이터가 뷰에 사용 가능해지기 전까지 네비게이션을 막습니다. 데이터 가져오기 composable들은 이 기능을 활용하고, 호출마다 가장 적합한 방식을 사용할 수 있도록 도와줍니다.
<NuxtLoadingIndicator>를 추가할 수 있습니다.$fetchNuxt는 ofetch 라이브러리를 포함하고 있으며, 애플리케이션 전역에서 $fetch 별칭으로 자동 임포트됩니다.
<script setup lang="ts">
async function addTodo () {
const todo = await $fetch('/api/todos', {
method: 'POST',
body: {
// 내 todo 데이터
},
})
}
</script>
$fetch만 사용하면 네트워크 호출 중복 제거 및 네비게이션 방지를 제공하지 않는다는 점에 주의하세요. $fetch를 클라이언트 측 상호작용(이벤트 기반)에 사용하거나 useAsyncData와 함께 사용하는 것이 권장됩니다.서버에서 useFetch를 호출할 때, Nuxt는 useRequestFetch를 사용하여 클라이언트 헤더와 쿠키를 프록시합니다 (host와 같이 전달되지 말아야 할 헤더는 예외).
<script setup lang="ts">
const { data } = await useFetch('/api/echo')
</script>
// /api/echo.ts
export default defineEventHandler(event => parseCookies(event))
또는, 아래 예시는 서버 측 요청(클라이언트에서 시작된)에서 쿠키에 접근하고 이를 API로 전송하기 위해 useRequestHeaders를 사용하는 방법을 보여줍니다. 동형 $fetch 호출을 사용함으로써, API 엔드포인트가 사용자의 브라우저에서 원래 전송된 것과 동일한 cookie 헤더에 접근할 수 있도록 보장합니다. 이는 useFetch를 사용하지 않을 때에만 필요합니다.
<script setup lang="ts">
const headers = useRequestHeaders(['cookie'])
async function getCurrentUser () {
return await $fetch('/api/me', { headers })
}
</script>
useRequestFetch를 사용해 헤더를 호출에 자동으로 프록시할 수도 있습니다.host, acceptcontent-length, content-md5, content-typex-forwarded-host, x-forwarded-port, x-forwarded-protocf-connecting-ip, cf-rayuseFetchuseFetch composable은 setup 함수에서 SSR-safe 네트워크 호출을 하기 위해 내부적으로 $fetch를 사용합니다.
<script setup lang="ts">
const { data: count } = await useFetch('/api/count')
</script>
<template>
<p>Page visits: {{ count }}</p>
</template>
이 composable은 useAsyncData composable과 $fetch 유틸리티를 감싼 래퍼입니다.
useAsyncDatauseAsyncData composable은 비동기 로직을 감싸고, 해당 로직이 resolve된 후 결과를 반환하는 역할을 합니다.
useFetch(url)은 거의 useAsyncData(url, () => event.$fetch(url))와 동일합니다. event.fetch에 대해 더 알고 싶다면 useRequestFetch를 참고하세요.)useFetch composable을 사용하는 것이 적절하지 않은 경우가 있습니다. 예를 들어 CMS나 서드파티가 자체 쿼리 레이어를 제공하는 경우입니다. 이때는 useAsyncData를 사용해 호출을 감싸면서도 composable이 제공하는 이점을 유지할 수 있습니다.
<script setup lang="ts">
const { data, error } = await useAsyncData('users', () => myGetFunction('users'))
// 이렇게도 가능합니다:
const { data, error } = await useAsyncData(() => myGetFunction('users'))
</script>
useAsyncData의 첫 번째 인자는 두 번째 인자인 쿼리 함수의 응답을 캐시하는 데 사용되는 고유 키입니다. 이 키는 쿼리 함수를 직접 전달하여 무시할 수 있으며, 이 경우 키는 자동으로 생성됩니다.
useAsyncData가 호출된 파일과 줄 번호만 고려하기 때문에, 특히 useAsyncData를 감싼 커스텀 composable을 만들 때와 같이 원치 않는 동작을 피하기 위해 항상 직접 키를 만드는 것이 좋습니다.
useNuxtData를 사용해 컴포넌트 간에 동일한 데이터를 공유하거나 특정 데이터를 새로고침하는 데 유용합니다.<script setup lang="ts">
const { id } = useRoute().params
const { data, error } = await useAsyncData(`user:${id}`, () => {
return myGetFunction('users', { id })
})
</script>
useAsyncData composable은 여러 $fetch 요청이 완료될 때까지 감싸서 기다린 다음, 그 결과를 처리하는 훌륭한 방법입니다.
<script setup lang="ts">
const { data: discounts, status } = await useAsyncData('cart-discount', async (_nuxtApp, { signal }) => {
const [coupons, offers] = await Promise.all([
$fetch('/cart/coupons', { signal }),
$fetch('/cart/offers', { signal }),
])
return { coupons, offers }
})
// discounts.value.coupons
// discounts.value.offers
</script>
useAsyncData는 데이터를 가져오고 캐시하기 위한 것이며, Pinia 액션 호출과 같은 사이드 이펙트를 트리거하는 용도가 아닙니다. 이는 nullish 값으로 반복 실행되는 등 의도치 않은 동작을 유발할 수 있습니다. 사이드 이펙트를 트리거해야 한다면, 이를 위해 callOnce 유틸리티를 사용하세요.<script setup lang="ts">
const offersStore = useOffersStore()
// 이렇게 하면 안 됩니다
await useAsyncData(() => offersStore.getOffer(route.params.slug))
</script>
useFetch와 useAsyncData는 아래에 나열된 동일한 반환 값을 가집니다.
data: 전달된 비동기 함수의 결과.refresh/execute: handler 함수가 반환한 데이터를 새로고침하는 데 사용할 수 있는 함수.clear: data를 undefined(또는 제공된 경우 options.default()의 값)로 설정하고, error를 undefined로 설정하며, status를 idle로 설정하고, 현재 진행 중인 요청을 취소된 것으로 표시하는 데 사용할 수 있는 함수.error: 데이터 가져오기에 실패한 경우 에러 객체.status: 데이터 요청의 상태를 나타내는 문자열 ("idle", "pending", "success", "error").data, error, status는 <script setup>에서 .value로 접근할 수 있는 Vue ref입니다.기본적으로 Nuxt는 refresh가 완료될 때까지 기다린 후에야 다시 실행할 수 있도록 합니다.
server: false로) 서버에서 데이터를 가져오지 않은 경우, 하이드레이션이 완료될 때까지 데이터는 가져오지지 않습니다. 이는 클라이언트 측에서 useFetch를 await하더라도 <script setup> 내에서 data는 null 상태로 남는다는 의미입니다.useAsyncData와 useFetch는 동일한 객체 타입을 반환하며, 마지막 인자로 공통된 옵션 집합을 받습니다. 이 옵션들은 네비게이션 차단, 캐싱, 실행 방식 등 composable의 동작을 제어하는 데 도움이 됩니다.
기본적으로 데이터 가져오기 composable들은 Vue의 Suspense를 사용하여 비동기 함수가 resolve될 때까지 기다린 후에 새 페이지로 네비게이션합니다. 이 기능은 lazy 옵션을 사용해 클라이언트 측 네비게이션에서 무시할 수 있습니다. 이 경우 status 값을 사용해 로딩 상태를 수동으로 처리해야 합니다.
<script setup lang="ts">
const { status, data: posts } = useFetch('/api/posts', {
lazy: true,
})
</script>
<template>
<!-- 로딩 상태를 직접 처리해야 합니다 -->
<div v-if="status === 'pending'">
Loading ...
</div>
<div v-else>
<div v-for="post in posts">
<!-- 무언가를 수행 -->
</div>
</div>
</template>
대안으로, 동일한 작업을 수행하기 위한 편의 메서드로 useLazyFetch와 useLazyAsyncData를 사용할 수 있습니다.
<script setup lang="ts">
const { status, data: posts } = useLazyFetch('/api/posts')
</script>
기본적으로 데이터 가져오기 composable들은 클라이언트와 서버 환경 모두에서 비동기 함수를 실행합니다. server 옵션을 false로 설정하면 클라이언트 측에서만 호출을 수행합니다. 초기 로드 시에는 하이드레이션이 완료되기 전까지 데이터가 가져와지지 않으므로, 대기 상태를 처리해야 합니다. 하지만 이후 클라이언트 측 네비게이션에서는 페이지를 로드하기 전에 데이터가 await됩니다.
lazy 옵션과 함께 사용하면, 첫 렌더링에 필요하지 않은 데이터(예: SEO에 민감하지 않은 데이터)에 유용합니다.
/* 이 호출은 하이드레이션 전에 수행됩니다 */
const articles = await useFetch('/api/article')
/* 이 호출은 클라이언트에서만 수행됩니다 */
const { status, data: comments } = useFetch('/api/comments', {
lazy: true,
server: false,
})
useFetch composable은 setup 메서드에서 호출되거나, 라이프사이클 훅 내 함수의 최상위 레벨에서 직접 호출되도록 설계되었습니다. 그렇지 않은 경우에는 $fetch 메서드를 사용해야 합니다.
pick 옵션은 composable에서 반환받고자 하는 필드만 선택함으로써 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 }))
},
})
pick과 transform 모두 원치 않는 데이터가 처음에 가져와지는 것을 막지는 못합니다. 하지만 서버에서 클라이언트로 전송되는 페이로드에 원치 않는 데이터가 추가되는 것은 방지합니다.useFetch와 useAsyncData는 동일한 데이터를 다시 가져오는 것을 방지하기 위해 키를 사용합니다.
useFetch는 제공된 URL을 키로 사용합니다. 또는 마지막 인자로 전달되는 options 객체에 key 값을 제공할 수 있습니다.useAsyncData는 첫 번째 인자가 문자열인 경우 이를 키로 사용합니다. 첫 번째 인자가 쿼리를 수행하는 handler 함수라면, useAsyncData 인스턴스의 파일 이름과 줄 번호에 고유한 키가 자동으로 생성됩니다.useNuxtData를 사용할 수 있습니다.여러 컴포넌트가 동일한 키로 useAsyncData 또는 useFetch를 사용할 경우, 동일한 data, error, status ref를 공유합니다. 이는 컴포넌트 간 일관성을 보장하지만, 일부 옵션은 일관되게 유지되어야 합니다.
다음 옵션들은 동일한 키를 사용하는 모든 호출에서 반드시 일관되어야 합니다:
handler 함수deep 옵션transform 함수pick 배열getCachedData 함수default 값// ❌ 이는 개발 환경에서 경고를 발생시킵니다
const { data: users1 } = useAsyncData('users', (_nuxtApp, { signal }) => $fetch('/api/users', { signal }), { deep: false })
const { data: users2 } = useAsyncData('users', (_nuxtApp, { signal }) => $fetch('/api/users', { signal }), { deep: true })
다음 옵션들은 경고 없이 안전하게 다르게 설정할 수 있습니다:
serverlazyimmediatededupewatch// ✅ 이는 허용됩니다
const { data: users1 } = useAsyncData('users', (_nuxtApp, { signal }) => $fetch('/api/users', { signal }), { immediate: true })
const { data: users2 } = useAsyncData('users', (_nuxtApp, { signal }) => $fetch('/api/users', { signal }), { immediate: false })
독립적인 인스턴스가 필요하다면, 다른 키를 사용하세요:
// 이들은 완전히 독립적인 인스턴스입니다
const { data: users1 } = useAsyncData('users-1', (_nuxtApp, { signal }) => $fetch('/api/users', { signal }))
const { data: users2 } = useAsyncData('users-2', (_nuxtApp, { signal }) => $fetch('/api/users', { signal }))
키로 computed ref, 일반 ref 또는 getter 함수를 사용할 수 있어, 의존성이 변경될 때 자동으로 업데이트되는 동적 데이터 가져오기가 가능합니다:
// computed 속성을 키로 사용
const userId = ref('123')
const { data: user } = useAsyncData(
computed(() => `user-${userId.value}`),
() => fetchUser(userId.value),
)
// userId가 변경되면 데이터가 자동으로 다시 가져와지고
// 다른 컴포넌트에서 사용하지 않는 경우 이전 데이터는 정리됩니다
userId.value = '456'
데이터를 수동으로 가져오거나 새로고침하려면, composable이 제공하는 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의 별칭으로, 완전히 동일하게 동작하지만, 즉시 실행되지 않을 때 더 의미론적인 이름입니다.
clearNuxtData와 refreshNuxtData를 참고하세요.어떤 이유로든 제공된 데이터를 지우고 싶지만, clearNuxtData에 전달할 특정 키를 알고 싶지 않은 경우, composable이 제공하는 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 가능한 요소에 사용할 수 있습니다.
<script setup lang="ts">
const id = ref(1)
const { data, error, refresh } = await useFetch('/api/users', {
/* id가 변경되면 다시 가져오기를 트리거합니다 */
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을 변경해야 한다면, 대신 computed URL을 사용하는 것이 좋습니다.
반응형 fetch 옵션이 제공되면, 이들은 자동으로 감시되어 재요청을 트리거합니다. 어떤 경우에는 watch: false를 지정해 이 동작을 옵트아웃하는 것이 유용할 수 있습니다.
const id = ref(1)
// id가 변경되어도 자동으로 다시 가져오지 않습니다
const { data, execute } = await useFetch('/api/users', {
query: { id }, // id는 기본적으로 감시됩니다
watch: false, // id의 자동 감시를 비활성화
})
// 재요청을 트리거하지 않음
id.value = 2
때때로 반응형 값으로부터 URL을 계산하고, 이 값들이 변경될 때마다 데이터를 새로 가져와야 할 수 있습니다. 복잡하게 처리하는 대신, 각 파라미터를 반응형 값으로 연결할 수 있습니다. Nuxt는 반응형 값을 자동으로 사용하고, 값이 변경될 때마다 다시 가져옵니다.
<script setup lang="ts">
const id = ref(null)
const { data, status } = useLazyFetch('/api/user', {
query: {
user_id: id,
},
})
</script>
더 복잡한 URL 구성이 필요한 경우, URL 문자열을 반환하는 computed getter 콜백을 사용할 수 있습니다.
의존성이 변경될 때마다 새로 구성된 URL을 사용해 데이터를 가져옵니다. 이를 not-immediate와 결합하면, 반응형 요소가 변경될 때까지 가져오기를 기다릴 수 있습니다.
<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'">
Type an user ID
</div>
<div v-else-if="pending">
Loading ...
</div>
<div v-else>
{{ data }}
</div>
</div>
</template>
다른 반응형 값이 변경될 때 강제로 새로고침해야 한다면, 다른 값을 감시할 수도 있습니다.
useFetch composable은 호출되는 즉시 데이터 가져오기를 시작합니다. 예를 들어 사용자 상호작용을 기다리기 위해, 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: 가져오기가 성공적으로 완료된 상태브라우저에서 $fetch를 호출할 때, cookie와 같은 사용자 헤더는 API로 직접 전송됩니다.
일반적으로 서버 사이드 렌더링 중에는 보안상의 이유로 $fetch가 사용자의 브라우저 쿠키를 포함하지 않으며, fetch 응답의 쿠키를 전달하지도 않습니다.
그러나 서버에서 상대 URL로 useFetch를 호출할 때, Nuxt는 useRequestFetch를 사용해 헤더와 쿠키를 프록시합니다 (host와 같이 전달되지 말아야 할 헤더는 예외).
내부 요청에서 클라이언트로 쿠키를 전달/프록시하려면, 이를 직접 처리해야 합니다.
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()
/* 각 쿠키를 들어오는 Request에 첨부합니다 */
for (const cookie of cookies) {
appendResponseHeader(event, 'set-cookie', cookie)
}
/* 응답의 데이터를 반환합니다 */
return res._data
}
<script setup lang="ts">
// 이 composable은 쿠키를 클라이언트로 자동 전달합니다
const event = useRequestEvent()
const { data: result } = await useAsyncData(() => fetchWithCookie(event!, '/api/with-cookie'))
onMounted(() => console.log(document.cookie))
</script>
Nuxt는 Options API 내에서 asyncData 가져오기를 수행하는 방법을 제공합니다. 이를 사용하려면 컴포넌트 정의를 defineNuxtComponent로 감싸야 합니다.
<script>
export default defineNuxtComponent({
/* 고유 키를 제공하기 위해 fetchKey 옵션을 사용 */
fetchKey: 'hello',
async asyncData () {
return {
hello: await $fetch('/api/hello'),
}
},
})
</script>
<script setup> 또는 <script setup lang="ts">를 사용하는 것입니다.서버에서 가져온 데이터를 클라이언트로 전송하기 위해 useAsyncData와 useLazyAsyncData를 사용할 때(및 Nuxt payload를 활용하는 기타 모든 경우), 페이로드는 devalue로 직렬화됩니다. 이를 통해 기본 JSON뿐만 아니라 정규식, Date, Map, Set, ref, reactive, shallowRef, shallowReactive, NuxtError 등 더 고급 데이터 타입도 직렬화하고 복원/역직렬화할 수 있습니다.
Nuxt에서 지원하지 않는 타입에 대해 자체 serializer/deserializer를 정의하는 것도 가능합니다. 자세한 내용은 useNuxtApp 문서를 참고하세요.
$fetch 또는 useFetch로 가져온 서버 라우트에서 전달되는 데이터에는 _적용되지 않는다_는 점에 유의하세요. 더 자세한 내용은 다음 섹션을 참고하세요.server 디렉터리에서 데이터를 가져올 때, 응답은 JSON.stringify를 사용해 직렬화됩니다. 하지만 직렬화는 JavaScript 원시 타입에만 제한되므로, Nuxt는 $fetch와 useFetch의 반환 타입을 실제 값과 일치시키기 위해 최선을 다합니다.
export default defineEventHandler(() => {
return new Date()
})
<script setup lang="ts">
// Date 객체를 반환했지만, `data`의 타입은 문자열로 추론됩니다
const { data } = await useFetch('/api/foo')
</script>
직렬화 동작을 커스터마이징하려면, 반환 객체에 toJSON 함수를 정의할 수 있습니다. toJSON 메서드를 정의하면, Nuxt는 이 함수의 반환 타입을 존중하며 타입을 변환하려 하지 않습니다.
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
})
<script setup lang="ts">
// `data`의 타입은 다음과 같이 추론됩니다
// {
// createdAt: {
// year: number
// month: number
// day: number
// }
// }
const { data } = await useFetch('/api/bar')
</script>
Nuxt는 현재 JSON.stringify를 대체하는 serializer를 지원하지 않습니다. 하지만 페이로드를 일반 문자열로 반환하고, 타입 안전성을 유지하기 위해 toJSON 메서드를 활용할 수 있습니다.
아래 예시에서는 superjson을 serializer로 사용합니다.
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
})
<script setup lang="ts">
import superjson from 'superjson'
// `date`는 { createdAt: Date }로 추론되며, Date 객체 메서드를 안전하게 사용할 수 있습니다
const { data } = await useFetch('/api/superjson', {
transform: (value) => {
return superjson.parse(value as unknown as string)
},
})
</script>
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((_nuxtApp, { signal }) => {
return Promise.all([
$fetch('/api/comments/', { signal }),
$fetch('/api/author/12', { signal }),
])
})
const comments = computed(() => data.value?.[0])
const author = computed(() => data.value?.[1])