들어가며
프론트엔드 개발을 하다 보면 거의 무조건 한 번쯤은 Axios를 사용하게 됩니다. 하지만 최근 프로젝트에서는 RTK Query의 내장 기능인 fetchBaseQuery로 완전히 전환했습니다. 이번 글에서는 왜 그렇게 했는지, 어떤 장단점이 있었는지, 그리고 실제 코드 예시를 통해 마이그레이션 방법을 공유하려고 합니다.
1️⃣ 기존 구조: Axios 기반 API 관리
기존에는 Axios 인스턴스를 만들어서 공통 헤더, 에러 핸들링, 인터셉터 등을 직접 설정했습니다.
// api/axiosInstance.js
import axios from 'axios';
const axiosInstance = axios.create({
baseURL: process.env.REACT_APP_API_URL,
timeout: 5000,
});
axiosInstance.interceptors.request.use((config) => {
const token = localStorage.getItem('accessToken');
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
axiosInstance.interceptors.response.use(
(res) => res,
(err) => {
if (err.response?.status === 401) {
window.location.href = '/login';
}
return Promise.reject(err);
}
);
export default axiosInstance;
이 방식은 유연하고 강력하지만, 문제는 Redux Toolkit Query(RTK Query)를 쓰기 시작하면서 복잡도가 커졌다는 점입니다.
2️⃣ RTK Query 도입 후의 고민
RTK Query는 데이터 요청, 캐싱, 리패칭 로직을 자동으로 관리해주는 훌륭한 도구입니다. 그런데 axios를 그대로 사용하려면 baseQuery를 직접 구현해야 했어요:
const axiosBaseQuery =
({ baseUrl } = { baseUrl: '' }) =>
async ({ url, method, data, params }) => {
try {
const result = await axios({ url: baseUrl + url, method, data, params });
return { data: result.data };
} catch (axiosError) {
const err = axiosError;
return { error: { status: err.response?.status, data: err.response?.data } };
}
};
이렇게 하면 되긴 하지만, 이미 RTK Query는 fetch 기반의 기본 구현체인 fetchBaseQuery를 제공하고 있었죠. 결국 “굳이 Axios를 유지해야 할까?” 라는 고민이 생겼습니다.
3️⃣ fetchBaseQuery로 전환한 이유
비교 항목 Axios fetchBaseQuery
| 패키지 용량 | ✅ 비교적 큼 (~12KB gzip) | 🪶 작음 (fetch 내장 사용) |
| RTK Query 통합 | 🔧 직접 baseQuery 작성 필요 | ⚡️ 기본 제공 |
| 에러 핸들링 | 커스터마이징 자유도 높음 | 기본 에러 구조 제공 |
| 브라우저 호환성 | 매우 넓음 | 최신 브라우저 중심 |
| 인터셉터 기능 | 지원 (요청/응답 가로채기) | 직접 래핑 필요 |
결국,
- 패키지 의존성 줄이기
- RTK Query와의 통합성 향상
- 코드 단순화
이 세 가지 이유로 fetchBaseQuery로 전면 전환을 결정했습니다.
4️⃣ 전환 후 코드 예시
// api/baseApi.js
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const baseApi = createApi({
reducerPath: 'baseApi',
baseQuery: fetchBaseQuery({
baseUrl: process.env.REACT_APP_API_URL,
prepareHeaders: (headers) => {
const token = localStorage.getItem('accessToken');
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
return headers;
},
}),
tagTypes: ['User', 'Post'],
endpoints: (builder) => ({
getUser: builder.query({
query: (id) => `/users/${id}`,
providesTags: ['User'],
}),
updatePost: builder.mutation({
query: (post) => ({
url: `/posts/${post.id}`,
method: 'PUT',
body: post,
}),
invalidatesTags: ['Post'],
}),
}),
});
export const { useGetUserQuery, useUpdatePostMutation } = baseApi;
✅ 장점:
- 불필요한 Axios 의존성 제거
- RTK Query와 완벽히 호환
- 자동 캐싱 / 리패칭 / 에러 핸들링
- 훨씬 간결한 코드 구조
5️⃣ 전환하면서 느낀 점
- fetchBaseQuery의 커스터마이징 한계는 있습니다. 예를 들어, 요청을 재시도하거나 응답을 변환하는 로직은 직접 래핑해야 합니다.
- 하지만 RTK Query의 캐싱·동기화 기능과의 궁합이 훨씬 좋습니다.
- 대부분의 REST API 환경에서는 Axios의 추가 기능까지는 필요하지 않았습니다.
🧭 결론
작은 프로젝트이거나, RTK Query를 사용하고 있다면 fetchBaseQuery로 통일하는 게 훨씬 깔끔합니다.
반면,
여러 API 클라이언트를 병행하거나, 복잡한 인터셉터 로직이 있다면 여전히 Axios가 더 유리할 수 있습니다.
✨ 마무리하며
Axios는 여전히 훌륭한 HTTP 클라이언트입니다. 하지만 RTK Query를 중심으로 상태 관리를 단일화하려면, fetchBaseQuery는 “기능보다 구조”를 깔끔하게 만들어주는 선택이었습니다.
'개발 > 프론트엔드' 카테고리의 다른 글
| 리액트로 만든 SW 프로그램을 exe 파일로 배포하자! (2) | 2025.08.22 |
|---|---|
| 30개의 상태가 달린 하나의 컴포넌트를 마주친 적 있으신가요? (2) | 2025.05.14 |
| 리코일을 도입해보십다 (2) | 2025.03.26 |
| CRA 지고 Vite가 왔다...!! (0) | 2025.03.26 |
| Redux Toolkit을 도입하면서 (1) | 2024.12.23 |