Расширенная маршрутизация

Обработка страницы 404

Любой пользователь может зайти на URL-адрес, который на вашем сайте не существует. Например, если пользователь заходит на https://example.com/does-not-exist, то сервер должен ответить кодом состояния HTTP 404, а на странице должно быть хоть какое-то объяснение произошедшего, а не просто пустая страница.

Для каждого заданного маршрута Qwik City может выбрать, как лучше всего для пользователя обработать ответ 404, будь то страница 404 по умолчанию, пользовательская страница 404 или динамически создаваемая страница 404.

Страница 404 по умолчанию

Вместо того чтобы показывать пустую страницу, Qwik City по умолчанию предоставляет типовую страницу 404 для любого маршрута, который не обрабатывается. Страница 404 по умолчанию рендерится в качестве запасного варианта, когда не найдена пользовательская страница 404. Страница Qwik City по умолчанию - это хорошо и всё такое, но мы рекомендуем создать пользовательскую страницу 404, чтобы пользователю было удобнее. Например, предоставить ему общий заголовок и навигацию, чтобы он мог найти страницу, которую он ищет.

Пользовательская страница 404

Вместо того чтобы показывать стандартный (скучный) ответ 404, можно создать собственную страницу 404, используя те же привычные макеты, что и на остальных страницах вашего сайта.

Чтобы создать пользовательскую страницу 404, добавьте файл 404.tsx в корневой каталог src/routes.

src/
└── routes/
    ├── 404.tsx            # Пользовательская страница 404
    ├── layout.tsx         # Макет по умолчанию
    └── index.tsx          # https://example.com/

В приведенном примере страница 404.tsx также будет использовать макет layout.tsx, поскольку она является соседом этого макета в том же каталоге.

Кроме того, использование маршрутизации на основе каталога Qwik City позволяет создавать пользовательские страницы 404 по разным путям. Например, если в структуру проекта был добавлен файл src/routes/account/404.tsx, то пользовательская страница 404 будет применяться только для маршрутов /account/*, а все остальные страницы 404 будут использовать корневую страницу 404.tsx.

Примечание: Во время разработки и в режиме предварительного просмотра пользовательские страницы 404 не будут отображаться, вместо них будет отображаться стандартная страница 404 Qwik City. Однако при сборке приложения для производственной среды пользовательская страница 404 будет сгенерирована в виде статического файла 404.html.

src/
└── routes/
    ├── account/
       └── 404.tsx        # Пользовательская страница 404 для маршрута `account/*``
       └── index.tsx      # https://example.com/account/
    ├── 404.tsx            # Пользовательская страница 404
    ├── layout.tsx         # Макет по умолчанию
    └── index.tsx          # https://example.com/

Стоит отметить, что пользовательские страницы 404 генерируются статически во время сборки, в результате чего получается статический файл 404.html, а не отдельно рендерящиеся страницы на стороне сервера. Эта стратегия снижает нагрузку на ваш HTTP-сервер, позволяя избежать рендера страниц 404 на стороне сервера и тем самым сохранить ресурсы.

Динамическая страница 404

После рендера страницы по умолчанию всегда возвращается ответ с кодом состояния HTTP 200, который сообщает браузеру что всё в порядке и маршрут существует. Однако, можно перехватить рендер страницы, и вручную установить код статуса ответа, отличный от 200, например 404.

Допустим, у нас есть страница товара с URL-адресом https://example.com/product/abc. Страница товара обрабатывается с помощью маршрута на основе каталога src/routes/product/[id]/index.tsx, а [id] является динамическим параметром URL-адреса.

В этом примере id используется в качестве ключа для загрузки данных о товаре из базы данных. Если данные о товаре найдены, отлично, мы можем отобразить данные. Однако, если данные о товаре не найдены в базе данных, мы всё равно можем обработать рендер этой страницы, но ответить кодом состояния HTTP 404.

import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
 
export const useProductLoader = routeLoader$(async ({ params, status }) => {
  // Пример вызова базы данных с использованием параметра id.
  // База данных может вернуть `null`, если товар не найден.
  const data = await productDatabase.get(params.id);
 
  if (!data) {
    // Данные о товаре не найдены.
    // Меняем код статуса ответа на 404.
    status(404);
  }
 
  // Возвращаем данные (которые могут быть `null`).
  return data;
});
 
export default component$(() => {
  // Получаем данные о товаре из загрузчика.
  const product = useProductLoader();
 
  if (!product.value) {
    // Данные о товаре не найдены.
    // Поэтому рендерим нашу пользовательскую страницу 404 для товара.
    return <p>Извините, у нас нет этого товара.</p>;
  }
 
  // Данные о товаре были найдены, так что давайте отобразим их.
  return (
    <div>
      <h1>{product.value.name}</h1>
      <p>{product.value.price}</p>
      <p>{product.value.description}</p>
    </div>
  );
});

Сгруппированные макеты

Маршруты общего назначения часто помещают в каталоги, чтобы они могли совместно использовать макеты, и чтобы связанные исходные файлы были логически сгруппированы друг с другом. Однако может возникнуть желание, чтобы каталог, который использовался для группировки похожих файлов и совместного использования макетов, был исключен из общедоступных URL-адресов. Здесь поможет "группировка" макетов (так называемый макет маршрута "без пути").

Если заключить имя каталога в круглые скобки - (имя), то этот каталог не будет включён в URL-адрес.

Например, предположим, что все маршруты, содержащие account, расположены в одном каталоге /account/. Однако, он может быть удалён из URL-адреса для получения более чистых и кратких URL-адресов. В приведённом ниже примере обратите внимание на то, что пути находятся в каталоге src/routes/(account)/, однако URL-адреса исключают (account)/.

src/
└── routes/
    └── (account)/         # Обратите внимание на круглые скобки
        ├── layout.tsx     # Макет для account
        └── profile/
            └── index.tsx  # https://example.com/profile
        └── settings/
            └── index.tsx  # https://example.com/settings

Именованный макет

Иногда маршруты нуждаются в макетах, коренным образом отличающихся от макетов соседних маршрутов. В этом случае можно определить несколько макетов для одноуровневых маршрутов. Один макет по умолчанию и любое количество именованных макетов. Затем дочерний маршрут может запросить определённый именованный макет.

Qwik City определяет соглашение о том, что макеты находятся внутри каталога src/routes, а имена файлов начинаются с layout. Именно поэтому макет по умолчанию называется layout.tsx. Именованный макет также начинается с layout, за которым следует тире - и уникальное имя, например layout-narrow.tsx.

Чтобы сослаться на именованный макет, файл маршрута index.tsx должен иметь суффикс @<имя>. Например, index@narrow.tsx будет использовать макет layout-narrow.tsx.

src/
└── routes/
    ├── contact/
       └── index@narrow.tsx      # https://example.com/contact (Макет: layout-narrow.tsx)
    ├── layout.tsx                # Макет по умолчанию
    ├── layout-narrow.tsx         # Именованный макет
    └── index.tsx                 # https://example.com/ (Макет: layout.tsx)
  • https://example.com/
    ┌──────────────────────────────────────────────────┐
    │       src/routes/layout.tsx                      │
    │  ┌────────────────────────────────────────────┐  │
    │  │    src/routes/index.tsx                    │  │
    │  │                                            │  │
    │  └────────────────────────────────────────────┘  │
    │                                                  │
    └──────────────────────────────────────────────────┘
  • https://example.com/contact
    ┌──────────────────────────────────────────────────┐
    │       src/routes/layout-narrow.tsx               │
    │  ┌────────────────────────────────────────────┐  │
    │  │    src/routes/contact/index@narrow.tsx     │  │
    │  │                                            │  │
    │  └────────────────────────────────────────────┘  │
    │                                                  │
    └──────────────────────────────────────────────────┘

Вложенный макет

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

src/
└── routes/
    ├── layout.tsx           # Родительский макет
    └── about/
        ├── layout.tsx       # Дочерний макет
        └── index.tsx        # https://example.com/about

В приведённом выше примере есть два макета, которые оборачивают страницу /about.

  1. src/routes/layout.tsx
  2. src/routes/about/layout.tsx

В этом случае макеты будут вложены друг в друга, а страница будет находиться внутри каждого из них.

┌────────────────────────────────────────────────┐
│       src/routes/layout.tsx                    │
│  ┌──────────────────────────────────────────┐  │
│  │    src/routes/about/layout.tsx           │  │
│  │  ┌────────────────────────────────────┐  │  │
│  │  │ src/routes/about/index.tsx         │  │  │
│  │  │                                    │  │  │
│  │  └────────────────────────────────────┘  │  │
│  │                                          │  │
│  └──────────────────────────────────────────┘  │
│                                                │
└────────────────────────────────────────────────┘
src/routes/layout.tsx
import { component$, Slot } from '@builder.io/qwik';
 
export default component$(() => {
  return (
    <main>
      <Slot /> {/* <== Здесь будет вставлен дочерний макет/маршрут */}
    </main>
  );
});
src/routes/about/layout.tsx
import { component$, Slot } from '@builder.io/qwik';
 
