HTTP 통신 — fetch와 SvelteKit에서의 데이터 패칭 전략
SvelteKit의 fetch는 그냥 fetch가 아닙니다 — 쿠키 전달, 상대 경로, 캐싱까지 자동으로 처리됩니다.
개념 정의
SvelteKit은 Web 표준 fetch API를 확장하여 SSR 환경에서도 동일하게 동작하는 향상된 fetch 를 제공합니다. load 함수에서 제공되는 fetch는 쿠키를 자동 전달하고, 서버에서 상대 경로 호출이 가능합니다.
load 함수에서의 fetch
// +page.server.js
export async function load({ fetch, cookies }) {
// SvelteKit fetch: 쿠키 자동 전달, 상대 경로 지원
const res = await fetch('/api/posts');
const posts = await res.json();
// 외부 API도 같은 fetch로 호출
const weatherRes = await fetch('https://api.weather.com/current');
const weather = await weatherRes.json();
return { posts, weather };
}
클라이언트에서의 데이터 패칭
<script>
let posts = $state([]);
let isLoading = $state(false);
let error = $state(null);
async function loadPosts() {
isLoading = true;
error = null;
try {
const res = await fetch('/api/posts');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
posts = await res.json();
} catch (e) {
error = e.message;
} finally {
isLoading = false;
}
}
// 마운트 시 데이터 로드
$effect(() => {
loadPosts();
});
</script>
{#if isLoading}
<p>로딩 중...</p>
{:else if error}
<p class="error">{error}</p>
<button onclick={loadPosts}>재시도</button>
{:else}
{#each posts as post}
<article>{post.title}</article>
{/each}
{/if}
재사용 가능한 fetch 유틸
// src/lib/api.js
class ApiClient {
constructor(baseFetch = fetch) {
this.fetch = baseFetch;
}
async request(url, options = {}) {
const res = await this.fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
});
if (!res.ok) {
const errorBody = await res.json().catch(() => ({}));
throw new ApiError(res.status, errorBody.message || '요청 실패');
}
return res.json();
}
get(url) { return this.request(url); }
post(url, data) {
return this.request(url, {
method: 'POST',
body: JSON.stringify(data),
});
}
put(url, data) {
return this.request(url, {
method: 'PUT',
body: JSON.stringify(data),
});
}
delete(url) {
return this.request(url, { method: 'DELETE' });
}
}
class ApiError extends Error {
constructor(status, message) {
super(message);
this.status = status;
}
}
export function createApi(fetch) {
return new ApiClient(fetch);
}
// +page.js
import { createApi } from '$lib/api';
export async function load({ fetch }) {
const api = createApi(fetch);
const posts = await api.get('/api/posts');
return { posts };
}
병렬 vs 순차 요청
// 나쁜 예 — 순차 실행 (느림)
export async function load({ fetch }) {
const posts = await fetch('/api/posts').then(r => r.json());
const tags = await fetch('/api/tags').then(r => r.json()); // posts 완료 후 시작
return { posts, tags };
}
// 좋은 예 — 병렬 실행 (빠름)
export async function load({ fetch }) {
const [posts, tags] = await Promise.all([
fetch('/api/posts').then(r => r.json()),
fetch('/api/tags').then(r => r.json()),
]);
return { posts, tags };
}
무한 스크롤
<script>
let { data } = $props();
let posts = $state(data.posts);
let page = $state(1);
let hasMore = $state(true);
let loading = $state(false);
async function loadMore() {
if (loading || !hasMore) return;
loading = true;
page++;
const res = await fetch(`/api/posts?page=${page}`);
const newPosts = await res.json();
if (newPosts.length === 0) {
hasMore = false;
} else {
posts = [...posts, ...newPosts];
}
loading = false;
}
</script>
{#each posts as post}
<article>{post.title}</article>
{/each}
{#if hasMore}
<button onclick={loadMore} disabled={loading}>
{loading ? '로딩 중...' : '더 보기'}
</button>
{/if}
면접 포인트
- "SvelteKit fetch와 일반 fetch의 차이는?": SvelteKit의 fetch는 SSR 시 서버에서 HTTP 요청 없이 직접 핸들러를 호출하고, 쿠키를 자동 전달하며, 상대 경로를 지원합니다.
- "데이터 패칭은 load에서 vs 클라이언트에서?": 초기 렌더링에 필요한 데이터는 load에서 (SEO, 초기 로딩 속도), 사용자 인터랙션 후 데이터는 클라이언트에서 fetch합니다.
정리
SvelteKit의 데이터 패칭 전략은 "서버에서 할 수 있는 건 서버에서, 클라이언트에서만 가능한 건 클라이언트에서"입니다. load 함수의 향상된 fetch를 최대한 활용하고, 클라이언트 패칭은 인터랙티브한 기능에만 사용하면 최적의 성능과 UX를 얻을 수 있습니다.
댓글 로딩 중...