컴포넌트 Fetch 에러 (1)
작성일 : 2025.01.02
1. 기본 개념
서버 컴포넌트
- Next.js 13+ 버전의 기본 컴포넌트 타입
- 서버에서 렌더링되어 클라이언트로 전송
- HTML과 최소한의 JavaScript만 전달
- 별도의 지시어 없이 사용 가능
- 데이터베이스, 파일 시스템 등에 직접 접근 가능
- 민감한 정보(API 키, 토큰 등)를 안전하게 사용 가능
서버 컴포넌트의 데이터 페칭 특징
- 자동 요청 중복 제거
async function ServerComponent() {
// 동일한 URL의 요청은 자동으로 중복 제거됨
const data1 = await fetch(URL);
const data2 = await fetch(URL); // 캐시된 결과 재사용
}
- 캐싱 제어
// POST 요청 캐시 방지
const data = await fetch(URL, {
method: 'POST',
cache: 'no-store'
});
// 특정 시간동안 캐시
const data = await fetch(URL, {
next: { revalidate: 3600 } // 1시간
});
- 동적 함수 사용
// 동적 경로에서 데이터 가져오기
async function Product({ id }: { id: string }) {
const data = await fetch(`${process.env.API_URL}/products/${id}`);
const product = await data.json();
return <div>{product.name}</div>;
}
- 병렬 데이터 페칭
async function ParallelFetch() {
// 병렬로 데이터 요청
const [productsPromise, categoriesPromise] = await Promise.all([
fetch(`${API_URL}/products`),
fetch(`${API_URL}/categories`)
]);
const [products, categories] = await Promise.all([
productsPromise.json(),
categoriesPromise.json()
]);
return (
<>
<ProductList products={products} />
<CategoryList categories={categories} />
</>
);
}
클라이언트 컴포넌트
- 'use client' 지시어로 명시적 선언 필요
- 브라우저에서 실행
- 사용자 상호작용 처리 가능
- 브라우저 API 사용 가능
2. Fetch 동작 방식 차이점
서버 컴포넌트에서의 Fetch
- 실행 환경: Next.js 서버 내부
- rewrites 설정 적용 안됨
- 전체 URL 필요
- 캐싱과 재검증 기능 사용 가능
// 서버 컴포넌트 예시
async function ServerComponent() {
// ❌ 잘못된 방식
const data = await fetch('/api/data');
// ✅ 올바른 방식
const data = await fetch('http://api.example.com/data', {
next: { revalidate: 3600 }
});
}
클라이언트 컴포넌트에서의 Fetch
- 실행 환경: 브라우저
- rewrites 설정 정상 적용
- 상대 경로 사용 가능
- 브라우저의 기본 fetch 동작 따름
'use client'
function ClientComponent() {
useEffect(() => {
// ✅ 상대 경로 사용 가능
fetch('/api/data')
.then(res => res.json())
.then(data => console.log(data));
}, []);
}
3. 실전 해결 방안
통합 Fetch 유틸리티 구현
export const universalFetch = async (input: RequestInfo | URL, init?: RequestInit) => {
// 서버 사이드 환경 체크
if (typeof window === 'undefined') {
if (typeof input === 'string' && !input.startsWith('http')) {
const baseUrl = process.env.API_BASE_URL;
return fetch(`${baseUrl}${input}`, init);
}
}
return fetch(input, init);
};
환경변수 설정
# .env.local
NEXT_PUBLIC_API_URL=http://api.example.com
API_BASE_URL=http://internal-api.example.com
rewrites 설정
// next.config.js
module.exports = {
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'http://api.example.com/api/:path*'
}
];
}
};
4. 권장 사항
서버 컴포넌트
- 전 체 URL 사용하기
- 환경변수로 베이스 URL 관리
- next.js의 캐싱 기능 활용
- 데이터 페칭 로직 추상화
클라이언트 컴포넌트
- rewrites 설정 활용하기
- 상대 경로 사용
- 에러 바운더리 구현
- 로딩 상태 관리
5. 주의 사항
-
서버 컴포넌트 fetch
- rewrites 설정에 의존하지 않기
- 항상 전체 URL 사용
- 환경별 URL 분리
-
클라이언트 컴포넌트 fetch
- CORS 이슈 고려
- 인증 토큰 관리
- 에러 처리 구현
6. 모범 사례
API 클라이언트 구현
// api-client.ts
const createApiClient = () => {
const baseUrl = typeof window === 'undefined'
? process.env.API_BASE_URL
: '/api';
return {
async get(path: string) {
const response = await fetch(`${baseUrl}${path}`);
return response.json();
},
// POST, PUT 등 추가 메서드 구현
};
};
export const apiClient = createApiClient();
실제 사용 예시
// 서버 컴포넌트
async function ProductList() {
const products = await apiClient.get('/products');
return <div>{/* 제품 목록 렌더링 */}</div>;
}
// 클라이언트 컴포넌트
'use client'
function ProductForm() {
const handleSubmit = async () => {
await apiClient.post('/products', formData);
};
return <form>{/* 폼 구현 */}</form>;
}
7. 참고자료
공식 문서
- Next.js Server Components
- 서버 컴포넌트의 기본 개념과 동작 방식
- 데이터 페칭 패턴과 최적화 방법
- Next.js Data Fetching
- 새로운 App Router에서의 데이터 페칭 방법
- 캐싱과 재검증 전략
- Next.js Rewrites
- URL 재작성 설정 방법
- 프록시 설정과 미들웨어 활용
추천 블로그 포스트
-
Understanding Server Components in Next.js
- Vercel의 공식 블로그 포스트
- 서버 컴포넌트의 심층적인 이해
-
- App Router의 실전 패턴
- 데이터 페칭 전략과 최적화 방법