데이터 패칭
Nuxt는 브라우저 또는 서버 환경에서 데이터 패칭을 수행할 수 있도록 두 가지 컴포저블과 내장 라이브러리를 제공합니다: useFetch, useAsyncData, 그리고 $fetch.
간단히 요약하면:
$fetch는 네트워크 요청을 하는 가장 간단한 방법입니다.useFetch는$fetch의 래퍼로, 유니버설 렌더링에서 데이터를 한 번만 패칭합니다.useAsyncData는useFetch와 비슷하지만 더 세밀한 제어를 제공합니다.
useFetch와 useAsyncData는 공통된 옵션과 패턴을 공유하며, 마지막 섹션에서 자세히 다룹니다.
useFetch와 useAsyncData의 필요성
Nuxt는 서버와 클라이언트 환경 모두에서 동작할 수 있는 이소모픽(또는 유니버설) 코드를 실행할 수 있는 프레임워크입니다. 만약 Vue 컴포넌트의 setup 함수에서 $fetch 함수를 사용하여 데이터 패칭을 수행하면, 데이터가 서버에서 한 번(HTML 렌더링 시), 클라이언트에서 한 번(HTML이 하이드레이션될 때) 두 번 패칭될 수 있습니다. 이는 하이드레이션 문제를 일으키고, 상호작용까지의 시간을 늘리며, 예측할 수 없는 동작을 유발할 수 있습니다.
useFetch와 useAsyncData 컴포저블은 API 호출이 서버에서 발생한 경우, 데이터를 페이로드로 클라이언트에 전달하여 이 문제를 해결합니다.
페이로드는 useNuxtApp().payload를 통해 접근할 수 있는 자바스크립트 객체입니다. 이는 하이드레이션 중 브라우저에서 코드가 실행될 때 동일한 데이터를 다시 패칭하지 않도록 클라이언트에서 사용됩니다.
<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> 컴포넌트를 사용하여 모든 비동기 데이터가 뷰에 제공되기 전까지 네비게이션을 방지합니다. 데이터 패칭 컴포저블을 사용하면 이 기능을 활용할 수 있으며, 호출별로 가장 적합한 방식을 사용할 수 있습니다.
<NuxtLoadingIndicator>를 추가할 수 있습니다.$fetch
Nuxt는 ofetch 라이브러리를 포함하고 있으며, $fetch 별칭으로 애플리케이션 전역에서 자동 임포트됩니다.
<script setup lang="ts">
async function addTodo() {
const todo = await $fetch('/api/todos', {
method: 'POST',
body: {
// 내 할 일 데이터
}
})
}
</script>
$fetch만 사용할 경우 네트워크 호출 중복 제거 및 네비게이션 방지가 제공되지 않습니다. 초기 컴포넌트 데이터 패칭 시에는
useAsyncData와 함께 사용하거나, 클라이언트 사이드 상호작용(이벤트 기반)에서 $fetch를 사용하는 것이 권장됩니다.클라이언트 헤더를 API로 전달하기
서버에서 useFetch를 호출할 때, Nuxt는 useRequestFetch를 사용하여 클라이언트 헤더와 쿠키를 프록시합니다(단, host와 같이 전달되지 않아야 하는 헤더는 제외).
<script setup lang="ts">
const { data } = await useFetch('/api/echo');
</script>
// /api/echo.ts
export default defineEventHandler(event => parseCookies(event))
아래 예시는 useRequestHeaders를 사용하여 서버 사이드 요청(클라이언트에서 유래)에서 쿠키를 API로 접근 및 전송하는 방법을 보여줍니다. 이소모픽 $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-ray
useFetch
useFetch 컴포저블은 setup 함수에서 SSR 안전한 네트워크 호출을 위해 내부적으로 $fetch를 사용합니다.
<script setup lang="ts">
const { data: count } = await useFetch('/api/count')
</script>
<template>
<p>페이지 방문 수: {{ count }}</p>
</template>
이 컴포저블은 useAsyncData 컴포저블과 $fetch 유틸리티의 래퍼입니다.
useAsyncData
useAsyncData 컴포저블은 비동기 로직을 래핑하고, 결과가 해결되면 반환하는 역할을 합니다.
useFetch(url)은 거의 useAsyncData(url, () => event.$fetch(url))과 동일합니다. 가장 일반적인 사용 사례를 위한 개발자 경험상의 설탕입니다. (
event.fetch에 대해 더 알고 싶다면 useRequestFetch를 참고하세요.)useFetch 컴포저블을 사용하는 것이 적합하지 않은 경우가 있습니다. 예를 들어, CMS나 서드파티가 자체 쿼리 레이어를 제공하는 경우입니다. 이 경우, useAsyncData를 사용하여 호출을 래핑하고, 컴포저블이 제공하는 이점을 계속 누릴 수 있습니다.
<script setup lang="ts">
const { data, error } = await useAsyncData('users', () => myGetFunction('users'))
// 이렇게도 가능합니다:
const { data, error } = await useAsyncData(() => myGetFunction('users'))
</script>
useAsyncData의 첫 번째 인자는 두 번째 인자인 쿼리 함수의 응답을 캐싱하는 데 사용되는 고유 키입니다. 쿼리 함수를 직접 전달하면 이 키는 무시되며, 자동으로 생성됩니다.
자동 생성된 키는
useAsyncData가 호출된 파일과 라인만을 고려하므로, 원치 않는 동작을 피하기 위해(예: useAsyncData를 래핑한 커스텀 컴포저블을 만들 때) 항상 직접 키를 생성하는 것이 좋습니다.
키를 설정하면
useNuxtData를 사용하여 여러 컴포넌트 간에 동일한 데이터를 공유하거나, 특정 데이터 새로고침에 유용합니다.<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는 데이터 패칭 및 캐싱을 위한 것이며, 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로,error를null로,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는 동일한 객체 타입을 반환하며, 마지막 인자로 공통된 옵션 세트를 받습니다. 이 옵션들은 네비게이션 차단, 캐싱, 실행 등 컴포저블의 동작을 제어하는 데 도움이 됩니다.
Lazy
기본적으로 데이터 패칭 컴포저블은 비동기 함수가 해결될 때까지 Vue의 Suspense를 사용하여 새 페이지로의 네비게이션을 대기합니다. 이 기능은 lazy 옵션을 사용하여 클라이언트 사이드 네비게이션에서 무시할 수 있습니다. 이 경우, status 값을 사용하여 로딩 상태를 직접 처리해야 합니다.
<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>
useLazyFetch와 useLazyAsyncData를 사용하여 동일한 작업을 편리하게 수행할 수도 있습니다.
<script setup lang="ts">
const { status, data: posts } = useLazyFetch('/api/posts')
</script>
클라이언트 전용 패칭
기본적으로 데이터 패칭 컴포저블은 클라이언트와 서버 환경 모두에서 비동기 함수를 실행합니다. server 옵션을 false로 설정하면 클라이언트 사이드에서만 호출이 수행됩니다. 초기 로드 시에는 하이드레이션이 완료되기 전까지 데이터가 패칭되지 않으므로, 대기 상태를 직접 처리해야 합니다. 하지만 이후 클라이언트 사이드 네비게이션에서는 데이터를 대기한 후 페이지가 로드됩니다.
lazy 옵션과 결합하면, 첫 렌더에 필요하지 않은 데이터(예: SEO에 민감하지 않은 데이터)에 유용합니다.
/* 이 호출은 하이드레이션 전에 수행됩니다 */
const articles = await useFetch('/api/article')
/* 이 호출은 클라이언트에서만 수행됩니다 */
const { status, data: comments } = useFetch('/api/comments', {
lazy: true,
server: false
})
useFetch 컴포저블은 setup 메서드에서 호출하거나, 라이프사이클 훅의 함수 최상위에서 직접 호출하는 것이 적합합니다. 그렇지 않으면 $fetch 메서드를 사용해야 합니다.
페이로드 크기 최소화
pick 옵션을 사용하면, 컴포저블에서 반환되는 필드 중 원하는 것만 선택하여 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는 첫 번째 인자가 문자열이면 이를 키로 사용합니다. 첫 번째 인자가 쿼리를 수행하는 핸들러 함수라면, 해당useAsyncData인스턴스의 파일명과 라인 번호에 고유한 키가 자동 생성됩니다.
useNuxtData를 사용할 수 있습니다.공유 상태 및 옵션 일관성
여러 컴포넌트가 동일한 키로 useAsyncData 또는 useFetch를 사용할 경우, 동일한 data, error, status ref를 공유합니다. 이는 컴포넌트 간 일관성을 보장하지만, 일부 옵션은 일관되어야 합니다.
다음 옵션들은 동일한 키로 호출 시 반드시 일치해야 합니다:
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 })
다음 옵션들은 다르게 설정해도 경고가 발생하지 않습니다:
serverlazyimmediatededupewatch
// ✅ 이렇게는 허용됩니다
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'))
반응형 키
키로 computed ref, 일반 ref, getter 함수를 사용할 수 있어, 의존성이 변경될 때마다 자동으로 데이터를 패칭하는 동적 데이터 패칭이 가능합니다:
// computed 속성을 키로 사용
const userId = ref('123')
const { data: user } = useAsyncData(
computed(() => `user-${userId.value}`),
() => fetchUser(userId.value)
)
// userId가 변경되면 데이터가 자동으로 재패칭되고,
// 다른 컴포넌트에서 사용하지 않는 경우 이전 데이터는 정리됩니다
userId.value = '456'
Refresh와 execute
데이터를 수동으로 패칭하거나 새로고침하려면, 컴포저블에서 제공하는 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와 동일하게 동작합니다.
clearNuxtData와 refreshNuxtData를 참고하세요.Clear
특정 키를 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 옵션을 사용하세요. 하나 또는 여러 감시 가능한 요소에 사용할 수 있습니다.
<script setup lang="ts">
const id = ref(1)
const { data, error, refresh } = await useFetch('/api/users', {
/* id가 변경되면 재패칭이 트리거됩니다 */
watch: [id]
})
</script>
반응형 값을 감시해도 패칭되는 URL이 변경되지 않는다는 점에 유의하세요. 예를 들어, 아래 코드는 사용자의 초기 ID만 계속 패칭합니다. URL은 함수가 호출되는 시점에 생성되기 때문입니다.
<script setup lang="ts">
const id = ref(1)
const { data, error, refresh } = await useFetch(`/api/users/${id.value}`, {
watch: [id]
})
</script>
반응형 값에 따라 URL을 변경해야 한다면, computed URL을 사용하는 것이 좋습니다.
Computed URL
때로는 반응형 값으로부터 URL을 계산하고, 값이 변경될 때마다 데이터를 새로고침해야 할 수 있습니다. 복잡하게 처리하지 않고, 각 파라미터를 반응형 값으로 연결할 수 있습니다. Nuxt는 반응형 값을 자동으로 사용하여 값이 변경될 때마다 재패칭합니다.
<script setup lang="ts">
const id = ref(null)
const { data, status } = useLazyFetch('/api/user', {
query: {
user_id: id
}
})
</script>
더 복잡한 URL 구성이 필요한 경우, computed getter 콜백을 사용하여 URL 문자열을 반환할 수 있습니다.
의존성이 변경될 때마다, 새로 구성된 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'">
사용자 ID를 입력하세요
</div>
<div v-else-if="pending">
로딩 중 ...
</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">데이터 가져오기</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 호출의 쿠키 전달
내부 요청에서 클라이언트로 쿠키를 전달/프록시하려면, 직접 처리해야 합니다.
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">
// 이 컴포저블은 쿠키를 클라이언트로 자동 전달합니다
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({
/* 고유 키를 제공하려면 fetchKey 옵션을 사용하세요 */
fetchKey: 'hello',
async asyncData () {
return {
hello: await $fetch('/api/hello')
}
}
})
</script>
<script setup> 또는 <script setup lang="ts">를 사용하는 것이 권장됩니다.서버에서 클라이언트로 데이터 직렬화
서버에서 패칭한 데이터를 클라이언트로 전송할 때 useAsyncData와 useLazyAsyncData(및 Nuxt 페이로드를 활용하는 모든 것)는 devalue로 페이로드를 직렬화합니다. 이를 통해 기본 JSON뿐만 아니라 정규식, Date, Map, Set, ref, reactive, shallowRef, shallowReactive, NuxtError 등 더 고급 데이터도 직렬화 및 복원/역직렬화할 수 있습니다.
Nuxt에서 지원하지 않는 타입에 대해 직접 직렬화/역직렬화 함수를 정의할 수도 있습니다. 자세한 내용은 useNuxtApp 문서를 참고하세요.
$fetch 또는 useFetch로 패칭한 서버 라우트의 데이터에는 적용되지 않습니다. 자세한 내용은 다음 섹션을 참고하세요.API 라우트에서 데이터 직렬화
server 디렉토리에서 데이터를 패칭할 때, 응답은 JSON.stringify로 직렬화됩니다. 하지만 직렬화는 자바스크립트 원시 타입에만 제한되므로, Nuxt는 $fetch와 useFetch의 반환 타입이 실제 값과 일치하도록 최선을 다합니다.
예시
export default defineEventHandler(() => {
return new Date()
})
<script setup lang="ts">
// Date 객체를 반환했지만, `data`의 타입은 string으로 추론됩니다
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 외의 대체 직렬화기를 지원하지 않습니다. 하지만, 페이로드를 일반 문자열로 반환하고, 타입 안전성을 유지하기 위해 toJSON 메서드를 활용할 수 있습니다.
아래 예시에서는 superjson을 직렬화기로 사용합니다.
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>
레시피
POST 요청을 통한 SSE(Server-Sent Events) 소비
EventSource 또는 VueUse 컴포저블 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('수신:', 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]);