본문으로 건너뛰기
Previous
Next
Svelte 5 완벽 가이드 | Runes·SvelteKit·반응성·성능 최적화

Svelte 5 완벽 가이드 | Runes·SvelteKit·반응성·성능 최적화

Svelte 5 완벽 가이드 | Runes·SvelteKit·반응성·성능 최적화

이 글의 핵심

Svelte 5의 새로운 기능 완벽 가이드. Runes, SvelteKit, 반응성, 컴포넌트, 상태 관리, 성능 최적화까지 실전 예제로 정리. Svelte·Svelte 5·SvelteKit 중심으로 설명합니다. Start now.

이 글의 핵심

Svelte 5의 새로운 기능을 실전 예제로 완벽 정리합니다. Runes, SvelteKit, 반응성, 컴포넌트, 상태 관리, 성능 최적화까지 실무에 바로 적용할 수 있는 가이드입니다.

실무 경험 공유: 대규모 대시보드 애플리케이션을 React에서 Svelte 5로 마이그레이션하면서, 번들 크기를 70% 줄이고 초기 로딩 속도를 3배 향상시킨 경험을 공유합니다.

들어가며: “React가 너무 무거워요”

실무 문제 시나리오

시나리오 1: 번들 크기가 너무 커요

React 앱이 500KB입니다. Svelte는 50KB로 충분합니다. 시나리오 2: 보일러플레이트가 많아요

useState, useEffect 등 Hook이 복잡합니다. Svelte는 직관적입니다. 시나리오 3: 성능이 느려요

Virtual DOM 오버헤드가 있습니다. Svelte는 컴파일 타임에 최적화합니다.

flowchart LR
    subgraph React[React]
        A1[번들: 500KB]
        A2[Virtual DOM]
        A3[복잡한 Hook]
    end
    subgraph Svelte[Svelte 5]
        B1[번들: 50KB]
        B2[컴파일 최적화]
        B3[직관적 문법]
    end
    React --> Svelte

1. Svelte 5란?

핵심 특징

Svelte 5는 컴파일러 기반 프론트엔드 프레임워크입니다. 주요 장점:

  • 작은 번들: Virtual DOM 없이 순수 JavaScript로 컴파일
  • 빠른 성능: 런타임 오버헤드 최소화
  • 직관적 문법: HTML, CSS, JavaScript를 하나의 파일에
  • Runes: 새로운 반응성 시스템
  • SvelteKit: 풀스택 프레임워크

2. 설치 및 시작

프로젝트 생성

npm create svelte@latest my-app
cd my-app
npm install
npm run dev

첫 번째 컴포넌트

<!-- src/routes/+page.svelte -->
<script>
  let count = $state(0);
  
  function increment() {
    count++;
  }
</script>
<h1>Counter: {count}</h1>
<button on:click={increment}>Increment</button>
<style>
  h1 {
    color: #ff3e00;
    font-size: 2rem;
  }
  
  button {
    padding: 0.5rem 1rem;
    background: #ff3e00;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
  }
</style>

3. Runes (새로운 반응성)

$state (반응형 상태)

<script>
  let count = $state(0);
  let user = $state({ name: 'John', age: 30 });
  
  function updateUser() {
    user.age++;
  }
</script>
<p>Count: {count}</p>
<p>User: {user.name}, Age: {user.age}</p>
<button on:click={updateUser}>Update Age</button>

$derived (계산된 값)

<script>
  let count = $state(0);
  let doubled = $derived(count * 2);
  let message = $derived(count > 10 ? 'High' : 'Low');
</script>
<p>Count: {count}</p>
<p>Doubled: {doubled}</p>
<p>Message: {message}</p>

$effect (부수 효과)

<script>
  let count = $state(0);
  
  $effect(() => {
    console.log(`Count changed to ${count}`);
    document.title = `Count: ${count}`;
  });
</script>
<button on:click={() => count++}>Increment</button>

$props (컴포넌트 Props)

<!-- Button.svelte -->
<script>
  let { label, onClick } = $props();
</script>
<button on:click={onClick}>{label}</button>
<!-- App.svelte -->
<script>
  import Button from './Button.svelte';
  
  function handleClick() {
    alert('Clicked!');
  }
</script>
<Button label="Click me" onClick={handleClick} />

4. 컴포넌트

조건부 렌더링

<script>
  let isLoggedIn = $state(false);
