🚨 SvelteKit은 초기 개발 단계입니다. 따라서 버전이 1.0에 도달하기 전까지는 몇 가지 부분이 변경될 수 있습니다.
- SvelteKit 공식문서 -
🚨 이 글에서 나오는 대부분의 예제 코드는 SvelteKit 공식문서에서 가져왔음을 알립니다.
Svelte가 뭔지 모른다면? 여기로
React에 NextJS, Vue에 Nuxt가 있듯이 Svelte에는 SvelteKit이 있다. NextJS와 Nuxt와 같이 SSR, SSG 등 여러가지 다양한 기능들을 제공한다. SvelteKit의 가장 매력적인 부분은 Svelte를 사용한다는 것이다. Svlete와 Vite를 사용하여 정말 말로 할 수 없을 정도로 만족스러운 개발자 경험을 누릴 수 있다.
💡 Vite에 대해서는 나중에 글을 쓸 예정이다.
SvelteKit이 지원하는 기능으로는
1. Build Optimizations
2. Offline support
3. Prefetch
4. Configurable Rendering
5. SSR
6. CSR
7. SSG
또한 SvelteKit은 기본적으로 Vite를 내장하고 있어서 HMR도 기본적으로 지원한다.
사용해보면 알겠지만 HMR은 정말 엄청난 개발자 경험을 제공한다. (저장 버튼을 누루는 순간에 페이지가 새로고침되어 있다!)
SvelteKit을 사용하기 위한 셋업은 매우 간단하다.
npm init svelte@next my-app
cd my-app
yarn
yarn run dev
NextJS를 배워본 사람이라면 SvelteKit의 Routing은 이미 배웠다고 생각해도 된다. SvelteKit은 filesystem-based router
을 사용하여 src/routes
폴더 안에 있는 파일들은 SvelteKit이 자동으로 파일 이름에 따라 routing을 해준다. 예를들어, home.svelte
라는 파일을 src/routes
폴더 안에 넣으면 /home
주소로 갔을때 자동으로 home.svelte
를 렌더링해준다.
동적 라우팅을 사용하고 싶다면 NextJS에서 하던것처럼 파일 이름을 다음과 같이 써주면 된다.
[id].svelte
products라는 폴더안에 위와 같은 파일을 만들면 products/1, products/2 등 뒤에 어떤 숫자가 붙더라도 SvleteKit이 모두 [id].svelte로 연결시킨다.
내 생각에는 이게 NextJS와 SvelteKit에서 가장 멋진 부분이라고 생각한다. 엔드 포인트는 다른 파일들과는 다르게 .ts
혹은 .js
로 작성하는데 이곳에 작성한 코드들은 브라우저에서 작동하는 것이 아니라 오직 서버에서만 동작한다. 이 말은 보안상 굉장히 안전한 위치에 있다는 것으로 엔드 포인트를 사용한다면 백엔드 없이 바로 DB에 접근할 수도 있다.
또한 페이지와 엔드 포인트의 파일명이 같다면 페이지의 Props을 엔드 포인트로부터 가져온다! 이 모든 것이 자동으로 이루어지기에 개발자는 아무런 설정을 할 필요가 없다.
예시 코드
import db from '$lib/database';
/** @type {import('@sveltejs/kit').RequestHandler} */
export async function get({ params }) {
// `params.id` comes from [id].js
const item = await db.get(params.id);
if (item) {
return {
body: { item }
};
}
return {
status: 404
};
}
밑의 Svelte 코드의 item은 위의 코드에서 자동으로 가져온 것이다.
<script>
// populated with data from the endpoint
export let item;
</script>
<h1>{item.title}</h1>
또한 get, post, put patch, del 등의 함수들을 제공하여 NextJS에서 하던것처럼 req.method에서 method가 뭔지 if문을 사용해서 구별하는 그런 귀찮은 일을 하지 않아도 된다.
import * as db from '$lib/database';
export async function get() {
const items = await db.list();
return {
body: { items }
};
}
export async function post({ request }) {
const [errors, item] = await db.create(request);
if (errors) {
// return validation errors
return {
status: 400,
body: { errors }
};
}
// redirect to the newly created item
return {
status: 303,
headers: {
location: `/items/${item.id}`
}
};
}
<!-- src/routes/items.svelte -->
<script>
// The page always has access to props from `get`...
export let items;
// ...plus props from `post` when the page is rendered
// in response to a POST request, for example after
// submitting the form below
export let errors;
</script>
{#each items as item}
<Preview item={item}/>
{/each}
<form method="post">
<input name="title">
{#if errors?.title}
<p class="error">{errors.title}</p>
{/if}
<button type="submit">Create item</button>
</form>
다음 예시에서 볼 수 있듯이 SvelteKit이 알아서 get으로 들어오는 요청은 get함수를 post로 들어오는 요청은 post함수를 실행시킨다. 이러한 방식을 사용하니 코드가 놀랍도록 단순하고 깨끗해진다. 비교를 위해 get, post 함수를 사용하지 않는 코드를 들고와봤다.
export default async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'POST') {
// put some code you want!
}
res.statusCode = 405;
return res.end();
};
위의 코드는 NextJS의 엔드 포인트이다. (NextJS에서도 이를 엔드 포인트라고 부르는지는 잘 모르겠다.) 들어온 요청에 따라서 이 요청이 GET인지 POST인지 일일이 분류하는 작업이 필요하다. 나는 이런 자잘한 코드들이 가독성을 떨어뜨리는 원인이 될 수 있다고 생각하기 때문에 SvelteKit의 이런 기능들은 정말 좋다고 생각한다.
request.body
에도 쉽게 접근할 수 있다.
export async function post({ request }) {
const data = await request.formData(); // or .json(), or .text(), etc
}
다음과 같이 request뒤에 원하는 데이터 포맷을 붙여주기만 하면 쉽게 원하는 데이터를 추출해 낼 수 있다.
HTML의 Form은 기본적으로 GET과 POST밖에 지원하지 않는다. 하지만 SvelteKit에서 설정을 한다면 PUT, PATCH, DELETE 같은 다른 메서드도 사용할 수 있다.
// svelte.config.js
export default {
kit: {
methodOverride: {
allowed: ['PUT', 'PATCH', 'DELETE']
}
}
};
<form method="post" action="/todos/{id}?_method=PUT">
<!-- form elements -->
</form>
파일이나 폴더 이름을 _ 혹은 .으로 시작하면 그 파일, 폴더는 라우팅되지 않는다.
src/routes/[category]/[item].svelte
or src/routes/[category]-[item].svelte
💡 parameter가 x-y-z와 같이 모호한 경우에는 x를 category로 y-z를 item으로 판별한다.
src/routes/[a].js
src/routes/[b].svelte
src/routes/[c].svelte
src/routes/[...catchall].svelte
src/routes/foo-[bar].svelte
위와 같은 파일들이 있을때 /foo-abc
라는 주소는 모든 파일에 매치된다. 따라서 SvleteKit은 다음과 같은 규칙에 따라 위의 파일들을 정렬한다.
src/routes/foo-[bar].svelte
)src/routes/foo-[bar].svelte
src/routes/[a].js
src/routes/[b].svelte
src/routes/[c].svelte
src/routes/[...catchall].svelte
위의 규칙에 따라 파일들은 다음과 같이 정렬되고 /foo-abc
는 src/routes/foo-[bar].svelte
와 매치된다.
위의 규칙에 따라 /foo-def
도 src/routes/foo-[bar].svelte
에 매치되어야만 하는데 /foo-def
만 src/routes/foo-[bar].svelte
가 아니라 src/routes/[b].svelte
에 매치시키고 싶다면 fallthrough
라는 옵션을 사용하면 된다.
<script context="module">
export function load({ params }) {
if (params.bar === 'def') {
return { fallthrough: true };
}
// ...
}
</script>
export function get({ params }) {
if (params.a === 'foo-def') {
return { fallthrough: true };
}
// ...
}
위와 같이 src/routes/foo-[bar].svelte
과 src/routes/[a].svelte
에서 모두 fallthrough 옵션을 사용하여 요청을 흘려보내준다면 src/routes/[b].svelte
에 도달하게 된다.
많은 웹 앱에서는 모든 페이지에 보여야만 하는 요소들이 존재한다, Header, Footer 등은 모든 페이지에 존재해야하며 이를 모든 페이지에 일일이 복사 붙여넣기 하는 일은 매우 귀찮은 일이다. 하지만 Layout components를 사용한다면 귀찮음을 한결 덜 수 있다.
먼저 Layout components를 만들기 위해서는 src/routes/__layout.svelte
라는 파일을 생성해야한다.
기본 레이아웃은 다음과 같이 생겼다.
<slot></slot>
레이아웃 페이지에는 어떤 것이든 추가할 수 있지만 무조건 <slot>
을 포함하고 있어야 한다. 만약 레이아웃에 <nav>
를 추가한다고 하면 다음과 같이 작성해야만 한다.
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/settings">Settings</a>
</nav>
<slot></slot>
레이아웃 페이지에 nav를 작성했다면 어떤 페이지로 가더라도 항상 nav를 볼 수 있다. (더 이상 복사 붙여넣기를 할 필요가 없다!)
실제 앱에서는 /settings
페이지 하나만 존재하는 경우는 잘 없다. 대부분 /settings/profile
, /settings/notifications
등 nested page를 가지고 있고 이 페이지들은 중복되는 요소들을 가지는 경우가 많다. 따라서 /settings
밑에 있는 파일들에게만 적용되는 layout을 만들고 싶다면 src/routes/settings/__layout.svelte
라는 파일을 만들면 된다.
__layout.reset.svelte
라는 파일을 만든다면 그 폴더에 있는 파일들은 기존에 만들었던 레이아웃의 영향을 받지 않고 기본 레이아웃을 사용하게 된다. 예를 들어 src/routes/admin/__layout.reset.svelte
라는 파일을 만든다면 /admin
폴더 아래에 있는 파일들은 모두 기본 레이아웃을 사용하게 된다.
만약 페이지를 로드하는데 실패한다면 SvelteKit은 우리를 위해 Error page를 띄워줄 수 있다. 그리고 __error.svelte
라는 파일을 통해 Error page를 커스텀 할 수도 있다.
예를 들어 src/routes/settings/notifications/index.svelte
가 로딩에 실패했다면 SvelteKit이src/routes/settings/notifications/__error.svelte
를 렌더링하려고 할 것이고 src/routes/settings/notifications/__error.svelte
라는 파일이 없다면 src/routes/settings/__error.svelte
혹은 src/routes/__error.svelte
을 렌더링한다.
또한 에러 페이지가 load
함수를 가지고 있다면 error
와 status
라는 인자를 받게 된다.
<script context="module">
/** @type {import('@sveltejs/kit').ErrorLoad} */
export function load({ error, status }) {
return {
props: {
title: `${status}: ${error.message}`
}
};
}
</script>
<script>
export let title;
</script>
<h1>{title}</h1>
page나 layout으로 지정된 컴포넌트들은 load
라는 함수를 사용할 수 있다. load
함수는 컴포넌트가 생성되기 전에 실행되는 함수이다. 이 함수를 사용한다면 컴포넌트가 생성되기 전에 데이터를 fetch 할 수 있어 로딩을 없앨 수 있다.
데이터가 엔드 포인트로부터 온다면 이 함수가 필요하지 않겠지만 외부 API를 사용하는 경우 이 함수를 유용하게 사용할 수 있다.
<script context="module">
/** @type {import('@sveltejs/kit').Load} */
export async function load({ params, fetch, session, stuff }) {
const response = await fetch(`https://cms.example.com/article/${params.slug}.json`);
return {
status: response.status,
props: {
article: response.ok && (await response.json())
}
};
}
</script>
load
함수는 NextJS의 getStaticProps
이나 getServerSideProps
과 매우 유사하게 작동한다. 하지만 load
함수는 client와 server 모두 위에서 작동한다는 차이점이 있다.
SvelteKit의 load
는 특별한 기능을 가진 fetch
를 가지는데
더 자세한 내용은 여기로
오늘은 SvelteKit에 대해 알아보았다. 이 글에서 언급하지 않은 Hooks, Modules, Service workers, Anchor options 등에 대해 알아보고 싶다면 여기서 알아보자. (영어라서 읽기 힘들다는 단점이 있다.)
sveltekit 좋아요! 좋은 정보 감사합니다 :)