Files
netrunner-landing/app
nineap 4b1ef9f951
Build & Push Image / Build and push to Gitea Registry (push) Successful in 2m28s
Цены тарифов — с бэкенда (регион/валюта), не из PocketBase
PocketBase pricing_{lang} остаётся источником переводного маркетингового
контента (название, тэглайн, описание, фичи, порядок) — но сами цифры
price1m/6m/1y теперь подменяются данными с GET /billing/plans нового
бэкенда (см. netrunner-backend::modules::geoip): валюта определяется по
IP посетителя, VPN/датацентр всегда получает базовый USD.

Соответствие тарифа PocketBase к тарифу бэкенда — по полю `name`
(ожидается "GHOST"/"NETRUNNER"/"MAINFRAME"). Бэкенд недоступен или для
конкретного тарифа не нашлось соответствия — тихий фолбэк на цену как
она есть в PocketBase, страница не ломается (тот же принцип, что уже
был у offline-кеша).
2026-07-11 00:55:15 +07:00
..
2026-07-07 18:49:09 +07:00

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 Полноэкранная версия гостевой книги с постраничной подгрузкой истории

Откуда берутся данные

  • Переводы UIdictionaries/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), т.к. там происходит модерация сообщений и реальные замеры скорости.