Головна / Статті

Стан URL, стан сервера та BFF: стек Vite для внутрішніх React-додатків

Чому автентифікована внутрішня React-платформа може краще функціонувати за допомогою Vite, TanStack Router, TanStack Query та окремого BFF, ніж за допомогою Next.js, і коли це вже не є правдою.

Видавець: REACTAPP.TOP10 хв читання2310 слів

Джерела: Next.js is excellent. That’s exactly why you **shouldn’t** use it everywhere.

Коли починається новий проект на React, на запитання «Чи варто нам використовувати Next.js?» зазвичай відповідають «так», ще до того, як хтось описає функціонал додатку. Для публічних сайтів із великою кількістю контенту така реакція часто є правильною. Однак для платформи з автентифікацією, де користувачі цілий день фільтрують таблиці та редагують записи після входу, багато найсильніших функцій цього фреймворку вирішують проблеми, яких у додатку насправді немає. У цій статті розглядається архітектура, яка насправді потрібна такій платформі, показується, як композитний набір React, Vite, TanStack Router, TanStack Query та backend-for-frontend (BFF) відповідає цій архітектурі, та наводиться чек-лист для визначення того, коли все ж таки Next.js є кращим вибором.

Ніщо тут не свідчить про те, що Next.js є слабким. Це один із найкращих фреймворків React, які існують. Аргумент полягає не у використанні його за замовчуванням, без перевірки того, чи відповідають його можливості потребам.

Конкретний сценарій

Уявімо невелику команду з п’яти інженерів, які створюють внутрішню платформу з понад 100 маршрутів, понад 300 кінцевих точок API та приблизно мільйон викликів API на місяць. Вони могли б побудувати її за допомогою Next.js. Натомість вони обирають React з Vite, TanStack Router для маршрутизації, TanStack Query для обробки даних та окремий BFF між фронтендом та бекенд-сервісами. Через шість місяців цей вибір залишається актуальним, не тому, що Next.js би зазнав невдачі, а тому, що платформа майже не використовує ті сфери, де Next.js проявляє найкращі результати.

Щоб отримати більш повне порівняння цих двох підходів для різних типів проєктів, дивіться Next.js чи звичайний React: підбір фреймворку під ваш проєкт. Тут увага зосереджена на більш вузькій темі: що потрібно додатку з автентифікацією та великою кількістю даних від кожного шару.

Опишіть додаток перед вибором фреймворку

Next.js став стандартним рішенням, оскільки об’єднує в собі багато функцій: маршрутизацію та лейаути на основі файлів, SSR та серверські компоненти, шар кешування, мідлвейр, код бекенду в одному проєкті, оптимізовані зображення та велику екосистему. Для багатьох продуктів таке об’єднання є ідеальним. Проте більш корисним першим запитанням є те, який додаток ви створюєте. Два React-додатки можуть майже не мати спільного з точки зору архітектури.

Публічний інтерфейс магазину

Публічний інтернет-магазин зазвичай має:

  • тисячі сторінок, які може відкрити будь-хто
  • трафік з пошукових систем
  • справжні вимоги SEO
  • сторінки продуктів, контент яких виграє від обробки на сервері
  • URL-адреси для поширення та попередні перегляди в соцмережах
  • багато статичного або повільно змінюваного контенту

Внутрішня платформа для операцій

Внутрішній інструмент зазвичай має:

  • майже кожен екран після автентифікації
  • пошук клієнтів, фільтри та таблиці з пагінацією
  • форми, панелі керування та перевірки дозволів
  • часті зміни та майже реальні дані
  • велику кількість інформації про стан сервера
  • високоінтерактивний інтерфейс
  • вже наявний шар API або BFF

Обидва є додатками на React, але вони вирішують різні проблеми, і архітектура має відображати це.

Користувачі керують додатком, а не просто читають його

