Сайт, App Router и Mantine
Маршруты и файлы
| URL | Файл в apps/web/src/app | Содержимое |
|---|---|---|
/ | page.tsx | Фотослайдер, последние новости, первые события, информационные блоки |
/news | news/page.tsx | Все новости |
/news/[slug] | news/[slug]/page.tsx | Полный текст новости |
/events | events/page.tsx | Все события |
/events/[slug] | events/[slug]/page.tsx | Полный текст и параметры события |
/api/trpc/* | api/trpc/[trpc]/route.ts | Типизированный API |
/api/webhooks/strapi | api/webhooks/strapi/route.ts | Приём изменения CMS |
/[slug] | [slug]/page.tsx | Информационная страница из CMS |
/api/media/[filename] | api/media/[filename]/route.ts | Резервный proxy старых local uploads |
/api/content-health | api/content-health/route.ts | Чтение опубликованного контента без кеша |
/api/health | api/health/route.ts | Проверка ответа web |
page.tsx создаёт страницу, layout.tsx — общую оболочку, route.ts — HTTP handler.
Каталог [slug] обозначает динамический сегмент пути. В Next 16 params в этих
страницах ожидается через await; типы PageProps/LayoutProps генерирует Next.
Что выполняется на сервере и в браузере
Страницы и layout — Server Components. Они могут обращаться к серверному caller и готовить HTML без загрузки всей серверной логики в браузер.
providers.tsx, site-shell.tsx, content-lists.tsx и blocks-content.tsx
помечены "use client": они подключают provider, интерактивную навигацию,
адаптер Next Link для карточек и renderer Strapi Blocks. Шапка получает только
title, logo, dateLabel, statusLabel и navigation; подвал — copyrightLabel.
Root layout выбирает эти поля из Site, а не передаёт весь редакционный объект
через клиентскую границу.
hero-carousel.tsx, award-strip.tsx, partner-sections.tsx и
festival-home.tsx составляют блоки на сервере. Внутри они могут использовать
клиентские компоненты UIKit с сериализуемыми данными и готовыми React-слотами.
"use client" задаёт границу клиентского модуля. Это не обещание, что компонент
никогда не участвует в первоначальном серверном рендеринге. Секреты и серверные
библиотеки не следует импортировать внутрь такой границы.
Документация Next.js о Server/Client Components.
Статическая генерация и обновление
Пошаговый учебный разбор — SSG, ISR и 404.
Главная, списки и опубликованные страницы генерируются при next build из Strapi.
Детальные маршруты используют generateStaticParams и force-static: новый slug
генерируется при первом посещении без пересборки. Для сборки нужны доступная CMS,
опубликованный Site, STRAPI_INTERNAL_URL и read-only STRAPI_READ_TOKEN.
Запросы CMS используют force-cache, тег cms и страховочный revalidate: 3600.
Webhook /api/webhooks/strapi проверяет Bearer-секрет и контракт, затем вызывает
revalidateTag("cms", { expire: 0 }) и revalidatePath("/", "layout"). Следующий
запрос обновляет страницу; повторные посещения получают статический HTML из кеша.
Сбрасывается также кеш старых адресов и 404, поэтому работают смена slug,
снятие с публикации и публикация ранее отсутствовавшей страницы. Для небольшого
сайта намеренно сбрасывается весь редакционный кеш.
server/cms/queries.ts дополнительно использует React cache для дедупликации
одного render. API остаются динамическими; /api/content-health явно обходит кеш
и проверяет живую CMS. По умолчанию файловый кеш принадлежит одному экземпляру
Next.js; перед масштабированием понадобится общий cache handler.
Настоящее отсутствие опубликованного slug вызывает notFound(). Ошибка сети,
авторизации, pagination или контракта — CmsUnavailableError, а не ложный 404.
Неудачная сборка не выпускает пустой сайт. Error/global-error показывают отказ;
React streaming не гарантирует HTTP 503. Media proxy отдельно возвращает 404/503.
node scripts/check-static-site.mjs собирает сайт на изолированной CMS и проверяет
prerender manifest, cache HIT, обновление через webhook, новый slug, снятие публикации
и динамическую проверку состояния. Внешняя CMS и её секреты для этой проверки не нужны.
Устройство Mantine
- Layout импортирует
@mantine/core/styles.layer.css, затем@atmanki/ui/styles.cssи проектныйindex.css. Слой Mantine ниже локальных CSS Modules. ColorSchemeScriptиmantineHtmlPropsсинхронизируют цветовую схему.- Клиентский
ProvidersпереэкспортируетUiProviderиз@atmanki/ui: тема и MantineProvider задаются в пакете UIKit. - Принудительно выбрана светлая схема.
В теме заданы палитра festival, основной цвет, шрифт, радиус 5 px и defaults
для Button/Container. CSS задаёт конкретную геометрию копируемого оформления.
Это два уровня настройки: компоненты Mantine и композиция страницы.
Официальная инструкция Mantine для Next.js.
Карта компонентов
Файл в src/components | Ответственность |
|---|---|
providers.tsx | Общая тема и provider |
site-shell.tsx | Шапка, desktop/mobile-навигация, Drawer и footer |
content-layout.tsx | PageContainer, PageHeading, ContentSection и ArticleLayout |
content-lists.tsx | NewsList на ArticleCollection, EventList/EventCard и EventDayList |
hero-carousel.tsx | Карусель FestivalHero, массив slides приходит через props |
award-strip.tsx | Исторические отметки, массив items приходит через props |
partner-sections.tsx | Группы партнёров, массив groups приходит через props |
festival-home.tsx | Серверная композиция информационных блоков главной |
blocks-content.tsx | Безопасный официальный BlocksRenderer и MediaGallery |
CSS Modules и повторное использование
Стили компонентов находятся в соседних *.module.css: Next.js создаёт уникальные
имена классов. Импортируйте объект styles и передавайте className={styles.root}.
Все размеры и responsive-правила блока держите в его модуле, включая media queries.
В index.css остаются шрифт, общие CSS variables, reset, базовая типографика,
focus-visible и prefers-reduced-motion. Глобальных классов карточек/страниц нет.
Для внутренних элементов Mantine используйте Styles API. Например, компонент
Faq в UIKit задаёт классы Accordion через classNames.
Не привязывайте проектный CSS к глобальным
.mantine-Accordion-* классам. Тема задаёт цвета/default props, module — геометрию.
Рекомендация Mantine.
Страница собирается из готовых блоков. Например:
<ContentSection title="Последние новости" action={{ href: "/news", label: "Все новости" }}>
<NewsList items={news.slice(0, 10)} featured />
</ContentSection>
Для карточек новостей приложение преобразует NewsItem в ArticleItem и передаёт
их ArticleCollection; EventCard используется в списке событий. FestivalInformation
преобразует FAQ из CMS в записи UIKit { id, question, answer }, где answer —
React-слот с BlocksContent. Карусель получает { title, description, image }.
ArticleLayout объединяет ссылку назад, metadata, необязательную обложку и summary;
текст Blocks передаётся через children, параметры события — через details. Получение данных
и notFound остаются ответственностью серверной страницы.
Root layout задаёт общую оболочку, а PageContainer — единственный main#main
страницы. ArticleLayout используется для новости, события и информационной
страницы. Отдельные route layouts вокруг списков затронули бы и detail routes:
перед выделением общей разметки нужно проверить, что main не становится вложенным.
CSS соответствует владельцам: content-layout.module.css хранит геометрию страницы
и статьи, а также общие metadata; blocks-content.module.css — текст и галерею;
events/[slug]/page.module.css — параметры события; app/layout.module.css —
skip-link. content-lists.module.css содержит строки событий и скрытую подпись
ссылки, site-shell.module.css — оформление даты в шапке. Карусели, навигация и
FAQ используют собственные стили UIKit.
lib/partner-groups.ts в одном месте распределяет группы партнёров по секциям
главной. Порядок секций остаётся явным JSX, внутри каждой сохраняется порядок
CMS, включая повторяющиеся группы. Неизвестные заголовки попадают в оставшиеся
группы; «Спонсоры» отображаются как «Финансовые партнеры», исходные данные
не меняются. Классификация пока зависит от редакционного заголовка: стабильные
ключи CMS потребовали бы отдельного изменения модели.
lib/dates.ts содержит только форматирование. Клиентские карточки импортируют
его и import type для NewsItem/EventItem; типы приходят из @atmanki/contracts, данные — из серверного repository.
CSS Modules не добавляют runtime-зависимость Emotion и работают с серверными
компонентами через стандартную сборку Next.js.
Слайдер не переключается автоматически. Это упрощает чтение и поведение при уменьшении анимации. Новости сортируются по убыванию даты, события — по возрастанию; главная берёт первые десять новостей, а расписание и подсобытия используют все события. Прошедшие события не фильтруются.
Даты, статус, навигация, логотип, слайды, отметки, страницы и FAQ приходят из CMS. Расписание образца относится к 2025 году, последние исходные новости — к 2026. Архивные даты сохраняются; отсутствующий endsAt не превращается в выдуманное окончание.
Статические ресурсы и дизайн
public/reference содержит шрифт оформления. Логотип/фотографии/партнёры приходят
из CMS и Garage; прежние статические копии удалены. В standalone-образе папку public нужно
копировать явно — соответствующий шаг есть в Dockerfile.
Источники материалов описаны в DESIGN.md.
Редакционные ресурсы загружаются через Strapi в Garage и читаются браузером
напрямую с media-домена через Caddy. /api/media/filename остаётся для старых uploads.
Старые файлы reference больше не служат источником контента. Шрифт остаётся локальным.
Приложение не запрашивает исходный сайт/API во время обычного запроса.
Текущая вёрстка переносит оформление образца, но не реализует его регистрацию, личный кабинет или весь внешний функционал. Скриншотная сверка не выполнена; отсутствие ошибок TypeScript не доказывает визуальную идентичность.
Адаптивность и доступность
Основные CSS breakpoints — 1200, 900 и 600 px. Меню меняется на Burger/Drawer,
карточки переходят в одну колонку, изображения получают ограниченную высоту.
Есть skip-link, видимый keyboard focus, подписи кнопок карусели и aria-live.
Для prefers-reduced-motion отключены анимации и плавный скролл.
Проверьте вручную: узкий экран, увеличение текста, Tab/Shift+Tab, открытие и закрытие меню, переключение карусели, переходы к записи и возврат назад. Эти проверки не автоматизированы — UI-тестов в проекте нет.
Запрет индексации
public/robots.txt:User-agent: *иDisallow: /.- Layout metadata:
index: false,follow: false. next.config.ts:X-Robots-Tag: noindex, nofollowдля всех путей.
Robots управляет поведением добросовестных роботов и не закрывает сайт от людей. Он не заменяет аутентификацию и не гарантирует удаление уже известного поисковику URL.
Где смотреть код
Layout, главная, CSS, тема, настройки Next.