데이터 로드
+page.svelte 컴포넌트를 렌더링하기 전에 +layout.svelte 데이터를 가져와야 하는 경우가 많다. 이는 load 함수를 정의하여 수행된다.
Page data
+page.svelte 파일은 로드 함수를 내보내는 형제 파일이 있을 수 있으며, 반환 값은 $props()를 통해 페이지에서 사용할 수 있다.
import type { PageLoad } from './$types';
export const load: PageLoad = ({ params }) => {
return {
post: {
title: `Title for ${params.slug} goes here`,
content: `Content for ${params.slug} goes here`
}
};
};
<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
</script>
<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>
+page.ts 파일 내의 load 함수는 서버와 브라우저에서 모두 실행된다. (export const ssr = false가 설정 된 경우는 브라우저에서만 실행된다.)
load함수가 항상 서버에서만 실행되어야 하는 경우는 동일한 경로의 +page.server.ts 파일에 작성한다.
import * as db from '$lib/server/database';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params }) => {
return {
post: await db.getPost(params.slug)
};
};
- 서버 함수가 추가 인수에 접근할 수 있으므로
PageLoad형에서 PageServerLoad로 변경된다.
Layout data
+layout.ts파일 또는 +layout.server.ts파일은 +layout.svelte 를 통해 데이터를 로드할 수 있다.
import * as db from '$lib/server/database';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async () => {
return {
posts: await db.getPostSummaries()
};
};
<script lang="ts">
import type { LayoutProps } from './$types';
let { data, children }: LayoutProps = $props();
</script>
<main>
{@render children()}
</main>
<aside>
<h2>More posts</h2>
<ul>
{#each data.posts as post}
<li>
<a href="/blog/{post.slug}">
{post.title}
</a>
</li>
{/each}
</ul>
</aside>
- layout에서
load 함수를 통해 리턴된 데이터는 하위 +layout.svelte와 +page.svelte 구성 요소 뿐만 아니라 해당 구성 요소가 속하는 레이아웃에서도 사용할 수 있다.
layout과 page에서 동일한 변수 명으로 반환한 데이터는 page에서 반환된 데이터로 출력된다.
page.data
+page.svelte와 +layout.svelte는
- 자식은 부모의 데이터에 접근 할 수 있지만, 그 반대는 기본적을 불가능 하다. 하지만 때로는 상향식으로 데이터가 필요한 경우가 있는데 이러한 데이터 흐름의 역전을 가능하게 해주는 것이 바로
$page스토어 이다.
- sveltekit은 앱의 현재 페이지 상태에 대한 정보를 담고 있는
$page라는 특별한 스토어를 제공하는데 이 스토어 안에는 여러 유용한 정보가 있다. 그중 $page.data 는 현재 활성화된 라우트에 대한 모든 load 함수의 반환 값을 합친 객체이다.
export const load = () => {
const post = {
title: 'load test title',
content: 'load test content'
}
return {
title: post.title,
post: post
}
}
<!-- src/routes/+layout.js -->
<script lang="ts">
import { page } from "$app/state";
import { onMount } from "svelte";
let { children } = $props();
onMount(() => {
console.log("main >>", page);
});
</script>
<!-- sub/+page.svelte 페이지 로딩시 <h1>load test title</h1> 출력 -->
<h1>{page.data.title}</h1>
{@render children?.()}
부모 데이터의 사용
await parent()를 사용하면 자식 load함수에서 부모 load함수의 데이터에 엑세스 할 수 있다.
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = () => {
return { a: 1 };
};
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = async ({ parent }) => {
const { a } = await parent();
return { b: a + 1 };
};
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ parent }) => {
const { a, b } = await parent();
return { c: a + b };
};
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
</script>
<!-- renders `1 + 2 = 3` -->
<p>{data.a} + {data.b} = {data.c}</p>
`parent()`의 기본 동작
parent() 함수는 server파일과 universal 파일에서 각각 다르게 동작하며, 서로의 데이터 체인을 침범하지 않는 것이 기본 원칙이다.
+layout.server.ts => +layout.server.ts => +page.server.ts => load - parent()
+layout.ts => +layout.ts => +page.ts => load - parent()
'투명한 통로'와 '가려짐(shadowing)' 현상
- sveltekit은 중간에
+layout.ts 파일이 없으면 그 위치에 서버에서 온 데이터를 그대로 통과 시키는 투명한 함수 ({ data }) => data가 있다고 간주 한다.
src/routes/
└── sales/
├── +layout.server.ts // (A)
└── report/
├── +layout.ts // (B)
└── +page.server.ts // (C)
- 데이터 흐름
sales/+layout.server.ts => report/+page.server.ts (C) => load - parent()
sales/+layout.server.ts (A) => sales/({data}) => data (투명한 통로) => report/+layout.ts (B) => load - parent()
- 만약
sales/+layout.ts 파일을 추가한다면 report/+layout.ts에서 parent() 호출 할 경우 새로 생신 sales/+layout.ts에서 load함수가 반환하는 값을 받게 된다. sales/+layout.ts의 load에서 다른 서버 데이터를 가공하거나 다른 값을 반환 한다면 sales/+layout.server.ts (A)의 데이터는 report/+layout.ts (B)에 직접 도달하지 못하고 '가려 지게'된다.
성능 최적화
parent()함수는 부모 load함수들이 모두 실행 될 때까지 기다려야 하므로 시간이 걸릴 수 있다.
export const load = async () => {
const parentData = await parent();
const myData = await getData(params)
return {...parentData, myData}
}
export const load = async () => {
const myData = getData(params)
const parentData = await parent();
return {...parentData, myData: await myData}
}
Universal vs server
load 함수는 서버와 브라우저 모두에서 실행되는 +page.ts, +layout.ts와 서버에서만 실행되는 +page.server.ts, +layout.server.ts 두 가지가 있다.
기본적으로는 범용 load함수는 사용자가 페이지를 처음 방문할 때 SSR 중에 서버에서 실행된다. 그런 다음 하이드 레이션 중에 다시 실행 되어 페치 요청의 응답을 재사용한다. 이후 모든 범용 함수는 브라우저에서 실행된다.
- 다만 페이지 옵션을 통해 서버측 렌더링을 비활성화 하면
load 범용 함수는 항상 클라이언트에서 실행된다.
- 유니버설과 서버
load 기능이 모두 포함된 경우라면 서버 load가 먼저 실행 된다.
load는 일반적으로 런타임에 호출 되지만, 페이지를 미리 렌더링 하는 경우에는 빌드 시점에 호출 된다.
구조의 분리
load함수가 실행 환경에 따라 분리 되는 구조는 관심사의 분리를 가능하게 한다.
Server load는 어떻게 데이터를 안전하게 가져올 것인가?
Universal load는 무엇을 페이지에 보여줄 것인가?
Server load는 데이터의 원천 소스를 다루고 Universal load는 그 데이터를 받아 최종적으로 프론트 엔드에 전달할 데이터를 가공하고 필터링 하는 게이트 웨이 역할을 하는 것이다.
Argument Input
- 두 실행 환경의
load 함수가 한 페이지에 동시에 존재할 경우 Server load 함수의 반환 값은 Universal load함수 인자의 data 속성이 된다.
- 두 실행 환경의
load 함수는 대부분의 인자를 공유하지만 실행 환경에 따라 특별한 인자를 가진다.
공통 인자
Universal Load 함수는 Server Load 함수의 대부분의 인자를 공유하고 이 인자 들은 LoadEvent (Universal)와 ServerLoadEvent (Server)객체에 포함되어 있다.
params : 라우트 파라미터 객체
route : 현재 라우트에 대한 정보 객체
url : 현재 페이지의 URL에 대한 정보를 담고 있는 표준 URL 객체
fetch : 서버와 클라이언트 양쪽에서 모두 동작하는 fetch 함수. 일반 fetch와 달리 서버에서 실행될 때도 상대 경로를 사용할 수 있고, 쿠키와 같은 인증 정보를 자동으로 처리해준다.
setHeaders : 응답 헤더를 설정하는 함수, 주로 캐싱 제어에 사용된다.
parent : 부모 load함수에서 반환된 데이터를 가져오는 함수 const data = await parent() 와 같이 사용된다.
depends : 특정 데이터에 대한 의존성을 수동으로 등록하는 함수, 데이터 무효화 및 재실행을 제어할 때 사용한다.
untrack : load함수 내에서 반응성 추적을 피하고 싶을 때 사용하는 함수.
`Server Load` 에만 있는 인자들
ServerLoadEvent는 LoadEvent의 모든 속성을 상속 받으며, 서버에서만 접근 가능하고 절대 브라우저에 노출 되어서는 안 되는 민감한 정보들을 추가로 가진다.
clientAddress : 사용자의 IP 주소
cookies : 들어오는 요청의 쿠키를 읽거나 나가는 응답에 쿠키를 설정할 수 있는 헬퍼. cookies.get('sessionId'), cookies.set('theme', 'dark')
locals : 미들웨어에서 요청에 추가한 커스텀 데이터. 주로 인증된 사용자 정보등을 담는 데 사용된다.
platform : Vercel, Netlify, Cloudflare Workers 등 배포 환경에 따른 컨텍스트 정보.
request : SvelteKit에 의해 가공되기 전의 원시 표준 Request 객체
Output
Universal load와 Server load 함수의 반환 값은 각 함수가 실행되는 환경과 네트워크 경계의 존재의 유무 때문에 차이가 발생한다.
Universal load: 서버와 브라우저, 즉 데이터를 소비하는 컴포넌트와 같은 공간에서 실행되 수 있어, 네트워크 전송을 위한 직렬화 과정이 필요 없기에 거의 모든 유형의 값을 반환할 수 있다.
Server load: 오직 서버에서만 실행되며, 그 결과는 네트워크를 통해 브라우저로 전송된다. 따라서 반환 값은 반드시 네트워크로 전송 가능한 형태, 즉 직렬화가 가능한 데이터여야만 한다.
- 만약
devalue가 지원하지 않는 커스텀 타입을 꼭 네트워크로 전송해야 한다면, Transport Hooks(hooks.server.ts에 정의)를 사용하여 해당 타입을 어떻게 직렬화 하고 역 직렬화를 할지 sveltekit에 직접 설정할 수 있다.
`Universal load`함수의 반환 값
Universal load 함수는 반환 값에 거의 제약이 없다. 서버 렌더링 시에는 load 함수와 페이지 컴포넌트는 동일한 서버 프로세스 내에서 중간에 네트워크를 거치지 않는다. 클라이언트 렌더링 시에는 사용자가 페이지를 이동할 때 Universal load 함수와 페이지 컨포넌트는 동일한 브라우저 환경 내에서 실행되므로 여기서도 값을 직접 전달할 수 있다.
- 일반적인 JSON 데이터
- Date, Map, Set, RegExp 객체
- 커스텀 클래스의 인스턴스 (new MyClass())
- svelte 컴포넌트 생성자
- 함수
`Server load`함수의 반환 값
Server load는 서버에서 실행되고, 그 결과는 네트워크를 통해 브라우저로 전송된다. 이 과정에서 메모리에 있는 복잡한 데이터 구조는 네트워크로 보낼 수 있는 텍스트 형태로 변환되어야 하는데, 이 변환 과정을 직렬화 라고 한다.
- sveltekit은 JSON.stringify 보다 강력한
devalue라는 라이브러리를 사용하여 직렬화를 수행한다.
- JSON 으로 표현 가능한 모든 것
- BitInt
- Date
- Map
- Set
- RegExp
- 순환 참조 또는 반복 참조가 있는 객체
- undefined
- Promise
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async () => {
return {
serverMessage: 'hello from server load function'
};
};
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ data }) => {
return {
serverMessage: data.serverMessage,
universalMessage: 'hello from universal load function'
};
};
URL Data
load 함수는 URL에 따라 기능이 달라 질수 있다. 이를 위해 load함수는 url, route, params 데이터를 제공한다.
params
- params는 동적 라우트 세그먼트의 값을 담고 있는 객체이다. 파일이나 폴더 이름을 대괄호로 묶어 생성한 동적 경로의 실제 값에 접근할 때 사용한다.
- 주요 사용 사례
{
'post': 'posting1'
}
---
{
'post': 'posting2',
'tag': 'js/svelte'
}
URL
- 현재 페이지의 URL에 대한 모든 정보를 담고 있는 Javascript
URL 객체 이다. params가 경로의 일부만 제공하는 것과 달리 url은 URL에 대한 접근을 제공한다.
- 주요 사용 사례
- 쿼리 파라미터 가져오기
(url.searchParams(...))
- 전체 경로가 필요한 경우
- URL 객체
URL {
href: 'http://localhost:5173/test?a=1&b=2',
origin: 'http://localhost:5173',
protocol: 'http:',
username: '',
password: '',
host: 'localhost:5173',
hostname: 'localhost',
port: '5173',
pathname: '/test',
search: '?a=1&b=2',
searchParams: URLSearchParams { 'a' => '1', 'b' => '2' },
hash: ''
}
url.searchParams.get('category')
url.searchParams.get('limit')
route
- route는 현재 요청과 일치하는 sveltekit의 라우트 패턴 정보를 담고 있는 객체이다. 실제 URL 경로가 아닌, sveltekit이 내부적으로 사용하는 라우드 ID에 접근할 때 사용한다.
- 주요 사용 사례
- 캐싱(caching) : 라우트 기반으로 캐시 키를 생성할 때
- 분석(analytics) : 어떤 라우트 패턴이 사용되었는지 추적할 때
- 레이아웃 분기 처리 : 특정 라우트 그룹에 따라 다른 레이아웃 로직을 적용하고 싶을 때
params나 url에 비해 사용 빈도는 낮지만, 고급 제어가 필요할 때 유용하다.
{
'id': '/blog/[slug]'
}