У внутрішній платформі користувач уходить у систему та працює в додатку протягом усього дня. Він не потрапляє сюди з результатів пошуку, не читає статей та не переглядає списки товарів. Він шукає інформацію, фільтрує дані, сортує їх, відкриває діалогові вікна, надсилає форми, змінює записи, перемикається між вкладками, прогортає таблиці, оновлює дані та переходить між пов’язаними елементами. Він використовує додаток, а не відвідує веб-сайт, і ця зміна впливає на те, які функції фреймворку є важливими.

SEO більше не є обов’язковим

Візьміть маршрут на кшталт /customers/12345, який відображає запис автентифікованого клієнта з конфіденційними даними. Ви явно не хочете, щоб пошукові системи його індексували. Немає потреби, щоб краулери його знаходили чи щоб перша HTML-відповідь була придатною для краулерів. Сторінка є цінною тому, що з нею взаємодіє авторизований користувач, а не тому, що її можна знайти. Одна з основних причин вибору Next.js тому має незначне значення. Це неузгодженість вимог, а не недолік фреймворку.

Де підходять серверні компоненти, а де — ні

Серверні компоненти є справді корисною моделлю, тому це питання вимагає уважного розгляду. Уявіть собі сторінку керування клієнтами з кількома розділами:

Customer
├── Profile
├── Loans
├── Payments
├── Transactions
├── Documents
└── Activity

Як тільки сторінка завантажується, користувач починає її змінювати. Наприклад, він фільтрує список оплат:

Status: Failed
Type: ACH
Date: Last 30 days

Потім вони створюють сторінки, сортують дані, шукають інформацію, відкривають транзакції та редагують щось. Вигляд інтерфейсу постійно змінюється під впливом дій користувача та нових даних з сервера. Основний потік не є циклом у стилі документа:

Request → Render document → Read document

Це більше схоже на цикл взаємодії:

User interaction
       ↓
Application state
       ↓
Server state
       ↓
API
       ↓
Updated UI

Чим більше ваші екрани нагадують другу діаграму, тим більше архітектура клієнтської частини впливає на якість: спосіб зберігання стану, спосіб кешування даних та реакція інтерфейсу на зміни. Компоненти сервера все ще можуть відображати частини такої сторінки, але це не усуває потреби у міцному шарі даних на клієнтській частині.

Використання URL як стану додатку

Припустимо, користувач операцій знаходиться на вкладці оплат у розділі про клієнта:

/customer/12345/payments

Вони застосовують набір фільтрів:

status=failed
type=ach
amountMin=500
page=2

Якщо ці фільтри закодовані в адресі, URL стає таким:

/customer/12345/payments
  ?status=failed
  &type=ach
  &amountMin=500
  &page=2

У цей момент URL — це не просто засіб навігації; він є частиною стану додатку. Один інженер може скопіювати його колезі, який потрапляє на того самого клієнта, у ту саму вкладку, з тими самими фільтрами та на ту саму сторінку результатів. Він також зберігається після перезавантаження та змушує кнопку «Назад» працювати так, як очікують користувачі. Саме для правильної роботи на багатьох екранах і знаходить своє застосування маршрутизатор із підтримкою типів.

Що надає TanStack Router

TanStack Router розглядає маршрутизацію як важливу складову проектування. Маршрути, параметри шляху та параметри пошуку можна описати в системі типів, що має значення, коли кількість маршрутів у додатку перевищує сотню. При такому обсязі маршрутизація вже не є просто питанням «який компонент має відображатися тут»; це частина архітектури. Для маршруту на кшталт:

/customer/$customerId/payments

