출시·고도화 중
SvelteKit 안내서 · 3/6
SvelteKit을 이해하려면 두 층을 알아야 합니다. 화면을 만드는 Svelte 컴포넌트와 룬(rune), 그리고 그 컴포넌트를 주소와 묶어 서버와 브라우저에서 렌더링하는 SvelteKit의 라우팅과 렌더링 방식입니다.
.svelte 파일 하나가 컴포넌트 하나입니다. <script>에 로직, 그 아래에 마크업, <style>에 스타일을 두며, <style>은 기본적으로 그 컴포넌트에만 적용됩니다. Svelte는 이 파일을 컴파일해 가상 DOM 없이 바뀐 부분만 직접 갱신하는 코드로 만듭니다.
현재 세대의 Svelte(Svelte 5)는 반응성을 룬이라는 $로 시작하는 특수 기호로 표현합니다. 룬은 함수처럼 보이지만 컴파일러가 해석하는 문법이므로 가져올(import) 필요가 없습니다.
| 룬 | 용도 |
|---|---|
$state | 바뀌면 화면이 갱신되는 상태 |
$derived | 다른 상태에서 계산되는 값 |
$effect | 상태가 바뀐 뒤 실행할 부수 효과(DOM 조작, 타이머, 외부 라이브러리) |
$props | 부모가 넘긴 속성 받기 |
$bindable | 부모와 양방향으로 묶을 수 있는 속성 표시 |
<script lang="ts">
let items = $state<string[]>([]);
let text = $state('');
let total = $derived(items.length);
let empty = $derived(total === 0);
$effect(() => {
document.title = `할 일 ${total}개`;
});
function add() {
if (text.trim()) items.push(text.trim());
text = '';
}
</script>
<input bind:value={text} />
<button onclick={add}>추가</button>
{#if empty}
<p>할 일이 없습니다.</p>
{:else}
<ul>
{#each items as item}
<li>{item}</li>
{/each}
</ul>
{/if}$state로 만든 배열과 객체는 깊은 반응성을 가지므로 items.push()처럼 직접 고쳐도 화면이 갱신됩니다. $effect는 남용하지 말고, 계산할 수 있는 값은 $derived로 표현하는 것이 좋습니다. $effect는 브라우저에서만 실행되고 서버 렌더링 중에는 실행되지 않습니다.
자식 컴포넌트는 $props()로 속성을 받습니다. 예전의 export let 문법과 on:click 지시어 대신, 속성 구조 분해와 onclick 같은 일반 속성을 씁니다. 자식에게 넘기는 마크업은 스니펫이 되며 {@render ...}로 그립니다.
<!-- src/lib/components/Card.svelte -->
<script lang="ts">
import type { Snippet } from 'svelte';
let { title, highlighted = false, children }: {
title: string;
highlighted?: boolean;
children: Snippet;
} = $props();
</script>
<section class:highlighted>
<h2>{title}</h2>
{@render children()}
</section><script lang="ts">
import Card from '#lib/components/Card.svelte';
</script>
<Card title="공지" highlighted>
<p>내일 점검이 있습니다.</p>
</Card>SvelteKit에서는 src/routes의 폴더 구조가 곧 주소입니다. +page.svelte가 페이지, +layout.svelte가 그 폴더와 하위 페이지를 감싸는 틀입니다. 레이아웃은 children 스니펫을 받아 페이지가 들어갈 자리에 그립니다.
<!-- src/routes/+layout.svelte -->
<script lang="ts">
import { page } from '$app/state';
let { children } = $props();
</script>
<nav>
<a href="/" aria-current={page.url.pathname === '/' ? 'page' : undefined}>홈</a>
<a href="/blog">블로그</a>
</nav>
<main>
{@render children()}
</main>$app/state의 page는 현재 주소, 매개변수, 페이지 데이터, 오류 정보를 담은 반응형 객체입니다. 이전 판의 $app/stores와 $page 저장소는 현재 판에서 제거되었습니다. 링크는 일반 <a> 태그로 쓰며, SvelteKit이 클릭을 가로채 페이지 전체를 새로 받지 않고 필요한 부분만 바꾸는 클라이언트 이동을 수행합니다.
SvelteKit은 기본적으로 첫 요청을 서버에서 렌더링(SSR) 해 완성된 HTML을 보내고, 브라우저에서 하이드레이션해 상호작용을 붙인 뒤 이후 이동은 클라이언트 렌더링(CSR) 으로 처리합니다. 이 동작은 +page.ts나 +layout.ts에서 페이지 옵션으로 바꿉니다.
// src/routes/about/+page.ts
export const prerender = true; // 빌드할 때 정적 HTML로 미리 만든다
// src/routes/dashboard/+layout.ts 에서는
// export const ssr = false; // 브라우저에서만 렌더링(SPA처럼)
// export const csr = false; // 자바스크립트 없이 HTML만 보낸다| 옵션 | 기본값 | 의미 |
|---|---|---|
prerender | false | true면 빌드 때 HTML 생성, 'auto'는 생성하면서 서버 렌더링 경로도 남김 |
ssr | true | false면 빈 틀만 보내고 브라우저에서 렌더링 |
csr | true | false면 자바스크립트를 보내지 않음 |
루트 레이아웃에 옵션을 두면 앱 전체에, 하위 폴더에 두면 그 아래 페이지에만 적용됩니다. 블로그 글이나 문서처럼 누구에게나 같은 페이지는 프리렌더링, 로그인한 사용자별 화면은 SSR, 편집기처럼 브라우저 API에 크게 기대는 화면은 ssr = false가 어울립니다.
$state, $derived, $effect, $props 같은 룬으로 표현합니다.onclick 같은 속성으로, 자식 마크업은 스니펫과 {@render}로 다룹니다.src/routes의 +page.svelte와 +layout.svelte가 화면과 공통 틀을 만듭니다.prerender, ssr, csr 옵션으로 렌더링 방식을 고릅니다.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.