SvelteKit의 fetch는 그냥 fetch가 아닙니다 — 쿠키 전달, 상대 경로, 캐싱까지 자동으로 처리됩니다.

개념 정의

SvelteKit은 Web 표준 fetch API를 확장하여 SSR 환경에서도 동일하게 동작하는 향상된 fetch 를 제공합니다. load 함수에서 제공되는 fetch는 쿠키를 자동 전달하고, 서버에서 상대 경로 호출이 가능합니다.

load 함수에서의 fetch

JAVASCRIPT
// +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 };
}

클라이언트에서의 데이터 패칭

SVELTE
<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 유틸

JAVASCRIPT
// 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);
}
JAVASCRIPT
// +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 순차 요청

JAVASCRIPT
// 나쁜 예 — 순차 실행 (느림)
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 };
}

무한 스크롤

SVELTE
<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를 얻을 수 있습니다.

댓글 로딩 중...