ви хочете:

  • чітко визначені, типовані параметри шляху
  • параметри пошуку, які перевіряються та мають передбачувані типи
  • функції навігації, які перевіряються компілятором, тож зміна назви параметра призводить до збою під час компіляції, а не роботи посилання
  • стани завантаження та помилок, визначені для кожного маршруту
  • механізми автентифікації на рівні маршруту
  • глибокі посилання, які завжди працюють
  • Важливо те, що все це не залежить від того, чи сторінка генерується на сервері. Для внутрішнього додатку, який повністю працює в браузері, це ідеально підходить.

    Стан сервера — справжній центр ваги

    У великих внутрішніх додатках більше уваги приділяється віддаленим даним, ніж сторінкам. Об’єкти, які вони постійно читають та записують, виглядають так:

    Customers
    Loans
    Payments
    Transactions
    Documents
    Users
    Accounts
    Reports
    

    Для кожного з них фронтенд мусить відповісти на однакові запитання:

    • Чи вже є ці дані в пам’яті?
    • Чи вони застаріли?
    • Чи потрібно їх знову завантажити, і коли?
    • Що відображає інтерфейс під час завантаження?
    • Що відбувається, коли запит зазнає невдачі?
    • Чи може інший компонент використовувати цей результат?
    • Що змінюється після мутації, і які запити потрібно анулювати?
    • Що має з’явитися миттєво, коли користувач повертається назад?
    • Чи може інтерфейс оновлюватися оптимістично?

    Це і є керування станом сервера, і саме для вирішення цієї проблеми створений TanStack Query: кеш, ключований за запитом, фонове оновлення даних, усунення дублікатів однакових запитів, хуки для мутацій та цілеспрямоване анулювання даних. Оскільки роутер керує станом URL, а Query — станом сервера, кожен рівень має чіткі обов’язки, а локальний стан компонентів обмежується справді локальними аспектами інтерфейсу, такими як наявність відкритого діалогового вікна.

    Збереження BFF як межі API

    У цьому сценарії фронтенд не викликає безпосередньо десятки мікросервісів. Він взаємодіє з BFF, який у свою чергу взаємодіє з сервісами. Таке розділення змушує фронтенд не знати деталей реалізації бекенду, включаючи те, чи складається відповідь з одного сервісу чи з п’яти. BFF керує контрактом API.

    Це також забезпечує чітке розділення під час розгортання. Фронтенд — це статичні ресурси, які подаються з CDN або статичного хоста:

    Frontend
       ↓
    CDN / static hosting
    

    тоді як BFF працює як окремий сервіс перед мікросервісами:

    BFF
       ↓
    Microservices
    

    Кожну з частин можна створювати та випускати незалежно. Next.js цілком можна розгорнути разом із BFF. Справжнє питання полягає у тому, чи мусить ваш фронтенд-фреймворк водночас бути й межею сервера. Якщо організація вже має шар бекенду, який забезпечує цю межу, додавання ще одного шару всередині фронтенд-фреймворка призводить до дублювання обов’язків.

    Аутентифікація залишається на серверній стороні

    Процес аутентифікації також ґрунтується на цьому принципі розділення. Користувач натискає «Увійти», і його перенаправляють на Okta. Після успішного входу калебек приходить на бекенд, який зберігає токен доступу на серверній стороні, а Redis відповідає за зберігання сеансів. Браузер містить лише ідентифікатор сеансу у захищеному куку. Концептуально:

    Browser
       │
       │ Sign in
       ▼
     Okta
       │
       │ Callback
       ▼
     BFF / Backend
       │
       ▼
     Redis
    

    React-додаток — це не місце розташування архітектури автентифікації; його завдання — керувати продуктом, тоді як BFF відповідає за безпеку та контракт API. Корисним побічним ефектом є те, що токени доступу ніколи не потрапляють до JavaScript браузера, що зменшує вплив помилок типу XSS. Використання цього підходу в Next.js не є за своєю суттю гіршим, але коли така архітектура вже існує в організації, її збереження поза фронтендом є простим та зрозумілим.

    Так, Next.js може виконати все це

    Він може. Ви можете створити додаток за допомогою Next.js з використанням клієнтських компонентів, використати TanStack Query всередині нього, налаштувати автентифікацію, під’єднати його до BFF та розгорнути його так, як вам потрібно. Проблема не у можливостях. Краще запитання — наскільки багато з цього фреймворку ви насправді будете використовувати. Якщо більша частина додатку в кінцевому підсумку буде виглядати ось так:

    'use client'
            ↓
    Client-side interactions
            ↓
    API calls
            ↓
    TanStack Query
    

    Тоді варто зупинитися та подумати, чи справді функції серверної частини виправдовують використання Next.js як основи вашого додатку з його конвенціями, середовищем виконання та шляхом оновлень.

    Кожен елемент складової стеку має свою функцію

    Для цього типу додатків розумним стандартним вибором є React, Vite, TanStack Router та TanStack Query, а також BFF. Йдеться не про з’єднування бібліотек до того моменту, поки ви випадково не створите власну фреймворк-систему. Кожен елемент має чітко визначену роль:

    • Vite забезпечує інструменти для компіляції та сервер розробки.
    • React відображає користувацький інтерфейс.
    • TanStack Router керує маршрутизацією та станом URL.
    • TanStack Query керує серверним станом.
    • BFF є межею API та безпеки.

    Оскільки обов’язки чітко визначені, ви можете замінити одну частину без необхідності переосмислювати весь додаток.

    Втрати від відмови від Next.js

    Композитні маршрути мають свої недоліки. Ви втрачаєте зручність, яка пропонується разом із фреймворком, та змушені самостійно приймати рішення, які б зробив Next.js за вас: щодо структури проекту, правил розділення коду, способу обробки клієнтських маршрутів статичним хостом та синхронізації оновлень кількох бібліотек. Крім того, вимоги можуть змінитися. Якщо пізніше половина додатку має стати загальнодоступною та SEO раптово стане важливим, Next.js виглядає набагато привабливіше. Те саме стосується випадків, коли потрібна інтенсивна обробка на сервері чи генерація статичного контенту, якщо серверні компоненти додають реальної цінності до вашого дизайну, або якщо ви дійсно хочете, щоб код фронтенду та сервера були навмисно поєднані в один деплоюваний проект. Отже, рішення не є „Next.js чи React“ — це вибір архітектури, яка підходить саме для вашого додатку.

    Чек-лист для вибору

    Перш ніж приймати рішення, розгляньте ці запитання:

    • Чи має додаток значну публічну частину? Це сильна сторона Next.js.
    • Чи важливий SEO? Next.js стає ще привабливішим.
    • Чи потрібне серверне формування вмісту чи статична генерація? У цьому аспекті також перемагає Next.js.
    • Чи можуть Server Components чи Server Actions суттєво покращити дизайн? Тоді варто серйозно розглянути Next.js.
    • Чи є додаток переважно автентифікованим та надзвичайно інтерактивним? Краще обрати React з Vite.
    • Чи вже існує у організації шар BFF чи API? Варто розглянути можливість використання окремого клієнтського додатку, розгорнутого окремо.
    • Чи є серверний стан основною проблемою? TanStack Query стає дуже привабливим вибором.
    • Чи є ключовими URL-статус та складне маршрутизування? TanStack Router також є дуже привабливим вибором.

    З цих відповідей випливає просте правило. Якщо додаток має значну публічну частину, варто серйозно розглянути Next.js. Якщо він переважно потребує автентифікації, особливо це внутрішня чи адміністративна платформа, почніть з React та Vite та додавайте лише ті компоненти, які потрібні. Жоден з цих наборів не є загалом кращим; один більше підходить для конкретної проблеми, але ця різниця легко зникає, коли певна фреймворк-система стає стандартним рішенням.

    Підсумок

    Найкраща архітектура — це не та, що має найбільший список функцій, а та, чиї функції вирішують реальні проблеми. Для інструменту для виконання операцій, доступ до якого обмежений логіном та який підтримується власним бекенд-шаром, має велику кількість даних на сервері, інтенсивну взаємодію та дуже мало публічного контенту, використання React з Vite, TanStack Router та TanStack Query — це не компроміс, а свідомий вибір.

    • Спочатку класифікуйте додаток: сайт з публічним контентом чи інструмент для автентифікації.
    • Вважайте URL та дані сервера двома основними джерелами інформації та виділіть для кожного окремий інструмент.
    • Зберігайте межі безпеки та API там, де вони вже існують у вашій організації.
    • Перегляньте своє рішення, якщо публічні сторінки, SEO чи серверна обробка стануть реальними вимогами.