4b1ef9f951
Build & Push Image / Build and push to Gitea Registry (push) Successful in 2m28s
PocketBase pricing_{lang} остаётся источником переводного маркетингового
контента (название, тэглайн, описание, фичи, порядок) — но сами цифры
price1m/6m/1y теперь подменяются данными с GET /billing/plans нового
бэкенда (см. netrunner-backend::modules::geoip): валюта определяется по
IP посетителя, VPN/датацентр всегда получает базовый USD.
Соответствие тарифа PocketBase к тарифу бэкенда — по полю `name`
(ожидается "GHOST"/"NETRUNNER"/"MAINFRAME"). Бэкенд недоступен или для
конкретного тарифа не нашлось соответствия — тихий фолбэк на цену как
она есть в PocketBase, страница не ломается (тот же принцип, что уже
был у offline-кеша).
app/ — маршруты Next.js App Router
Весь пользовательский контент живёт под динамическим сегментом app/[lang]/,
кроме нескольких файлов верхнего уровня, которые Next.js требует держать вне
локализованных сегментов (not-found.tsx, loading.tsx, globals.css, favicon.ico).
Как работает i18n через [lang]
- Корневой
proxy.ts(Next.js middleware) перехватывает каждый запрос. Если путь уже начинается с/enили/ru— пропускает его как есть. Если локали в пути нет (например,/) — определяет язык браузера через заголовокAccept-Language(Negotiator+@formatjs/intl-localematcher) и редиректит на/en...или/ru.... - Дальше уже сам Next.js резолвит запрос в
app/[lang]/..., аparams.langпередаётся во всеpage.tsx/layout.tsxэтого сегмента. generateStaticParamsв проекте не используется — страницы под[lang]не пре-генерируются статически на билде дляen/ru, а рендерятся динамически (server-side) по каждому запросу, с точечным ISR-кэшированием там, где это нужно (см.revalidateв запросах к PocketBase вFaq.tsx,BlogPreview.tsx,blog/page.tsx).- Строковый контент (заголовки, лейблы, кнопки) берётся не из 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), т.к. там происходит модерация сообщений и реальные замеры скорости.