본문으로 건너뛰기
개발 머꼬
개발 노트Nuxt.js
hohyeon.dev14

Nuxt에서 $fetch로 데이터를 가져왔더니 요청이 두 번 나간 이유

  • #Common Pitfall
  • #Engineering Note
  • #Nuxt.js
  • #Vue

문제 발생

Nuxt 페이지에서 목록을 불러오는데, 네트워크 탭에 같은 API 요청이 두 번 찍혔습니다. 서버 로그에도 한 번, 브라우저에도 한 번이었습니다.

<script setup>
const posts = await $fetch("/api/posts");   // 서버에서 한 번, 클라이언트에서 또 한 번
</script>

원인 분석

공식 문서가 이 상황을 그대로 설명합니다 — 컴포넌트의 setup 함수 안에서 $fetch로 데이터를 가져오면 데이터가 두 번 요청될 수 있습니다. HTML을 만들기 위해 서버에서 한 번, 그 HTML을 하이드레이션할 때 클라이언트에서 다시 한 번입니다.

$fetch는 그냥 요청을 보내는 함수입니다. 서버에서 받아온 결과를 클라이언트로 넘겨주는 장치가 없으니, 브라우저는 자기가 아는 게 없어서 처음부터 다시 받습니다.

useFetch/useAsyncData는 그 장치를 갖고 있습니다. 문서의 표현대로 API 호출이 서버에서 일어나면 그 데이터는 payload에 실려 클라이언트로 전달됩니다. 그래서 하이드레이션 때 다시 요청하지 않고, 하이드레이션 불일치도 생기지 않습니다.

문서는 $fetch만 쓰면 네트워크 호출 중복 제거와 내비게이션 차단이 제공되지 않는다고도 명시합니다.

해결 방안

  1. 초기 데이터는 useFetch로 가져옵니다.
<script setup>
const { data: posts, status, error } = await useFetch("/api/posts");
</script>
  1. $fetch는 사용자 동작에 반응하는 요청에 씁니다. 폼 제출, 버튼 클릭처럼 이벤트에서 시작되는 호출은 애초에 서버 렌더링과 무관하므로 $fetch가 맞습니다.
<script setup>
async function submit() {
  await $fetch("/api/posts", { method: "POST", body: form.value });
}
</script>
  1. 직접 가공이 필요하면 useAsyncData를 씁니다. 첫 인자는 응답을 캐시하는 데 쓰이는 고유 키입니다.
const { data } = await useAsyncData("posts", () => $fetch("/api/posts"));
  1. 키는 직접 짓습니다. 문서가 권합니다 — useAsyncData를 감싸는 커스텀 컴포저블을 만들 때처럼 의도치 않은 동작을 피하려면 항상 자기 키를 만드는 편이 좋습니다. 같은 키를 쓰면 useNuxtData로 다른 컴포넌트에서 그 데이터를 그대로 읽을 수 있습니다.

  2. 호출 위치가 정해져 있습니다. 이 컴포저블들은 setup 안이나 라이프사이클 훅의 최상위에서 호출해야 합니다. 조건문 안이나 콜백 안에서 부르면 SSR 보장이 깨집니다. 그런 자리에는 $fetch를 씁니다.

  3. 파라미터가 바뀌면 다시 가져오게 합니다. 키나 옵션에 computed·getter를 주면 그 값이 바뀔 때 재요청합니다. watch 옵션으로 감시 대상을 명시할 수도 있습니다.

const page = ref(1);
const { data } = await useFetch("/api/posts", { query: { page } });
  1. lazyimmediate는 목적이 다릅니다. lazy: true는 내비게이션을 막지 않고 마운트 이후로 미루고, immediate: false는 자동 실행 자체를 막아 execute()로 직접 시작하게 합니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.