export default component$(() => {
  return (
    <section>
      <Slot /> {/* <== Здесь будет вставлен дочерний макет/маршрут */}
    </section>
  );
});
src/routes/about/index.tsx
import { component$ } from '@builder.io/qwik';
 
export default component$(() => {
  return <h1>О сайте</h1>;
});

Для приведённого выше примера будет отображаться следующий HTML:

<main>
  <section>
    <h1>Описание</h1>
  </section>
</main>

plugin.ts

Файлы plugin.ts и plugin@<name>.ts могут быть созданы в корне каталога src/routes для обработки любого входящего запроса до выполнения даже корневого макета.

У вас может быть несколько файлов plugin.ts, каждый из которых имеет свое имя. Например, plugin@auth.ts и plugin@security.ts. Имя @<name> является необязательным и используется разработчиками только для идентификации плагина.

Обработчики запросов, такие как onRequest, onGet, onPost и т.д. вызываются перед выполнением функций server$.

Порядок выполнения файлов plugin.ts

Если файл plugin.ts существует и в нём есть экспортированные обработчики запросов, то они выполняются первыми.

Затем экспортированные обработчики запросов из файлов plugin@<имя>.ts выполняются в алфавитном порядке имён файлов. Например, onGet от plugin@auth.ts выполняется раньше onGet от plugin@security.ts, поскольку auth находится в алфавитном порядке перед security.

Наконец, если существует функция server$, то она выполняется последней.

Участники

Спасибо всем участникам, которые помогли сделать эту документацию лучше!

  • manucorporat
  • adamdbradley
  • cunzaizhuyi
  • the-r3aper7
  • mhevery
  • jakovljevic-mladen
  • vfshera
  • thejackshelton
  • wtlin1228
  • hamatoyogi