오늘은 SvelteKit에서 $props Rune으로 할 수 있는 Nested component와 Loading Data를 알아보겠습니다.
목차
1. SvelteKit 개발 환경 구축, 기본 구조 및 Rune
2. Nested component와 Loading Data(현재 글)
2.1. 공통 예제
2.1.1. static/shared.css
2.1.2. src/app.html
2.2. Nested component
2.2.1. src/routes/Nested.svelte
2.2.2. src/routes/+page.svelte
2.2.3. 결과
2.3. Nested component에 props 전달하기(한 번에 import)
2.3.1. src/routes/Nested.svelte
2.3.2. src/routes/+page.svelte
2.3.3. 결과
2.4. Nested component에 props 전달하기(속성 이름으로 import)
2.4.1. src/routes/+page.svelte
2.4.2. src/routes/Nested.svelte
2.5. +layout.page로 레이아웃 구성하기
2.5.1. src/routes/+layout.svelte
2.5.2. src/routes/+page.svelte
2.5.3. src/routes/test/+page.svelte
2.5.4. 결과
2.6. Loading Data
2.6.1. src/routes/+layout.svelte
2.6.2. src/routes/+page.svelte
2.6.3. src/routes/blog/data.ts
2.6.4. src/routes/blog/+page.server.ts
2.6.5. src/routes/blog/+page.svelte
2.6.6. src/routes/blog/[slug]/+page.server.ts
2.6.7. src/routes/blog/[slug]/+page.svelte
2.6.8. 결과
3. Node 서버로 빌드(2025. 05. 31. 업로드 예정)
1편과 달리 2편부터는 최상위 폴더(프로젝트명 폴더)를 생략하고 경로를 설명하겠습니다.
2.1. 공통 예제
2.2~2.6절에 모두 적용되는 공통 예제 코드입니다.
2.1.1. static/shared.css
h1{background-color: #aaa;}
nav{
background: #e0e6eb;
z-index: 2;
border-radius: 0.5em;
gap: 1em;
margin: 0 0 1em;
padding: 1em;
display: flex;
position: relative
}
nav a{text-decoration: none}
nav a[aria-current=true]{border-bottom: 2px solid}
2.1.2. src/app.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
<link rel="stylesheet" href="/shared.css" />
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
8행을 주목하겠습니다.
<link rel=”stylesheet” href=”/shared.css” />
이 코드를 추가해 줍니다.
2.2. Nested component
여러 번 반복될 만한 부분이 있다면 별도 component로 만들어 둔 뒤 nesting하여 사용할 수 있습니다.
2.2.1. src/routes/Nested.svelte
<p>This is another paragraph.</p>
2.2.2. src/routes/+page.svelte
<script lang="ts">
import Nested from "./Nested.svelte";
</script>
<svelte:head>
<title>Welcome to SvelteKit</title>
</svelte:head>
<h1>Welcome to SvelteKit</h1>
<p>This is a paragraph.</p>
<Nested/>
<style>
p {
color: goldenrod;
font-family: 'Comic Sans MS', cursive;
font-size: 2em;
}
</style>
[Line 2]
import Nested from “./Nested.svelte”;
Nested component를 사용하기 위해 import합니다.
[Line 11]
Nested.svelte에 작성한 내용이 삽입될 위치에 <Nested/>라고 씁니다.
2.2.3. 결과
“This is a paragraph.”와 “This is another paragraph.” 모두 p 태그에 묶이지만, 원래 src/routes/+page.svelte에 있던 “This is a paragraph.”만 src/routes/+page.svelte에 작성된 style 태그의 스타일이 적용되었으며, Nested component의 내용인 “This is another paragraph.”는 src/routes/+page.svelte에 작성된 style 태그의 스타일이 적용되지 않았습니다.
다만, /static/shared.css에 작성된 스타일은 Nested component에도 적용됩니다.
2.3. Nested component에 props 전달하기(한 번에 import)
2.3.1. src/routes/Nested.svelte
<script lang="ts">
let props = $props();
</script>
<p>This is {props.paragraphName} paragraph.</p>
[Line 2]
let props = $props();
$props()는 Rune 중 하나입니다.
[Line 5]
<p>This is {props.paragraphName} paragraph.</p>
paragraphName은 아래 이어지는 src/routes/+page.svelte에서 등장합니다.
2.3.2. src/routes/+page.svelte
<script lang="ts">
import Nested from "./Nested.svelte";
</script>
<svelte:head>
<title>Welcome to SvelteKit</title>
</svelte:head>
<h1>Welcome to SvelteKit</h1>
<p>This is a paragraph.</p>
<Nested paragraphName="nested"/>
<style>
p {
color: goldenrod;
font-family: 'Comic Sans MS', cursive;
font-size: 2em;
}
</style>
[Line 2]
import Nested from “./Nested.svelte”;
Nested component를 사용하기 위해 import합니다.
[Line 11]
<Nested paragraphName=”nested”/>
paragraphName 속성값을 입력해 주었습니다. 이것이 src/routes/Nested.svelte에서 props.paragraphName 값이 되는 것입니다.
2.3.3. 결과
src/routes/+page.svelte의 11행에서 paragraphName 속성값을 “nested”로 입력하였으므로 “This is nested paragraph.”라고 출력되었습니다.
2.4. Nested component에 props 전달하기(속성 이름으로 import)
2.4.1. src/routes/+page.svelte
이번에는 src/routes/+page.svelte 먼저 보겠습니다.
<script lang="ts">
import Nested from "./Nested.svelte";
</script>
<svelte:head>
<title>Welcome to SvelteKit</title>
</svelte:head>
<h1>Welcome to SvelteKit</h1>
<p>This is a paragraph.</p>
<Nested paragraphName="nested"/>
<style>
p {
color: goldenrod;
font-family: 'Comic Sans MS', cursive;
font-size: 2em;
}
</style>
[Line 2]
import Nested from “./Nested.svelte”;
Nested component를 사용하기 위해 import합니다.
[Line 11]
<Nested paragraphName=”nested”/>
paragraphName 속성값을 입력해 주었습니다. 이것이 src/routes/Nested.svelte에서 props.paragraphName 값이 되는 것입니다.
2.4.2. src/routes/Nested.svelte
<script lang="ts">
let {paragraphName} = $props();
</script>
<p>This is {paragraphName} paragraph.</p>
[Line 2]
let paragraphName = $props();
이번에는 2.3.1절과 달리 변수명을 src/routes/+page.svelte에서 사용하는 속성 이름으로 했습니다.
[Line 5]
<p>This is {paragraphName} paragraph.</p>
그러면 HTML 코드 부분에서 속성값을 불러올 때도 그냥 {paragraphName}만 쓰면 됩니다.
결과는 2.3절과 같습니다.
2.5. +layout.page로 레이아웃 구성하기
- 📁.svelte-kit
- 📁node_modules
- 📁src
- 📁lib
- 📁routes
- app.d.ts
- app.html: 2.1.2절 공통 예제
- 📁static
- favicon.png
- shared.css: 2.1.1절 공통 예제
- .gitignore
- .npmrc
- package-lock.json
- package.json
- README.md
- svelte.config.js
- tsconfig.json
- vite.config.ts
2.5.1. src/routes/+layout.svelte
src/routes/+layout.svelte는 액자나 틀 같은 것이라고 생각하시면 됩니다.
<script lang="ts">
let {children}=$props();
</script>
<h1>Welcome to SvelteKit</h1>
<nav>
<a href="/">home</a>
<a href="/test">test</a>
</nav>
{@render children()}
[Line 2]
let {children}=$props();
액자나 틀 안에 넣을 내용(자식 내용)을 넣기 위해 {children} 변수를 선언합니다.
[Line 12]
{@render children()}
자식 내용이 들어갈 자리에 이 코드를 작성해 줍니다.
2.5.2. src/routes/+page.svelte
<svelte:head>
<title>Welcome to SvelteKit</title>
</svelte:head>
<p>This is the home page.</p>
2.5.3. src/routes/test/+page.svelte
<svelte:head>
<title>Welcome to Test</title>
</svelte:head>
<p>하이퍼링크 잘 되죠?</p>
2.5.4. 결과
잘 적용되었습니다.
/test 페이지에 들어가 보면 +layout.svelte의 내용은 공통적으로 적용되어 있고 +page.svelte의 내용만 바뀐 것을 보실 수 있습니다.
2.6. Loading Data
데이터를 별도 파일에 작성해 두고 불러오는 방법을 알아보겠습니다.
- 📁.svelte-kit
- 📁node_modules
- 📁src
- 📁static
- favicon.png
- shared.css: 2.1.1절 공통 예제
- .gitignore
- .npmrc
- package-lock.json
- package.json
- README.md
- svelte.config.js
- tsconfig.json
- vite.config.ts
2.6.1. src/routes/+layout.svelte
<script lang="ts">
let {children}=$props();
</script>
<nav>
<a href="/">home</a>
<a href="/blog">blog</a>
</nav>
{@render children()}
2.6.2. src/routes/+page.svelte
<svelte:head>
<title>Welcome to SvelteKit</title>
</svelte:head>
<p>This is the home page.</p>
2.6.3. src/routes/blog/data.ts
export const posts = [
{
slug: 'welcome',
title: 'Welcome to the Aperture Science computer-aided enrichment center',
content:
'<p>We hope your brief detention in the relaxation vault has been a pleasant one.</p><p>Your specimen has been processed and we are now ready to begin the test proper.</p>'
},
{
slug: 'safety',
title: 'Safety notice',
content:
'<p>While safety is one of many Enrichment Center Goals, the Aperture Science High Energy Pellet, seen to the left of the chamber, can and has caused permanent disabilities, such as vaporization. Please be careful.</p>'
},
{
slug: 'cake',
title: 'This was a triumph',
content: "<p>I'm making a note here: HUGE SUCCESS.</p>"
}
];
SvelteKit Tutorial의 코드를 그대로 가져오되, 확장명만 .ts로 바꾸었습니다.
posts 배열을 만들었습니다.
2.6.4. src/routes/blog/+page.server.ts
import type {PageServerLoad} from './$types';
import {posts} from './data';
export const load: PageServerLoad = () => {
return{
summaries: posts.map((post)=>({
slug: post.slug,
title: post.title
}))
}
}
+page.server.ts이므로 Server Side에서 실행됩니다.
[Line 1]
import type {PageServerLoad} from ‘./$types’;
+page.server.ts에서 쓰기 위한 type을 import합니다.
[Line 2]
import {posts} from ‘./data’;
src/routes/blog/data.ts의 posts 배열을 import합니다.
[Line 4]
export const load: PageServerLoad = () => {
PageServerLoad type 변수 load를 선언합니다. 오른쪽의 () => {는 람다 함수 시작 부분입니다.(람다 함수는 11행까지 이어짐.)
[Line 5~10]
5행)return{
6행) summaries: posts.map((post)=>({
7행) slug: post.slug,
8행) title: post.title
9행) }))
10행)}
람다 함수의 return 부분입니다. load 함수는 객체를 return합니다.
객체 안에는 summaries가 들어 있으며, summaries의 값은 또 다른 객체의 배열입니다. posts 배열을 이용하여 객체를 만들 것입니다(참고로 posts의 원소가 객체). posts의 원소를 post라고 했을 때 7~8행으로 구성된 객체의 배열이 생성됩니다.
2.6.5. src/routes/blog/+page.svelte
<script lang="ts">
import type { PageProps } from './$types';
let {data}: PageProps=$props();
</script>
<svelte:head>
<title>Welcome to SvelteKit</title>
</svelte:head>
<h1>blog</h1>
<ul>
{#each data.summaries as {slug, title}}
<li><a href="/blog/{slug}">{title}</a></li>
{/each}
</ul>
[Line 2]
import type { PageProps } from ‘./$types’;
Props를 위한 type을 import합니다.
[Line 3]
let {data}: PageProps=$props();
PageProps형 변수 data를 선언하고 $props() Rune을 사용합니다. src/routes/blog/+page.server.ts의 load 함수에서 return한 값들을 가져옵니다.
[Line 13]
{#each data.summaries as {slug, title}}
data.summaries의 원소 개수만큼 반복하는 반복문을 돌립니다. 이때 as {slug, title}} 키워드를 사용하여 {slug}와 {title}로 바로 접근할 수 있도록 합니다.
[Line 14]
<li><a href=”/blog/{slug}”>{title}</a></li>
페이지 목록을 만듭니다.
2.6.6. src/routes/blog/[slug]/+page.server.ts
[slug] 폴더는 src/routes/blog/data.ts의 slug를 파라미터로 가져오겠다는 뜻입니다.
import type {PageServerLoad} from './$types';
import {posts} from '../data';
import {error} from '@sveltejs/kit';
export const load: PageServerLoad = ({ params }) => {
const post=posts.find((post)=>post.slug===params.slug);
if (!post) error(404);
return{post};
}
[Line 1]
import type {PageServerLoad} from ‘./$types’;
+page.server.ts에서 쓰기 위한 type을 import합니다.
[Line 2]
import {posts} from ‘../data’;
src/routes/blog/data.ts의 posts 배열을 import합니다.
주의) src/routes/blog/+page.server.ts는 from ‘./data‘를 쓰지만, src/routes/blog/[slug]/+page.server.ts에서는 from ‘../data‘를 써야 합니다.
[Line 3]
import {error} from ‘@sveltejs/kit’;
에러 코드를 출력하기 위해 import합니다.
[Line 5]
export const load: PageServerLoad = ({ params }) => {
PageServerLoad type 변수 load를 선언합니다. 람다 함수의 매개변수 중 params는 URL의 파라미터를 불러옵니다.
[Line 6]
const post=posts.find((post)=>post.slug===params.slug);
post 변수를 선언합니다. 배열의 find method는 함수 조건을 만족하는 첫 번째 요소를 반환합니다.
[Line 8]
if (!post) error(404);
post)=>post.slug===params.slug를 만족하지 않으면(잘못된 URL로 접속하면) 에러 코드 404를 출력합니다.
[Line 10]
return{post};
{post}를 반환합니다.
2.6.7. src/routes/blog/[slug]/+page.svelte
<script lang="ts">
import type { PageProps } from './$types';
let {data}: PageProps=$props();
</script>
<svelte:head>
<title>{data?.post?.title}</title>
</svelte:head>
<h1>{data?.post?.title}</h1>
<div>{@html data?.post?.content}</div>
[Line 2]
import type { PageProps } from ‘./$types’;
Props를 위한 type을 import합니다.
[Line 3]
let {data}: PageProps=$props();
PageProps형 변수 data를 선언하고 $props() Rune을 사용합니다. src/routes/blog/[slug]/+page.server.ts의 load 함수에서 return한 값들을 가져옵니다.
[Line 7]
<title>{data?.post?.title}</title>
탭 제목은 data?.post?.title로 합니다. narrowing을 위해 . 대신 ?.을 사용합니다.
[Line 10]
<h1>{data?.post?.title}</h1>
이 제목도 마찬가지입니다.
[Line 11]
<div>{@html data?.post?.content}</div>
data?.post?.content를 출력하되, HTML 태그를 살리기 위해 @html을 앞에 붙입니다.
2.6.8. 결과
blog를 클릭해 보겠습니다.
글 목록이 나옵니다.
글을 클릭하면 글 내용이 잘 출력됩니다.
2.7. 글 마무리
제 글을 읽어 주셔서 감사합니다. 다음에 만나요!
2.8. 참고 자료
1) Svelte contributors. 2025. “Props”, Svlete Docs. (2025. 05. 16. 방문). https://svelte.dev/docs/svelte/$props
2) Svelte contributors. 2025. “Loading Data”, Svlete Docs. (2025. 05. 16. 방문). https://svelte.dev/docs/kit/load
3) Svelte contributors. 2025. “Page Data”, Svlete Tutorial. (2025. 05. 16. 방문). https://svelte.dev/tutorial/kit/page-data
4) Svelte contributors. 2025. “Layout Data”, Svlete Tutorial. (2025. 05. 16. 방문). https://svelte.dev/tutorial/kit/layout-data
5) denev6. 2025. “SvelteKit과 Svelte 기초”, 낙서장. (2025. 04. 26. 방문). https://denev6.tistory.com/208
6) sting01. 2023. “svelte 5 rune”, sting01.log. (2025. 05. 09. 방문). https://velog.io/@sting01/svelte-5-rune
7) Jeol Hager 외. 2022. “SvelteKit PageLoad module not found”, stackoverflow. (2025. 05. 09. 방문). https://stackoverflow.com/questions/74469661/sveltekit-pageload-module-not-found
8) khromov. 2023. “How to use static folder in sveltekit”, 사이트명. (2025. 05. 09. 방문). https://www.reddit.com/r/sveltejs/comments/17e540x/how_to_use_static_folder_in_sveltekit/?rdt=63933
9) 나루. 2022. “SvelteKit 현재 url, query string, params 구하기”, 오솔길. (2025. 05. 16. 방문). https://osg.kr/archives/686
10) Nathan Sebhastian. 2022. “JavaScript Map - JS.map() 함수 사용 방법 (배열 메소드)”, freeCodeCamp. (2025. 05. 16. 방문). https://www.freecodecamp.org/korean/news/javascript-map-method/