Маршрутизация
Основой SvelteKit является файловый маршрутизатор. Маршруты вашего приложения (то есть URL-пути, доступные пользователям) определяются структурой директорий в вашем проекте:
src/routes
— корневой маршрутsrc/routes/about
создаёт маршрут/about
src/routes/blog/[slug]
создаёт маршрут с параметромslug
, который позволяет динамически загружать данные при запросе страницы, например/blog/hello-world
Каждая директория маршрута содержит один или несколько файлов маршрутов, которые можно распознать по префиксу +
.
Далее мы подробно рассмотрим эти файлы, но вот несколько простых правил, которые помогут понять, как работает маршрутизация в SvelteKit:
- Все файлы могут выполняться на сервере
- Все файлы выполняются на клиенте, кроме
+server
- Файлы
+layout
и+error
применяются как к текущей директории, так и ко всем вложенным
+page.svelte
Заголовок раздела «+page.svelte»Компонент +page.svelte
определяет страницу вашего приложения. По умолчанию страницы рендерятся как на сервере (SSR) при первом запросе, так и в браузере (CSR) при последующей навигации.
<h1>Приветствую и добро пожаловать на мой сайт!</h1><a href="/about">О моем сайте</a>
<h1>О моем сайте</h1><p>TODO...</p><a href="/">Главная</a>
Страницы могут получать данные из load
-функций через проп data
.
<script> /** @type {import('./$types').PageProps} */ let { data } = $props();</script>
<h1>{data.title}</h1><div>{@html data.content}</div>
<script lang="ts"> import type { PageProps } from './$types';
let { data }: PageProps = $props();</script>
<h1>{data.title}</h1><div>{@html data.content}</div>
+page.js
Заголовок раздела «+page.js»Часто странице требуется загрузить данные перед рендерингом. Для этого добавляется модуль +page.js
, экспортирующий функцию load
:
import { error } from '@sveltejs/kit';
/** @type {import('./$types').PageLoad} */export function load({ params }) { if (params.slug === 'hello-world') { return { title: 'Привет, мир!', content: 'Добро пожаловать на наш блог. Lorem ipsum dolor sit amet...' }; }
error(404, 'Страница не найдена');}
import { error } from '@sveltejs/kit';import type { PageLoad } from './$types';
export const load: PageLoad = ({ params }) => { if (params.slug === 'hello-world') { return { title: 'Привет, мир!', content: 'Добро пожаловать на наш блог. Lorem ipsum dolor sit amet...' }; }
error(404, 'Страница не найдена');};
Эта функция работает в связке с +page.svelte
, что означает её выполнение:
- на сервере во время серверного рендеринга (SSR)
- в браузере при клиентской навигации (CSR)
Подробное описание API доступно в разделе load
.
Помимо load
, модуль +page.js
может экспортировать значения для настройки поведения страницы:
export const prerender = true
илиfalse
, или'auto'
export const ssr = true
илиfalse
export const csr = true
илиfalse
Подробнее об этих настройках читайте в разделе Параметры страницы.
+page.server.js
Заголовок раздела «+page.server.js»Если ваша функция load
должна выполняться только на сервере — например, когда необходимо:
получать данные из базы данных или обращаться к защищённым переменным окружения (например, API-ключам) — то вы можете:
- Переименовать
+page.js
→+page.server.js
- Изменить тип
PageLoad
→PageServerLoad
import { error } from '@sveltejs/kit';
/** @type {import('./$types').PageServerLoad} */export async function load({ params }) { const post = await getPostFromDatabase(params.slug);
if (post) { return post; }
error(404, 'Страница не найдена');}
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, 'Страница не найдена');};
При клиентской навигации SvelteKit будет загружать эти данные с сервера, что означает:
- возвращаемое значение должно быть сериализуемо с помощью devalue
- полное описание API доступно в разделе
load
Как и +page.js
, модуль +page.server.js
может экспортировать параметры страницы:
prerender
ssr
csr
Файл +page.server.js
также может экспортировать actions (серверные действия). Если load
позволяет читать данные с сервера, то actions
позволяют записывать данные на сервер через элемент <form>
- подробнее см. в разделе Действия форм
При возникновении ошибки в load
SvelteKit отображает страницу ошибки по умолчанию. Её можно переопределить для каждого маршрута, добавив файл +error.svelte
:
<script> import { page } from '$app/state';</script>
<h1>{page.status}: {page.error.message}</h1>
<script lang="ts"> import { page } from '$app/state';</script>
<h1>{page.status}: {page.error.message}</h1>
SvelteKit будет «подниматься по дереву» в поисках ближайшей границы ошибки:
- Если указанный выше файл не существует, будет проверен
src/routes/blog/+error.svelte
- Затем
src/routes/+error.svelte
- И только затем отобразится стандартная страница ошибки
Если и это не сработает (или если ошибка возникла в функции load
корневого +layout
, который находится “выше” корневого +error
), SvelteKit переключится на статическую резервную страницу ошибки. Её можно кастомизировать, создав файл src/error.html
.
Особые случаи:
- При ошибке в
load
функции+layout(.server).js
используется ближайший+error.svelte
файл выше этого макета (не рядом с ним) - Для 404 ошибок используется
src/routes/+error.svelte
(или стандартная страница ошибки, если файл не существует)
Подробнее об обработке ошибок можно прочитать здесь.
+layout
Заголовок раздела «+layout»До сих пор мы рассматривали страницы как полностью независимые компоненты — при навигации текущий компонент +page.svelte
уничтожается и заменяется новым.
Однако во многих приложениях есть элементы, которые должны отображаться на каждой странице, такие как:
- главное меню
- подвал (футер)
Вместо того чтобы дублировать их в каждом +page.svelte
, мы можем вынести их в макеты.
+layout.svelte
Заголовок раздела «+layout.svelte»Чтобы создать макет, который будет применяться ко всем страницам, создайте файл src/routes/+layout.svelte
. Стандартный макет (который SvelteKit использует, если вы не предоставите свой собственный) выглядит следующим образом…
<script> let { children } = $props();</script>
{@render children()}
…но мы можем добавить любую разметку, стили и поведение. Единственное обязательное требование — компонент должен содержать тег @render
для отображения содержимого страницы. Например, добавим навигационную панель:
<script> let { children } = $props();</script>
<nav> <a href="/">Главная</a> <a href="/about">О сайте</a> <a href="/settings">Настройки</a></nav>
{@render children()}
<script lang="ts"> let { children } = $props();</script>
<nav> <a href="/">Главная</a> <a href="/about">О сайте</a> <a href="/settings">Настройки</a></nav>
{@render children()}
Если мы создадим страницы для следующих маршрутов:
/
(главная страница)/about
(страница «О сайте»)/settings
(страница настроек)
<h1>Главная<</h1>
<h1>О сайте</h1>
<h1>Настройки</h1>
…навигация будет оставаться видимой, и переход между тремя страницами будет приводить только к замене содержимого <h1>
.
Макеты могут быть вложенными. Предположим, у нас есть не просто страница /settings
, а вложенные страницы вроде /settings/profile
и /settings/notifications
с общим подменю (реальный пример: github.com/settings).
Мы можем создать макет, который применяется только к страницам внутри /settings
(при этом наследуя корневой макет с основной навигацией):
<script> /** @type {import('./$types').LayoutProps} */ let { data, children } = $props();</script>
<h1>Настройки</h1>
<div class="submenu"> {#each data.sections as section} <a href="/settings/{section.slug}">{section.title}</a> {/each}</div>
{@render children()}
<script lang="ts"> import type { LayoutProps } from './$types';
let { data, children }: LayoutProps = $props();</script>
<h1>Настройки</h1>
<div class="submenu"> {#each data.sections as section} <a href="/settings/{section.slug}">{section.title}</a> {/each}</div>
{@render children()}
Как заполняется data
, можно увидеть в примере +layout.js
из следующего раздела ниже.
По умолчанию каждый макет наследует родительский макет. Если это нежелательно, могут помочь расширенные макеты.
+layout.js
Заголовок раздела «+layout.js»Аналогично тому, как +page.svelte
получает данные из +page.js
, компонент +layout.svelte
может получать данные из функции load
в +layout.js
.
/** @type {import('./$types').LayoutLoad} */export function load() { return { sections: [ { slug: 'profile', title: 'Профиль' }, { slug: 'notifications', title: 'Уведомления' } ] };}
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = () => { return { sections: [ { slug: 'profile', title: 'Профиль' }, { slug: 'notifications', title: 'Уведомления' } ] };};
Если +layout.js
экспортирует параметры страницы — prerender
, ssr
и csr
— они будут использоваться как значения по умолчанию для дочерних страниц.
Данные, возвращаемые из функции load
макета, также доступны всем его дочерним страницам:
<script> /** @type {import('./$types').PageProps} */ let { data } = $props();
console.log(data.sections); // [{ slug: 'profile', title: 'Profile' }, ...]</script>
<script lang="ts"> import type { PageProps } from './$types';
let { data }: PageProps = $props();
console.log(data.sections); // [{ slug: 'profile', title: 'Profile' }, ...]</script>
+layout.server.js
Заголовок раздела «+layout.server.js»Для выполнения функции load
макета на сервере переместите её в +layout.server.js
и измените тип LayoutLoad
на LayoutServerLoad
.
Как и +layout.js
, файл +layout.server.js
может экспортировать параметры страницы — prerender
, ssr
и csr
.
+server
Заголовок раздела «+server»Помимо страниц, вы можете определять маршруты с помощью файла +server.js
(иногда называемого «API-роутом» или «эндпойнтом»), что даёт полный контроль над ответом. Файл +server.js
экспортирует функции, соответствующие HTTP-методам: GET
, POST
, PATCH
, PUT
, DELETE
, OPTIONS
и HEAD
, которые принимают аргумент RequestEvent
и возвращают объект Response
.
Например, можно создать маршрут /api/random-number
с обработчиком GET
:
import { error } from '@sveltejs/kit';
/** @type {import('./$types').RequestHandler} */export function GET({ 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` и `max` должны быть числами, причем `min` должен быть меньше `max`'); }
const random = min + Math.random() * d;
return new Response(String(random));}
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` и `max` должны быть числами, причем `min` должен быть меньше `max`'); }
const random = min + Math.random() * d;
return new Response(String(random));};
Первый аргумент Response
может быть ReadableStream
, что позволяет передавать большие объёмы данных потоком или создавать server-sent events (за исключением платформ с буферизацией ответов, таких как AWS Lambda).
Вы можете использовать методы error
, redirect
и json
из @sveltejs/kit
для удобства (но это не обязательно).
При возникновении ошибки (либо через error(...)
, либо непредвиденной) ответ будет представлен в виде JSON-описания ошибки или резервной страницы ошибки (которую можно настроить через src/error.html
) в зависимости от заголовка Accept
. В этом случае компонент +error.svelte
не будет отображен. Подробнее об обработке ошибок можно прочитать здесь.
Получение данных
Заголовок раздела «Получение данных»Экспортируя обработчики POST
, PUT
, PATCH
, DELETE
, OPTIONS
или HEAD
, файлы +server.js
могут использоваться для создания полноценного API:
<script> let a = 0; let b = 0; let total = 0;
async function add() { const response = await fetch('/api/add', { method: 'POST', body: JSON.stringify({ a, b }), headers: { 'content-type': 'application/json' } });
total = await response.json(); }</script>
<input type="number" bind:value={a}> +<input type="number" bind:value={b}> ={total}
<button onclick={add}>Подсчитать</button>
<script lang="ts"> let a = 0; let b = 0; let total = 0;
async function add() { const response = await fetch('/api/add', { method: 'POST', body: JSON.stringify({ a, b }), headers: { 'content-type': 'application/json' } });
total = await response.json(); }</script>
<input type="number" bind:value={a}> +<input type="number" bind:value={b}> ={total}
<button onclick={add}>Подсчитать</button>
import { json } from '@sveltejs/kit';
/** @type {import('./$types').RequestHandler} */export async function POST({ request }) { const { a, b } = await request.json(); return json(a + b);}
import { json } from '@sveltejs/kit';import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ request }) => { const { a, b } = await request.json(); return json(a + b);};
Обработчик fallback
Заголовок раздела «Обработчик fallback»Экспорт обработчика fallback
позволяет обрабатывать любые необработанные методы запросов, включая такие методы как MOVE
, для которых нет отдельных экспортов из +server.js
.
import { json, text } from '@sveltejs/kit';
/** @type {import('./$types').RequestHandler} */export async function POST({ request }) { const { a, b } = await request.json(); return json(a + b);}
// Данный обработчик будет отвечать на запросы PUT, PATCH, DELETE и аналогичные/** @type {import('./$types').RequestHandler} */export async function fallback({ request }) { return text(`Я перехватил ваш запрос ${request.method}!`);}
import { json, text } from '@sveltejs/kit';import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ request }) => { const { a, b } = await request.json(); return json(a + b);};
// Данный обработчик будет отвечать на запросы PUT, PATCH, DELETE и аналогичныеexport const fallback: RequestHandler = async ({ request }) => { return text(`Я перехватил ваш запрос ${request.method}!`);};
Согласование содержимого
Заголовок раздела «Согласование содержимого»Файлы +server.js
могут располагаться в том же каталоге, что и файлы +page
, позволяя одному маршруту быть либо страницей, либо API-эндпойнтом. Правила определения:
- Запросы
PUT
/PATCH
/DELETE
/OPTIONS
всегда обрабатываются+server.js
, так как они не применимы к страницам - Запросы
GET
/POST
/HEAD
обрабатываются как запросы страниц, если заголовокaccept
указывает приоритетtext/html
(т. е. это запрос страницы из браузера), в противном случае они обрабатываются+server.js
- Ответы на
GET
-запросы включают заголовокVary: Accept
, чтобы прокси и браузеры кэшировали HTML и JSON ответы раздельно
Во всех приведённых примерах мы импортировали типы из файла $types.d.ts
. Этот файл автоматически генерируется SvelteKit в скрытой директории при использовании TypeScript (или JavaScript с аннотациями типов JSDoc), обеспечивая безопасность типов при работе с корневыми файлами.
Например, аннотация let { data } = $props()
с типом PageProps
(или LayoutProps
для файла +layout.svelte
) указывает TypeScript, что тип data
соответствует данным, возвращаемым из функции load
:
<script> /** @type {import('./$types').PageProps} */ let { data } = $props();</script>
<script lang="ts"> import type { PageProps } from './$types';
let { data }: PageProps = $props();</script>
В свою очередь, аннотация функции load
с помощью PageLoad
, PageServerLoad
, LayoutLoad
или LayoutServerLoad
(для +page.js
, +page.server.js
, +layout.js
и +layout.server.js
соответственно) гарантирует правильную типизацию параметров params
и возвращаемого значения.
Если вы используете VS Code или другую IDE с поддержкой Language Server Protocol и TypeScript плагинов, вы можете полностью опустить эти типы! Инструментарий Svelte для IDE автоматически подставит правильные типы, обеспечивая проверку типов без их явного указания. Это также работает с нашей командной строкой svelte-check
.
Подробнее об опускании $types
можно прочитать в нашем блоге.
Другие файлы
Заголовок раздела «Другие файлы»Все остальные файлы в директории маршрута игнорируются SvelteKit. Это позволяет располагать компоненты и вспомогательные модули рядом с маршрутами, которые их используют.
Если компоненты и модули нужны нескольким маршрутам, рекомендуется размещать их в $lib
.