Маршрутизация
Основой SvelteKit является файловый маршрутизатор. Маршруты вашего приложения (то есть URL-пути, доступные пользователям) определяются структурой директорий в вашем проекте:
src/routes— корневой маршрутsrc/routes/aboutсоздаёт маршрут/aboutsrc/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илиfalseexport 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 может экспортировать параметры страницы:
prerenderssrcsr
Файл +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.