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·반응성·성능 최적화」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.
- 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
- 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
- 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
- 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
- 부하 후 검증: 피크 대비 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 스냅샷 비교 |
| 빌드·배포만 실패 | 환경 변수, 권한, 플랫폼 차이, lockfile | CI 로그와 로컬 diff, 런타임·이미지 버전 핀 |
| 설정 불일치 | 프로필·시크릿·기본값, 리전 | 스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화 |
| 데이터 불일치 | 비멱등 재시도, 부분 쓰기, 캐시 무효화 누락 | 멱등 키·아웃박스·트랜잭션 경계 재검토 |
권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.
배포 전에는 git add → git commit → git push 후 npm run deploy 순서를 권장합니다.