BetaДокументация для beta‑теста, возможны ошибки и неточности.
Перейти к содержимому

Pages ​

Pages — это сущность, определяющая существующие страницы на сайте. Это самостоятельные сущности, которые никто больше не может переиспользовать (даже App импортирует их только для построения роутера).

Также важно отметить, что pages может быть частью модуля, но не обязательно. И в этом случае pages в модуле соблюдает все правила pages из FEOD. Более того вариант с pages в модуле является более предпочтительным и правильным. В это время вы можете оставить pages для страниц которые мучительно тяжело отнести к какому-либо модулю, например 404, about us и прочие на верхнем уровне.

Важно

Несмотря на сходство pages из FEOD и pages из FSD, они имеют определенные отличия.

Важность уровня ​

Данный уровень важен: в нём можно изучить, какие URL доступны на сайте и куда они ведут. Стоит отметить, что модули могут определять свои URL, поэтому это не единственный источник истины. Зато даже если вынести все страницы в модули, то в pages могут остаться страницы, которые мучительно тяжело отнести к какому-либо модулю, например 404, about us и прочие. Также выделение уровня страниц как самостоятельного важно потому, что это даёт нам возможность сформировать особые требования по работе с роутами. В базовой реализации речь идёт о файловом роутинге. Кроме того, pages, которые находятся в модулях, следуют тем же правилам, что и основной уровень, что даёт определённый уровень стандартизации.

Возможное альтернативное название: ​

  • views — привычнее для пользователей Vue Router
  • routes — тоже неплохо объясняет суть: маршруты приложения. Неплохо подходит для использования на бэкенде

Роль Pages в FEOD ​

Pages играют ключевую роль в структуре FEOD:

  • Определяют маршруты приложения — каждая страница соответствует конкретному URL
  • Являются точками входа — страницы связывают URL с компонентами
  • Изолированы от бизнес-логики — страницы не содержат сложной логики, они используют модули
  • Не переиспользуются — страницы уникальны и не импортируются в других местах

Принцип работы ​

Если что-то имеет URL, то этот URL заводится здесь. Структура файлов в папке pages напрямую соответствует структуре URL-адресов приложения. Это делает навигацию по проекту интуитивно понятной — разработчик может легко найти страницу, зная её URL.

Правила заведения страниц ​

Важно

Использование файлового роутинга не является обязательным.

Также любые конвенции по именованию файлов не являются обязательными. Вы можете использовать любые имена файлов и папок, которые вам удобны. Здесь лишь дан пример как это можно сделать на основе существующих конвенций для Vue Router.

Если роутинг задаёт фреймворк ​

Модель ниже — лишь вариант по умолчанию: быстрый поиск компонента страницы по URL. FEOD не требует ни файлового роутинга, ни конкретных имён файлов.

Если каталог страниц фреймворка можно разместить отдельно, его страницы и есть слой pages. В Nuxt файловый роутинг совпадает с этой моделью, а dir.pages указывает на src/pages. Прослойка между маршрутом и страницей там не нужна.

Если каталог маршрутов совмещает страницы с layout, обработчиками и настройками приложения (app в Next, routes в SvelteKit), он относится к слою app. Тогда страницы FEOD лежат отдельно (при необходимости под другим именем через layerDirs.pages, например screens, если pages занят фреймворком). Файлы маршрутов остаются тонкими: параметры, метаданные, загрузка данных маршрута и рендер страницы.

Стили страницы ​

Страница может подключать собственные стили: home.tsx → home.scss или home.module.scss, home/index.tsx → любые таблицы стилей внутри home/. Импорт стилей другой страницы и любых других файлов pages по-прежнему запрещён.

1. Соответствие файловой системы и роутинга ​

Страница располагается по принципам совпадения файловой системы и роутинга. Путь к файлу соответствует URL-адресу.

Примеры:

  • URL: /main/home → Файл: pages/main/home.vue
  • URL: /about → Файл: pages/about.vue
  • URL: /products/categories → Файл: pages/products/categories.vue

2. Индексные страницы ​

Если URL оканчивается на /, то используется файл index.vue:

  • URL: /main/home/ → Файл: pages/main/home/index.vue
  • URL: / → Файл: pages/index.vue
  • URL: /products/ → Файл: pages/products/index.vue

3. Динамические параметры ​

Если URL требует наличия параметра, то имя параметра берётся в квадратные скобки []:

  • URL: /posts/123445 → Файл: pages/posts/[id].vue
  • URL: /users/42 → Файл: pages/users/[id].vue
  • URL: /products/category/item-123 → Файл: pages/products/category/[itemId].vue

4. Множественные параметры ​

Если параметров несколько, то все они берутся в [] и разделяются подчёркиванием:

  • URL: /posts/12342_12123 → Файл: pages/posts/[userId]_[postId].vue
  • URL: /users/42/posts/123 → Файл: pages/users/[userId]/posts/[postId].vue

5. Catch-all маршруты ​

Если нужно захватить все возможные продолжения, используется [...param]:

  • URL: /docs/guide/getting-started → Файл: pages/docs/[...path].vue
  • URL: /any/deep/nested/path → Файл: pages/[...all].vue
  • URL: /404 или любой несуществующий → Файл: pages/[...404].vue

6. Named views (множественные компоненты) ​

