Перейти к содержанию

Загрузка страницы и серверные данные

Маршруты «Маршрута» теперь позволяют напрямую открыть отдельный урок. Данные для него выбирает функция load, а компонент отображает полученный объект. Рассмотрим, чем загрузка публичного каталога отличается от чтения личного состояния. Результат урока — ясная граница: сервер получает доверенные сведения, страница получает только необходимую проекцию.

Нужно помнить устройство +page.ts из предыдущей главы. Он подходит для общедоступной фикстуры, однако не должен импортировать секреты или серверный репозиторий. SvelteKit предлагает +page.server.ts для операций, которые выполняются на сервере. В этой статье используем вымышленного демонстрационного читателя только для описания формы ответа; настоящая сессия появится позже. Пример не запускался.

Две среды одной страницы

Универсальная загрузка может участвовать в серверном построении страницы и в последующей браузерной навигации. Следовательно, её зависимости должны быть допустимы в обеих средах. Каталог с названиями уроков отвечает этому условию. Ключ подписи сессии, пароль хранилища и запрос личных закладок — нет.

Серверная загрузка исполняется на сервере, но возвращённые ею данные передаются странице. Имя файла защищает место выполнения, а не превращает весь результат в секрет. Если вернуть поле passwordHash, оно всё равно окажется среди данных, доступных клиенту. Поэтому защита требует двух решений: правильного модуля для чтения и явного отбора полей для выдачи.

Заменим универсальный файл маршрута урока на полный src/routes/lessons/[id]/+page.server.ts; прежний +page.ts для этого маршрута удаляется. Здесь пока возвращаются только публичные сведения, поэтому изменение среды не добавляет личных данных автоматически.

import { error } from '@sveltejs/kit';
import { lessons } from '$lib/catalog';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = ({ params }) => {
  const lesson = lessons.find(item => item.id === params.id);
  if (!lesson) error(404, 'Урок не найден');
  return { lesson: { id: lesson.id, title: lesson.title, minutes: lesson.minutes } };
};

Ожидаем тот же заголовок и длительность, что прежде. Такое сохранение результата показывает, что выбор среды и форма отображения — отдельные вопросы. Вынос на сервер нужен тогда, когда источник действительно требует серверных возможностей, а не как универсальный способ ускорения любой страницы. Различия server и universal load описаны в документации.

Проекция вместо целой записи

Представим, что будущий репозиторий читателя возвращает объект с внутренним идентификатором, временем создания, технической версией, закладками и завершёнными уроками. Экрану требуется только безопасное имя и два набора идентификаторов. Проекцией называется новый объект, специально собранный для этого экрана.

Следующий фрагмент показывает контракт будущей серверной загрузки кабинета. Он ещё не подменяет отсутствующий репозиторий и не является самостоятельной программой: reader здесь означает запись, полученную после проверки сессии в серверных главах.

const view = {
  reader: { displayName: reader.displayName },
  bookmarks: [...reader.bookmarks],
  completed: [...reader.completed],
  version: reader.version
};
return view;

Копирование массивов обозначает границу владения, хотя само по себе не решает авторизацию. Страница видит снимок состояния на момент загрузки. Она не получает прямую ссылку на объект серверного хранилища и не может изменить его обычным присваиванием. Для записи потребуется отдельный POST с проверяемыми параметрами.

Не возвращайте в load функции репозитория или открытые соединения. Передача данных между средами требует подходящего представления. Простые строки, числа, массивы и объекты удобны для учебного ответа. Если источник содержит специальные значения, их сериализация должна соответствовать поддерживаемому контракту SvelteKit, а не предположению, что любой объект одинаково передаётся в браузер.

Зависимость от адреса

Загрузка получает params и url, поэтому адрес может определять выбранный урок или фильтр каталога. Это предпочтительнее чтения глобального window.location на серверном шаге: у сервера нет браузерного окна, но есть текущий запрос. При прямом открытии URL параметры уже известны до построения интерфейса.

Допустим, добавлен фильтр / ?q=css без пробела в реальном адресе. Получение строки из url.searchParams делает фильтр воспроизводимым по ссылке. Но личный идентификатор пользователя не следует брать из такого параметра. Человек может изменить query string, поэтому будущий кабинет обязан получать владельца из сессии, а фильтр — из URL. Это разные уровни доверия к входным данным.

Если фильтр приводит к пустому набору, корректным результатом может быть страница с пояснением «Уроки не найдены». Не всякое отсутствие строк является HTTP-ошибкой. Поиск существует и успешно обработан, просто его результат пуст. Напротив, неизвестный адрес конкретного урока означает отсутствие самого запрошенного ресурса и получает 404.

Загрузка не записывает прогресс

Чтение страницы должно оставаться чтением. Не добавляйте завершение урока внутрь load: функция может выполняться повторно из-за навигации или обновления данных. В результате обычный переход или предварительная загрузка стал бы менять историю читателя. Просмотр материала тоже не доказывает его завершение, поэтому предметная модель запрещает такую запись.

На небольшом примере легко принять повторную загрузку за событие ровно один раз. После появления клиентской навигации, ошибок сети и нескольких вкладок эта уверенность исчезнет. Действия записи нужно делать явными и устойчивыми к повтору. В следующих главах мы заменим локальный toggle формой с конкретным желаемым состоянием, чтобы повтор запроса не переворачивал отметку обратно.

Проследите путь будущего запроса словами: браузер выбирает адрес, сервер проверяет сессию, загрузка читает состояние владельца, формирует проекцию, компонент её отображает. На каждом переходе понятно, кто знает личность и кто владеет данными. Такой разбор полезнее, чем просто перенос всех файлов на сервер.

Данные страницы после навигации

При переходе с HTML на CSS компонент страницы может быть использован снова с новыми данными. Поэтому обращение к data.lesson.title должно оставаться зависимым от текущего data, а не от однажды скопированного названия. Если объявить обычный снимок в начале компонента и забыть его обновить, адрес уже изменится, а заголовок останется прежним.

Для производного значения используйте $derived, когда оно действительно нужно отдельно. Для простого вывода достаточно обращения к data. Это продолжает различие состояния и вычисления из второго урока, теперь на границе навигации.

Не переносите текущие данные страницы в общий серверный модуль ради удобного импорта. Контекст запроса уже содержит нужную область жизни. Глобальная копия не знает, кто её последний изменил и какой запрос читает следом. Передавайте проекцию через предусмотренную загрузку, чтобы зависимость оставалась видимой в типе страницы.

Текущее состояние проекта содержит серверную загрузку публичного урока и подготовленный контракт личного ответа. Кабинет ещё не заявляет о сохранённой истории. Следующий урок разберёт, когда HTML формируется на запрос, когда заранее при сборке и почему персональный результат нельзя включить в общий prerender.

Оглавление серии