심심한 개발자의 취미생활

파일 시스템 기반 라우팅

  • sveltekit의 핵심은 파일 시스템 기반 라우팅이다. 앱의 경로, 즉 사용자가 액세스 할 수 있는 경로는 /src/routes 안의 코드 베이스의 디렉터리에 의해 정의 된다.
  • 각 경로 디렉토리에는 접두사(+)로 식별 할 수 있는 하나 이상의 경로 파일이 포함되어 있어야 한다.
    • 모든 파일은 서버에서 실행 될 수 있다.
    • +server파일을 제외한 모든 파일은 클라이언트에서 실행된다.
    • +layout, +error파일은 해당 파일이 있는 디렉토리 뿐만 아니라 하위 디렉토리에서도 적용된다.

`+page`

`+page.svelte`

  • +page.svelte파일은 앱의 페이지를 정의한다. 기본적으로 페이지는 최초 요청 시에는 서버(SSR)에서 렌더링 되고, 이후 탐색 시에는 브라우저(CSR)에서 렌더링 된다.
<!-- src/routes/+page.svelte -->
<h1>main page</h1>
<a href="/about">about</a>
<!-- src/routes/about/+page.svelte -->
<h1>about page</h1>
<p>contents</p>
<a href="/">main</a>
  • sveltekit은 <Link> 구성 요소가 아닌 <a>태그를 사용하여 경로간 탐색을 한다.

`+page.ts`

  • 페이지가 렌더링 되기 전에 데이터를 로드 해야 경우에는 +page.ts 모듈에서 load함수를 사용한다.
import { error } from '@sveltejs/kit';
import type { PageLoad } from './$types';

export const load: PageLoad = ({ params }) => {
	if (params.slug === 'hello-world') {
		return {
			title: 'Hello world!',
			content: 'Welcome to our blog. Lorem ipsum dolor sit amet...'
		};
	}

	error(404, 'Not found');
};
  • 이 함수는 +page.svelte와 함께 실행된다. SSR 시에는 서버에서 실행되고, CSR 시에는 브라우저에서 실행된다.
  • 또한 load, +page.ts의 동작을 구성하는 값을 내보낼 수 있다.
    • export const prerender = true (false, 'auto')
    • export const ssr = true (false)
    • export const csr = true (false)

`+page.server.ts`

  • +page.ts와 역할은 동일하지만 브라우저에는 코드가 전송되지 않고 오직 서버에서만 실행된다.
  • 직접적인 데이터 베이스 접근이나 서버의 파일시스템 접근, 비공개 키값 사용 등 보안이 중요한 작업 수행을 작성한다.
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ params }) => {
	const post = await getPostFromDatabase(params.slug);

	if (post) {
		return post;
	}

	error(404, 'Not found');
};

`+error`

`+error.svelte`

  • load함수 수행중 오류가 발생하면 sveltekit은 기본 오류 페이지를 렌더링한다. 하지만 +error.svelte 파일을 추가하여 경로 별로 이 오류페이지를 사용자가 지정할 수 있다.
<script lang="ts">
	import { page } from '$app/state';
</script>

<h1>{page.status}: {page.error.message}</h1>

`+layout`

  • +layout은 여러 페이지에 걸쳐 공통적으로 사용되는 템플릿을 정의한다. 여러 페이지에 동일한 헤더, 네비게이션 바, 푸터 등을 표시하고자 할 때 +layout에 작성한다.

`+layout.svelte`

  • 모든 페이지에 적용되는 레이아웃을 만들려면 src/routes/+layout.svelte 파일을 생성하고 기본 레이아웃을 작성한다.
<!-- +layout.svelte -->
<!-- 작성된 레이아웃이 없을 경우 sveltekit의 기본 레이아웃 내용 -->
<script>
	let { children } = $props();
</script>

{@render children()}
  • 하위 페이지에만 따로 layout을 적용하고 싶다면 하위 디렉토리에 추가 적인 +layout.svelte 파일을 작성한다.
<!-- src/routes/settings/+layout.svelte -->
<script lang="ts">
	import type { LayoutProps } from './$types';

	let { data, children }: LayoutProps = $props();
</script>

<h1>Settings</h1>

