Svelte 5 완벽 가이드 | Runes·SvelteKit·반응성·성능 최적화
이 글의 핵심
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>
정리 및 체크리스트
핵심 요약
- 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. 문법이 다르지만 개념은 비슷합니다. 컴포넌트 단위로 점진적 마이그레이션이 가능합니다.