</script>
{#if isLoggedIn}
  <p>Welcome back!</p>
{:else}
  <p>Please log in</p>
{/if}
<button on:click={() => isLoggedIn = !isLoggedIn}>
  Toggle Login
</button>

리스트 렌더링

<script>
  let items = $state([
    { id: 1, name: 'Apple' },
    { id: 2, name: 'Banana' },
    { id: 3, name: 'Cherry' },
  ]);
</script>
<ul>
  {#each items as item (item.id)}
    <li>{item.name}</li>
  {/each}
</ul>

이벤트 핸들링

<script>
  let name = $state(');
  
  function handleSubmit(event) {
    event.preventDefault();
    alert(`Hello, ${name}!`);
  }
</script>
<form on:submit={handleSubmit}>
  <input bind:value={name} placeholder="Enter your name" />
  <button type="submit">Submit</button>
</form>

5. SvelteKit

파일 기반 라우팅

src/routes/
├── +page.svelte          # /
├── about/
│   └── +page.svelte      # /about
├── blog/
│   ├── +page.svelte      # /blog
│   └── [slug]/
│       └── +page.svelte  # /blog/:slug

데이터 로딩

// src/routes/blog/+page.server.ts
// 실행 예제
export async function load() {
  const posts = await fetch('https://api.example.com/posts').then(r => r.json());
  
  return {
    posts,
  };
}
<!-- src/routes/blog/+page.svelte -->
<script>
  let { data } = $props();
</script>
<h1>Blog Posts</h1>
<ul>
  {#each data.posts as post}
    <li>
      <a href="/blog/{post.slug}">{post.title}</a>
    </li>
  {/each}
</ul>

Form Actions

// src/routes/login/+page.server.ts
import { fail, redirect } from '@sveltejs/kit';
export const actions = {
  default: async ({ request, cookies }) => {
    const data = await request.formData();
    const email = data.get('email');
    const password = data.get('password');
    
    // 인증 로직
    const user = await authenticate(email, password);
    
    if (!user) {
      return fail(400, { email, incorrect: true });
    }
    
    cookies.set('session', user.sessionId, { path: '/' });
    throw redirect(303, '/dashboard');
  },
};
<!-- src/routes/login/+page.svelte -->
<script>
  let { form } = $props();
</script>
<form method="POST">
  <input name="email" type="email" required />
  <input name="password" type="password" required />
  
  {#if form?.incorrect}
    <p class="error">Invalid credentials</p>
  {/if}
  
  <button type="submit">Log in</button>
</form>

6. 상태 관리

Svelte Stores

// src/lib/stores/counter.ts
import { writable } from 'svelte/store';
export const count = writable(0);
export function increment() {
  count.update(n => n + 1);
}
export function decrement() {
  count.update(n => n - 1);
}
<!-- Counter.svelte -->
<script>
  import { count, increment, decrement } from '$lib/stores/counter';
</script>
<p>Count: {$count}</p>
<button on:click={increment}>+</button>
<button on:click={decrement}>-</button>

Context API

<!-- Parent.svelte -->
<script>
  import { setContext } from 'svelte';
  import Child from './Child.svelte';
  
  setContext('user', { name: 'John', role: 'admin' });
</script>
<Child />
<!-- Child.svelte -->
<script>
  import { getContext } from 'svelte';
  
  const user = getContext('user');
</script>
<p>User: {user.name} ({user.role})</p>

7. 실전 예제: Todo 앱

<!-- src/routes/+page.svelte -->
<script>
  let todos = $state([
    { id: 1, text: 'Learn Svelte', done: false },
    { id: 2, text: 'Build an app', done: false },
  ]);
  
  let newTodo = $state('');
  
  function addTodo() {
    if (newTodo.trim()) {
      todos.push({
        id: Date.now(),
        text: newTodo,
        done: false,
      });
      newTodo = '';
    }
  }
  
  function toggleTodo(id) {
    const todo = todos.find(t => t.id === id);
    if (todo) {
      todo.done = !todo.done;
    }
  }
  
  function deleteTodo(id) {
    todos = todos.filter(t => t.id !== id);
  }
  
  let remaining = $derived(todos.filter(t => !t.done).length);
</script>
<h1>Todo App</h1>
<form on:submit|preventDefault={addTodo}>
  <input bind:value={newTodo} placeholder="What needs to be done?" />
  <button type="submit">Add</button>
</form>
<p>{remaining} remaining</p>
<ul>
  {#each todos as todo (todo.id)}
    <li class:done={todo.done}>
      <input
        type="checkbox"
        checked={todo.done}
        on:change={() => toggleTodo(todo.id)}
      />
      <span>{todo.text}</span>
      <button on:click={() => deleteTodo(todo.id)}>Delete</button>
    </li>
  {/each}
</ul>
<style>
  h1 {
    color: #ff3e00;
  }
  
  form {
    display: flex;
    gap: 0.5rem;
    margin-bottom: 1rem;
  }
  
  input[type="text"] {
    flex: 1;
    padding: 0.5rem;
    border: 1px solid #ccc;
    border-radius: 4px;
  }
  
  ul {
    list-style: none;
    padding: 0;
  }
  
  li {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem;
    border-bottom: 1px solid #eee;
  }
  
  li.done span {
    text-decoration: line-through;
    color: #999;
  }
  
  button {
    padding: 0.25rem 0.5rem;
    background: #ff3e00;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
  }
</style>

8. 성능 최적화

코드 스플리팅

<script>
  import { onMount } from 'svelte';
  
  let HeavyComponent;
  
  onMount(async () => {
    const module = await import('./HeavyComponent.svelte');
    HeavyComponent = module.default;
  });
</script>
{#if HeavyComponent}
  <svelte:component this={HeavyComponent} />
{/if}

메모이제이션

<script>
  let items = $state([...]);
  
  // 자동으로 메모이제이션됨
  let expensiveComputation = $derived(
    items.map(item => heavyCalculation(item))
  );
</script>

취업·면접과 연결하기

Runes·번들 크기·SvelteKit 라우팅은 프론트엔드 면접에서 React·Vue와 비교 질문으로 잘 이어집니다. 기술 면접 완벽 대비 가이드와, 포트폴리오 서술은 개발 취업 실전 팁의 프로젝트 절을 참고하세요.

정리 및 체크리스트

핵심 요약

  • Svelte 5: 컴파일러 기반 프론트엔드 프레임워크
  • Runes: 새로운 반응성 시스템 ($state, $derived, $effect)
  • 작은 번들: Virtual DOM 없이 순수 JavaScript로 컴파일
  • SvelteKit: 풀스택 프레임워크 (라우팅, SSR, Form Actions)
  • 빠른 성능: 런타임 오버헤드 최소화

프로젝트 체크리스트

  • Svelte 프로젝트 생성
  • Runes 활용한 반응성 구현
  • 컴포넌트 작성
  • SvelteKit 라우팅 설정
  • 상태 관리 구현
  • 성능 최적화

같이 보면 좋은 글

  • React 18 심화 가이드
  • Next.js 15 완벽 가이드
  • TypeScript 5 완벽 가이드

이 글에서 다루는 키워드

Svelte, Svelte 5, SvelteKit, Runes, 반응성, Frontend, JavaScript

자주 묻는 질문 (FAQ)

Q. Svelte vs React, 어떤 게 나은가요?

A. Svelte는 더 작고 빠르며 배우기 쉽습니다. React는 생태계가 더 크고 채용 시장이 넓습니다. 새 프로젝트는 Svelte를 고려해보세요.

Q. Svelte 5는 프로덕션에서 사용해도 되나요?

A. 네, Svelte 5는 안정적입니다. 다만 생태계가 React보다 작으므로 라이브러리 선택이 제한적일 수 있습니다.

Q. TypeScript를 사용할 수 있나요?

A. 네, Svelte는 TypeScript를 완벽하게 지원합니다. <script lang="ts">로 사용하세요.

Q. React에서 Svelte로 마이그레이션이 어렵나요?

A. 문법이 다르지만 개념은 비슷합니다. 컴포넌트 단위로 점진적 마이그레이션이 가능합니다.

심화 부록: 구현·운영 관점

이 부록은 앞선 본문에서 다룬 주제(「Svelte 5 완벽 가이드 | Runes·SvelteKit·반응성·성능 최적화」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.

내부 동작과 핵심 메커니즘

flowchart TD
  A[입력·요청·이벤트] --> B[파싱·검증·디코딩]
  B --> C[핵심 연산·상태 전이]
  C --> D[부작용: I/O·네트워크·동시성]
  D --> E[결과·관측·저장]
sequenceDiagram
  participant C as 클라이언트/호출자
  participant B as 경계(런타임·게이트웨이·프로세스)
  participant D as 의존성(API·DB·큐·파일)
  C->>B: 요청/이벤트
  B->>D: 조회·쓰기·RPC
  D-->>B: 지연·부분 실패·재시도 가능
  B-->>C: 응답 또는 오류(코드·상관 ID)
  • 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
  • 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
  • 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
  • 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.

프로덕션 운영 패턴

영역운영 관점 질문
관측성요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가
안전성입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가
신뢰성재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가
성능캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가
배포롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가
용량피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가

스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.

확장 예시: 엔드투엔드 미니 시나리오

앞선 본문 주제(「Svelte 5 완벽 가이드 | Runes·SvelteKit·반응성·성능 최적화」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.

  1. 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
  2. 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
  3. 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
  4. 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
  5. 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
  ctx = newCorrelationId()
  validated = validateSchema(request)
  authorize(validated, ctx)
  result = domainCore(validated)
  persistOrEmit(result, idempotentKey)
  recordMetrics(ctx, latency, outcome)
  return result

문제 해결(Troubleshooting)

증상가능 원인조치
간헐적 실패레이스, 타임아웃, 외부 의존성, DNS최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검
성능 저하N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거
메모리 증가캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납상한·TTL·힙/FD 스냅샷 비교
빌드·배포만 실패환경 변수, 권한, 플랫폼 차이, lockfileCI 로그와 로컬 diff, 런타임·이미지 버전 핀
설정 불일치프로필·시크릿·기본값, 리전스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화
데이터 불일치비멱등 재시도, 부분 쓰기, 캐시 무효화 누락멱등 키·아웃박스·트랜잭션 경계 재검토

권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.

배포 전에는 git addgit commitgit pushnpm run deploy 순서를 권장합니다.