<div class="submenu">
	{#each data.sections as section}
		<a href="/settings/{section.slug}">{section.title}</a>
	{/each}
</div>

{@render children()}

`+layout.ts`

  • +page.ts를 통해+page.svelte에서 데이터를 로드하는 것과 마찬가지로 +layout.svelte+layout.tsload 함수를 통해 데이터를 로드 할수 있다.
  • +layout.ts가 페이지 옵션(프리렌더, SSR 및 CSR)을 내보내면 하위 페이지의 기본값으로 사용된다.
import type { LayoutLoad } from './$types';

export const load: LayoutLoad = () => {
	return {
		sections: [
			{ slug: 'profile', title: 'Profile' },
			{ slug: 'notifications', title: 'Notifications' }
		]
	};
};

`+layout.server.ts`

  • +page.server.ts와 역할이 동일하다. 단 데이터 사용을 위해선 LayoutServerLoad로 형식을 변경해야 한다.

`+server.ts`

  • +server.ts 파일은 커스텀 API 앤드포인트를 만드는 데 사용된다. +server.ts 파일은 서버에서만 작동하며 svelte 컴포넌트를 렌더링하는 대신, HTTP 요청(Request)을 직접 처리하고 응답(Response)을 반환하는 서버 사이드 로직을 작성한다.
  • +page.server.ts가 특정 페이지를 위한 데이터를 준비한다면, +server.ts는 페이지와 독립적인 재사용 가능한 API 라우트를 생성한다.
  • 파일 안에서 GET, POST, POST, PATCH, DELETE 등 HTTP 메소드 이름으로 함수를 export 하면, 해당 메소드의 요청을 처리하는 핸들러가 된다.
  • 파일의 위치가 곧 API URL 경로가 되는 파일 기반 라우팅 기능을 지원한다.
    • src/routes/api/posts/+server.ts => /api/posts
    • src/routes/api/posts/[id]/+server.ts => /api/posts/[id]
  • +server.ts는 sveltekit 프로젝트 내에서 완전한 기능의 백엔드 API 서버를 구축할 수 있는 강력한 기능 도구 이다. 페이지 렌더링과 무관하게 순수한 데이터나 로직을 HTTP를 통해 제공하고 싶을 떄 사용된다.
import { error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const GET: RequestHandler = ({ url }) => {
	const min = Number(url.searchParams.get('min') ?? '0');
	const max = Number(url.searchParams.get('max') ?? '1');

	const d = max - min;

	if (isNaN(d) || d < 0) {
		error(400, 'min and max must be numbers, and min must be less than max');
	}

	const random = min + Math.random() * d;

	return new Response(String(random));
};

export const POST: RequestHandler = async ({ request }) => {
	const { a, b } = await request.json();
	return json(a + b);
};

export const fallback: RequestHandler = async ({ request }) => {
	return text(`I caught your ${request.method} request!`);
};

Fallback method handler

  • API 요청 메소드에 해당되는 작성된 핸들러가 없다면 fallback 메서드가 처리하게 된다.

경로 충돌

  • +server.ts 파일은 +page파일과 동일한 경로에 배치 될 수 있으며, 이를 통해 동일한 경로가 페이지 로드 또는 API 앤드 포인트가 될수 있다. sveltekit은 다음과 같은 규칙으로 어떤 경로를 사용할지 정한다.
    • PUT, PATCH, DELETE, OPTIONS 요청은 페이지 로딩에 적용되지 않으므로 +server.ts 경로가 선택된다.
    • 요청이 text/html일 경우(브라우저 페이지 요청)는 페이지 로드 요청이 선택되고, 그렇지 않을 경우 +server.ts 경로가 선택된다.
    • GET요청에 대한 응답에는 브라우저가 HTML/JSON을 선택할 수 있도록 Vary: Accept헤더가 포함된다.

`$types`

괄호 라우팅

  • sveltekit에서는 폴더명 이나 파일명을 괄호로 묶어 라우팅을 제어하거나 파라미터 입력을 받는 기능을 제공한다.
  1. 동적 라우트 - [파라미터_이름]

    • 가장 흔하게 사용되는 형태로, URL 일부를 변수(파라미터)로 만들고 싶을때 사용한다.
    • 예시
      • 파일 경로 : src/routes/shop/products/[id]/+page.svelte
      • 매칭 URL : /shop/products/123 , /shop/products/abc
      • load 함수의 params.id은 123, abc
  2. 라우트 그룹 - (그룹_이름)

    • 폴더명을 일반 괄호로 감사면, 관련 라우트 들을 그룹으로 묶을 수 있지만 URL 경로에는 영향을 주지 않는다.
    • 예시1
      • 파일 경로 : src/routes/(marketing)/about/+page.svelte
      • 매칭 URL : /about
        • (marketing)은 무시됨
    • 예시2
      • 파일 경로 : src/routes/(app)/dashboard/+page.svelte
      • 매칭 URL : /dashboard
        • (app)은 무시됨
  3. 선택적 파라미터 - [[]]

    • 동적 라우트 세그먼트가 있어도 되고 없어도 되는 경우에 사용한다.
    • 예시1
      • 파일 경로 : src/routes/[[lang]]/contact/+page.svelte
      • 매칭 URL1 : /contact
        • load 함수에서 params.lang은 undefined
      • 매칭 URL2 : /en/contact
        • load 함수에서 params.lang은 'en'
  4. 나머지 파라미터 - [... ]

    • 특정 지점 부터 나머지 모든 URL 경로를 하나의 파라미터로 잡을 때 사용한다.
    • 예시
      • 파일 경로 : src/routes/docs/[...slug]/+page.svelte
      • 매칭 URL : /docs/a/b/c
        • load 함수에서 params.slug의 값은 'a/b/c'