
지난 시간에는 Nuxt의 자동 라우팅과 컴포넌트 자동 임포트 기능을 익혔습니다. 이제 실제로 애플리케이션에 생명을 불어넣을 데이터(Data)를 외부 API에서 가져와 봅시다.
Nuxt의 핵심 컴포저블인 useFetch를 사용하여 데이터를 가져오고, 사용자 경험을 개선하기 위해 로딩 상태와 에러 처리까지 완벽하게 구현하는 실습을 진행합니다.
1. ⚙️ useFetch의 자동 상태 관리 이해
일반적인 Vue 환경에서는 API를 호출할 때 isLoading, data, error 세 가지 상태를 직접 관리해야 합니다. 하지만 useFetch는 이 모든 것을 한 번에 처리해줍니다.
useFetch는 비동기 작업 후 다음과 같은 3가지 반응형(Reactive) 변수를 반환합니다.
| 변수 | 설명 | 용도 |
| data | API 호출로 받은 최종 데이터 | 화면에 내용을 표시할 때 사용 |
| pending | 데이터 로딩 중인지 여부 (Boolean) | 로딩 인디케이터, 스켈레톤 UI 표시 |
| error | API 호출 중 에러가 발생했는지 여부 | 에러 메시지 표시 |
2. 👩💻 실습 준비: 사용자 목록 페이지 생성
가상의 데이터를 제공하는 JSONPlaceholder API를 사용하여 사용자 목록을 가져오겠습니다.
- 페이지 파일 생성: pages/users.vue 파일을 생성합니다.
- API 주소: https://jsonplaceholder.typicode.com/users
3. 실습 1: useFetch로 사용자 목록 가져와 테이블로 표시
pages/users.vue 파일에 useFetch를 사용하여 데이터를 가져온 후, 간단한 HTML 테이블로 출력합니다.
<template>
<div>
<AppHeader />
<h1>사용자 목록</h1>
<table v-if="data" border="1" style="width: 100%; border-collapse: collapse;">
<thead>
<tr style="background-color: #eee;">
<th>ID</th>
<th>이름</th>
<th>이메일</th>
</tr>
</thead>
<tbody>
<tr v-for="user in data" :key="user.id">
<td style="padding: 8px;">{{ user.id }}</td>
<td style="padding: 8px;">{{ user.name }}</td>
<td style="padding: 8px;">{{ user.email }}</td>
</tr>
</tbody>
</table>
<p v-else>데이터를 불러오는 중이거나, 목록이 비어있습니다.</p>
</div>
</template>
<script setup>
// useFetch를 사용하여 데이터를 가져오고, data 변수를 추출합니다.
// <script setup> 내에서는 await를 바로 사용할 수 있습니다.
const { data } = await useFetch('https://jsonplaceholder.typicode.com/users');
</script>
브라우저 확인: http://localhost:3000/users로 접속하면 사용자의 이름, 이메일 등이 테이블 형태로 즉시 나타나는 것을 확인할 수 있습니다.
4. 실습 2: 로딩 상태 처리와 스켈레톤 UI 구현
useFetch가 제공하는 pending 변수를 활용하여, 데이터가 로딩되는 짧은 시간 동안 사용자에게 스켈레톤 UI(Skeleton UI)를 보여주어 사용자 경험을 개선합니다.
4.1. 스켈레톤 컴포넌트 추가
로딩 중일 때 표시할 간단한 스켈레톤 UI를 만듭니다.
- components/UserSkeleton.vue 파일을 생성합니다.
<template> <div style="padding: 10px; border: 1px solid #eee; margin-bottom: 5px;"> <div style="height: 15px; background-color: #ddd; width: 60%; margin-bottom: 5px;"></div> <div style="height: 10px; background-color: #ddd; width: 40%;"></div> </div> </template> <script setup> // 스켈레톤이 반복될 횟수만 정의합니다. const props = defineProps({ count: { type: Number, default: 5 } }); </script>
4.2. pages/users.vue 업데이트 (로딩/에러 처리)
useFetch에서 pending과 error 변수를 함께 추출하고, <template>에서 조건부 렌더링을 적용합니다.
<template>
<div>
<AppHeader />
<h1>사용자 목록</h1>
<div v-if="pending">
<UserSkeleton v-for="i in 5" :key="i" />
</div>
<div v-else-if="error" style="color: red; border: 1px solid red; padding: 10px;">
<h2>🚨 데이터를 불러오는 데 실패했습니다.</h2>
<p>{{ error.message }}</p>
</div>
<table v-else-if="data && data.length" border="1" style="width: 100%; border-collapse: collapse;">
</table>
<p v-else>등록된 사용자가 없습니다.</p>
</div>
</template>
<script setup>
// pending과 error 변수도 함께 구조 분해 할당으로 추출합니다.
const { data, pending, error } = await useFetch(
'https://jsonplaceholder.typicode.com/users'
);
</script>
실습 결과:
- 페이지를 새로고침하면 데이터가 로드되는 짧은 시간 동안 5개의 스켈레톤 박스가 표시된 후 실제 데이터가 나타납니다.
- (선택 실습) useFetch의 URL을 일부러 users-error 등으로 잘못 입력하면, error 상태로 진입하여 에러 메시지 박스가 출력됩니다.
5. ✨ 실습 3: 컴포저블(composables/)로 로직 재사용하기
API 호출 로직은 애플리케이션의 핵심이므로, composables/ 폴더에 별도의 함수로 분리하여 관리하는 것이 좋습니다.
- 컴포저블 파일 생성: composables/useUsers.js 파일을 만듭니다.
// composables/useUsers.js // 외부에 노출할 함수명은 'use'로 시작하는 것이 Nuxt의 컨벤션입니다. export const useUsers = () => { // useFetch에 고유 키(key)를 지정하여 Nuxt가 데이터 캐싱과 공유를 관리하도록 합니다. return useFetch('https://jsonplaceholder.typicode.com/users', { key: 'all-users-list' // 이 키를 사용하면 다른 컴포넌트에서도 같은 데이터를 공유합니다. }); } - 페이지에서 컴포저블 사용: pages/users.vue 파일을 다음과 같이 수정합니다.
<script setup> // Nuxt는 composables 폴더의 함수를 자동으로 import 해줍니다. // 복잡한 URL 대신 useUsers()만 호출하여 사용합니다. const { data, pending, error } = useUsers(); </script>
핵심: 이제 API 주소나 로직이 변경되어도 pages/users.vue는 수정할 필요가 없으며, 다른 페이지에서도 useUsers()를 호출하여 동일한 로직을 쉽게 재사용할 수 있게 되었습니다.
📝 블로그 마무리: 데이터의 흐름을 통제하다
이번 시간에는 useFetch를 사용하여 외부 데이터를 가져오고, pending과 error 상태를 이용하여 사용자 친화적인 로딩 및 에러 처리를 구현했습니다. 마지막으로, 컴포저블을 통해 로직을 분리하는 Nuxt 개발의 모범 사례를 경험했습니다.
'Nuxt.js 를 배워보자 > 💡 2. 데이터 가져오기 실습: API 서버와 연결하기' 카테고리의 다른 글
| 🏗️ 컴포넌트 내 데이터 로직 분리: 컴포저블 사용하기 (0) | 2025.12.05 |
|---|