Если нужно использовать несколько компонентов для одного роута (named views), то файлы именуются с точкой и именем view:

  • URL: /login → Файлы:
    • pages/login.form.vue (идёт в <router-view name="form">)
    • pages/login.content.vue (идёт в <router-view name="content">)

7. Приватные модули страниц ​

Опционально

Данный пункт не является обязательным для реализации и остаётся на усмотрение разработчика.

Можно создавать приватные модули конкретной страницы с префиксом _:

  • Для /pages/home.vue → /pages/_home/index.ts — _home становится приватным модулем для страницы /pages/home
  • Для /pages/home/ → /pages/home/_home/index.ts — _home становится приватным модулем для страниц внутри /pages/home

Стоит отметить, что приватные модули более уместны для фреймворков с однофайловыми компонентами, где дробление на компоненты в рамках является затруднительным. Для остальных случаев лучше использовать обычные модули.

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

Приватный модуль можно использовать и как место для логики одной страницы, в духе подхода «pages first» из FSD 2.1: пока логика нужна только странице, она остаётся в _home, а когда понадобится где-то ещё, модуль переносится в modules. Изоляцию проверяет правило pages-private-modules: снаружи страницы такой модуль импортировать нельзя. Основным путём остаются страницы внутри модулей, а выбор за командой.

Структура Pages ​

  • pages
    • index.vue
    • main
      • home.vue
      • index.vue
    • posts
      • _posts
        • components
          • PostCard.vue
          • PostList.vue
        • api
          • postsApi.ts
        • index.ts
      • [id].vue
      • index.vue
    • users
      • [userId]
        • posts
          • [postId].vue
        • index.vue
    • docs
      • [...path].vue
    • [...404].vue

Примеры реализации ​

Единый пример показывает все типы страниц в одном дереве и с одним набором файлов.

  • pages
    • posts
    • products
    • profile
      • _profile
        • components
        • api

Выберите файл в дереве слева

В этом примере реализации Posts и Products опущены. Их публичный API возвращает refs данных, загрузки и ошибки: useProducts() — список; usePost(id) и useProduct(id) — одну запись. Последние принимают реактивный id, перезагружают запись при его изменении и игнорируют устаревшие ответы. Products также экспортирует ProductList и ProductDetails. Приватный _profile показан полностью.

Лучшие практики ​

1. Минимизируйте логику в страницах ​

Страницы должны быть тонкими и использовать модули для бизнес-логики:

vue
<script setup lang="ts">
import { ProductList, useProducts } from '@/modules/Products'
const { products, loading, error } = useProducts()
</script>

<template>
  <p v-if="loading">Загрузка...</p>
  <p v-else-if="error" role="alert">{{ error }}</p>
  <ProductList v-else :products="products" />
</template>

2. Используйте композаблы для переиспользуемой логики ​

Если логика может быть переиспользована, вынесите её в композабл модуля:

vue
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import { usePost } from '@/modules/Posts'

const route = useRoute()
const id = computed(() => String(route.params.id))
const { post, loading, error } = usePost(id)
</script>

<template>
  <p v-if="loading">Загрузка...</p>
  <p v-else-if="error" role="alert">{{ error }}</p>
  <article v-else-if="post">
    <h1>{{ post.title }}</h1>
    <p>{{ post.content }}</p>
  </article>
</template>

usePost принимает реактивный id, следит за его изменениями и возвращает refs post, loading, error. HTTP-запрос и защита от устаревшего ответа находятся внутри модуля Posts.

3. Обрабатывайте состояния загрузки и ошибок ​

Всегда обрабатывайте состояния загрузки и ошибок:

vue
<script setup lang="ts">
import { ProductList, useProducts } from '@/modules/Products'
const { products, loading, error } = useProducts()
</script>

<template>
  <p v-if="loading">Загрузка...</p>
  <p v-else-if="error" role="alert">{{ error }}</p>
  <ProductList v-else :products="products" />
</template>

4. Используйте константы для роутов ​

Выносите URL-пути в /common/constants/routes.ts:

ts
// common/constants/routes.ts
export const ROUTES = {
  HOME: '/',
  ABOUT: '/about',
  PRODUCTS: {
    LIST: '/products',
    DETAIL: (id: string) => `/products/${encodeURIComponent(id)}`,
  }
}

Пути используются при навигации; они не заменяют описания маршрутов в app/router.ts.

vue
<!-- pages/index.vue -->
<script setup lang="ts">
import { ROUTES } from '@/common/constants/routes'
import { RouterLink } from 'vue-router'
</script>

<template><RouterLink :to="ROUTES.PRODUCTS.LIST">Каталог</RouterLink></template>

FAQ ​

Можно ли не создавать Pages вообще? ​

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

Как организовать вложенные маршруты? ​

Используйте структуру папок, соответствующую вложенности URL:

  • URL: /users/42/posts/123 → pages/users/[userId]/posts/[postId].vue

Можно ли использовать layout в страницах? ​

Layouts должны быть определены в app/layouts и выбираться в роутере. Страницы не должны содержать логику выбора layout, если только вы не используете файловый роутинг и не имеете возможности выбрать layout прямо здесь.

Как обрабатывать 404? ​

Создайте catch-all маршрут pages/[...404].vue, который будет обрабатывать все несуществующие маршруты.

Связанные разделы ​

  • App — настройка роутера и layouts
  • Modules — бизнес-логика для страниц
  • Common — общие компоненты и утилиты