# `app/` — маршруты Next.js App Router Весь пользовательский контент живёт под динамическим сегментом `app/[lang]/`, кроме нескольких файлов верхнего уровня, которые Next.js требует держать вне локализованных сегментов (`not-found.tsx`, `loading.tsx`, `globals.css`, `favicon.ico`). ## Как работает i18n через `[lang]` 1. Корневой `proxy.ts` (Next.js middleware) перехватывает каждый запрос. Если путь уже начинается с `/en` или `/ru` — пропускает его как есть. Если локали в пути нет (например, `/`) — определяет язык браузера через заголовок `Accept-Language` (`Negotiator` + `@formatjs/intl-localematcher`) и редиректит на `/en...` или `/ru...`. 2. Дальше уже сам Next.js резолвит запрос в `app/[lang]/...`, а `params.lang` передаётся во все `page.tsx`/`layout.tsx` этого сегмента. 3. `generateStaticParams` в проекте не используется — страницы под `[lang]` не пре-генерируются статически на билде для `en`/`ru`, а рендерятся динамически (server-side) по каждому запросу, с точечным ISR-кэшированием там, где это нужно (см. `revalidate` в запросах к PocketBase в `Faq.tsx`, `BlogPreview.tsx`, `blog/page.tsx`). 4. Строковый контент (заголовки, лейблы, кнопки) берётся не из PocketBase, а из JSON-словарей `dictionaries/en.json` / `dictionaries/ru.json`, которые грузятся через `getDictionary(lang)` из `lib/dictionaries.ts` (только на сервере, файл помечен `import "server-only"`). ## Паттерн `page.tsx` + `*-client.tsx` Почти каждый раздел состоит из пары файлов: - **`page.tsx`** — серверный async-компонент. Получает `params.lang`, грузит словарь (`getDictionary`) и/или данные из PocketBase (`lib/pb.ts`), передаёт их вниз как пропсы. - **`*-client.tsx`** — клиентский компонент (`"use client"`) с интерактивностью: анимации на `framer-motion`, локальное состояние, realtime-подписки PocketBase, запросы к Rust-шлюзу `proxy-ws` и т.д. Разделение оправдано тем, что серверная часть не тянет в клиентский бандл лишний JS (словари, PocketBase SDK для первого запроса), а клиентская часть свободно использует хуки и браузерные API. Есть и раздел без клиентской пары — `blog` и `blog/[slug]`, а также `Faq.tsx`/ `BlogPreview.tsx` в `components/sections/`: там контент статичен после загрузки (no client-only interactivity), поэтому они остаются серверными компонентами целиком. ## Разделы | Маршрут | Файлы | Назначение | |---|---|---| | `/` | `page.tsx` | Главная: статично рендерит `Hero`+`Features`, а `Pricing`/`FAQ`/`TerminalGuestbook`/`BlogPreview` подгружает через `next/dynamic` (code-splitting тяжёлых интерактивных секций, см. комментарий в файле) | | `/auth` | `page.tsx` + `auth-client.tsx` | Авторизация через Telegram Login Widget против бэкенда на `account.netrunner-vpn.com` (`/users/me`, `/auth/telegram`, `credentials: "include"`); сессия — только HttpOnly cookie с `Domain=.netrunner-vpn.com`, лендинг ничего не хранит сам (ни `localStorage`, ни токен в URL). После успеха — полный переход на `{ACCOUNT_ORIGIN}/profile` (ЛК физически в другом сервисе, не в этом репо) | | `/blog` | `page.tsx` | Список опубликованных постов из PocketBase (`posts_{lang}`), ISR-кэш на час | | `/blog/[slug]` | `page.tsx` | Отдельный пост блога, контент рендерится как HTML (`dangerouslySetInnerHTML`), без кэша (`cache: "no-store"`) | | `/docs` | `page.tsx` + `docs-client.tsx` | "Хранилище" документов (`documents_{lang}`) с поиском и фильтром по категориям, читалка на весь экран | | `/download` | `page.tsx` + `download-client.tsx` | Клиенты VPN по платформам из PocketBase (`downloads_{lang}`), ссылки на прямое скачивание/магазины | | `/nodes` | `page.tsx` + `nodes-client.tsx` | Список VPN-серверов со статусом/пингом/загрузкой — **сейчас данные моковые**, реального API ещё нет | | `/speedtest` | `page.tsx` | Обёртка вокруг `components/sections/SpeedtestClient.tsx` — измерение скорости через Rust-шлюз `proxy-ws` | | `/terminal` | `page.tsx` + `terminal-client.tsx` | Полноэкранная версия гостевой книги с постраничной подгрузкой истории | ## Откуда берутся данные - **Переводы UI** — `dictionaries/en.json` / `dictionaries/ru.json` через `lib/dictionaries.ts`. - **Динамический контент** (посты, FAQ, тарифы, документы, платформы загрузки, комментарии гостевой книги) — PocketBase через синглтон `lib/pb.ts`. Коллекции обычно разделены по языку суффиксом (`posts_en`/`posts_ru`, `faqs_en`/`faqs_ru` и т.д.). - **Гостевая книга и спидтест** отправляют/читают трафик не напрямую в PocketBase, а через Rust-шлюз `proxy-ws` (см. `/api/proxy/*` в `next.config.ts`), т.к. там происходит модерация сообщений и реальные замеры скорости.