Фальшиве відчуття безпеки або найбільший недолік Next.js

💡 Усі статті, обговорення, новини про Front-end — в одному місці. Приєднуйтесь до Front-end спільноти!

Вітаю, мене звати Кирил і я Senior Frontend Engineer. Десь рік тому я почав роботу над пет-проєктом Freeversity. Як фреймворк для нього я обрав нещодавній реліз Next.js 13.4 з App Router. І, відверто кажучи, за цей рік фреймворк підкидав мені дуже багато неприємних та неочевидних сюрпризів там, де все, на перший погляд, здавалось максимально прямолінійним.

Цей текст буде про все, що змушувало мене палати під час роботи з Next.js App Router. Колись пізніше мені б хотілося написати й про сильні сторони фреймворку. Але не виключено, що це буде стаття про міграцію на Remix.js :)

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

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

SSR та Next.js: Трохи контексту

Якщо у вас був досвід роботи з SSR React-застосунками, ви знаєте, що сама ідея полягає у виконанні реакт-коду на сервері для генерації HTML-розмітки та подальшим виконанням того самого коду (зібраного трошки інакше, але з тих самих вхідних файлів) на клієнті для «гідрації» розмітки в повноцінний інтерактивний React-застосунок.

В теорії все виглядає доволі просто: в нас є код, який ми пишемо один раз, а виконуємо двічі у двох різних рантаймах. Що може піти не так? А піти не так може багато чого: від бібліотек, які підтримують лише один рантайм до цілої купи ускладнень, пов’язаних з початковим завантаженням даних задля генерації розмітки на сервері, налаштуванням Lazy Loading (з коробки для SSR воно почало працювати лише з React 18) та більш критичного значення оптимізації продуктивності застосунку.

Популярність Next.js була обумовлена саме тим, що він одним з перших в екосистемі React надав готове середовище та запропонував ефективні варіанти розв’язання цих проблем. А також поступово обростав фічами, на кшталт SSG (Static Site Generation) та часткового SSG, ISR (Incremental Static Regeneration) тощо.

В травні 2023 року відбувся реліз Next.js 13.4, в якому App Router зазначався стабільним та рекомендованим підходом для подальшої роботи з Next.js. І саме з ним з цього моменту пропонувалося створювати всі нові застосунки.

З собою App Router також приніс купу довгоочікуваних фіч: Layouts, Streaming та React Server Components. RSC повинні були полегшити найбільший головний біль React SSR — гідрацію та купу JS-коду, який треба завантажувати на клієнт, навіть для компонентів без інтерактивності.

Саме в цей момент я починав роботу над Freeversity і вагався між Next.js та Remix. Після деяких тестових спроб я обрав Next.js через більшу популярність та фічі, які на той момент здавалися максимально сучасними та корисними. Все це йшло з екосистемою Vercel, що дозволяє парою кліків безкоштовно деплоїти Next.js-застосунок напряму з GitHub-репозиторію.

І тут ми перейдемо до головної фундаментальної проблеми Next.js.

Runtimes

В попередньому розділі ми згадували, що зазвичай SSR-застосунок виконується у двох рантаймах: браузер та Node.js-server. Це додає застосункам неабиякої складності, але у підсумку економить купу часу розробника.

Next.js йде далі. І до двох звичних рантаймів додається третий — Edge. В документації цій особливості приділяється не так багато уваги: розділ про рантайми, розділ про Edge. Це створює фальшиве відчуття безпеки, бо число 3 описує кількість рантаймів лише в теорії. Тоді як на практиці все набагато складніше.

Client

Тут нічого нового. Сюди добирається лише код компонентів, промаркованих директивою ‘use client’. Саме цей код виконує гідрацію та додає компонентам інтерактивність.

Edge Functions

В Edge-рантаймі виконується middleware, що є додатковим шаром перед обробкою будь-якого запита (до сторінок, чи до бекенд-ендпоїнтів). Middleware може виконувати редиректи, додавати куки та надсилати HTTP-запити. Загалом, все доволі схоже на звичний Route Handler, але доступу до БД тут не має, тож спілкуватися з сервером можна лише відправляючи запити через fetch.

Serverless Functions

Цей рантайм використовується для двох головних речей:

  1. Генерація розмітки з Server та Client-компонентів (так, Client-компоненти також виконуються і на сервері, ха!).
  2. Обробка бекенд API-запитів (Route Handlers в теці /app/api).

І тут починається пекло. Важливою є особливість, що для Route Handlers, Server та Client-компонентів доступні різні API та діють різні правила виконання.

Route Handlers необхідні для обробки запитів, комунікації з БД та відправки відповідей. Нічого нового, чи не так? Але першою магічною особливістю, яку ви побачите, буде те, що хедери та куки можна імпортувати як статичні файли. І для кожного запита магічним чином в імпортованих файлах будуть лише дані, які стосуються саме поточного запита. Як таке взагалі можливо?!

Можливо це завдяки тому, що це не просто Node.js-рантайм. Це Lambda-функція, контекст якої створюється і виконується окремо для кожного запита. Це значно зменшує ціну хостинга вашого застосунку, допоки користувачів у вас небагато. А також неймовірно спрощує масштабування, коли кількість користувачів збільшується.

Але й на цьому сюрпризи не закінчуються! Server та Client-компоненти, хоч і виконуються на сервері, не мають жодного доступу до back-end API (майже, ха!). До того ж, для комунікації з сервером ми повинні користуватися Server Actions або Fetch API і відправляти запити так, ніби код виконується в браузері.

При цьому API, доступні для Server та Client-компонентів, теж різняться: в серверних компонентах не можна використовувати хуки чи контекст, але можна читати куки та хедери. Клієнтські ж компоненти можуть мати стан, бути інтерактивними, але потребують гідрації та додаються до клієнтського бандла.

В підсумку ми маємо не три, а п’ять(!) різних середовищ виконання коду, в кожного з яких є свої особливості та доступні API. І саме з них випливає решта підводних каменів цього фреймворка.

HTTP-запити

Що може бути не так з HTTP-запитами? Навіть найпрадавніші сайти якось виконували цю роботу. Звісно, були проблеми, пов’язані з читабельністю асинхронного коду, керуванням стану запита та обробкою помилок. Але Next.js вивів цю проблему на новий рівень.

Отже, повертаємося до наших рантаймів:

Рантайм

Доступні HTTP APIs

Client

  • Fetch API,
  • XMLHttpRequest

Edge Functions

  • Fetch API Polyfill (undici HTTP)

Serverless Functions

  • Node.js HTTP Agent
  • Fetch API Polyfill (undici HTTP)

Що це значить на практиці?

Undici

По-перше, можливість помилки, яка поклала мій пет-проєкт на кілька днів і вбила купу часу на дебаг і розшифровку логів Vercel. Хоча в цей час я міг би писати якусь набагато менш емоційну статтю, скажімо, про оптимізацію продуктивності React-застосунків. Помилка, з якою я мав справу, чудово описана в статті Fix: Vercel + Next.JS «fetch failed» from undici. І найнеприємнішою її особливістю є те, що відтворюється вона найнеочікуванішим чином і лише на інфраструктурі Vercel.

Але що нам заважає відмовитись від використання вбудованого Fetch API з ненадійними поліфілами та встановити найпопулярнішу та багато разів перевірену кросплатформенну бібліотеку для HTTP-запитів? А саме, Axios! Таку рекомендацію можна час від часу побачити в топіках, пов’язаних з undici.

Axios

Це цілком робочий варіант аж до того моменту, поки вам не доведеться відправити HTTP-запит в Edge Function. Бо найпопулярніша бібліотека для HTTP-запитів за версією npm-trends працює лише в рантаймах, що мають XMLHttpRequest або Node.js HTTP Agent! І особисто в мене не вийшло запустити Axios в Edge з жодним з наявних Fetch API-адаптерів. Тож якщо вам судилося винести з цієї статті лише один корисний висновок, то ось він: не використовуйте Axios з Next.js App Router. Особисто я зупинився на cross-fetch.

UPD: В коментарях зазначили, що бета-версія axios підтримує Fetch API для відправки HTTP-запитів, тож є вірогідність, що після оновлення, axios з декими незручностями, але буде працювати в Edge-середовищі. Однак офіційно це середовище не підтримується, тож з використанням axios з Next.js все ще варто бути обачним.

Але і це не все.

Credentials

HTTP-запити по-різному виконуються для Client та Server-середовища. Запити з компонентів на клієнті автоматично містять в собі headers та cookies, тоді як в запити на сервері кредентіали треба передавати явно. І імпортувати їх можна виключно в коді, який виконується тільки на сервері. Тобто, для кожного такого запита передавати кредентіали треба аж з рівня Server Component, де їх можна імпортувати.

Cookies

Офіційна документація Next.js стверджує, що ми можемо задавати серверні куки в Middleware, Server Actions та Route Handlers: Cookies in Next.js. І це створює фальшиве відчуття безпеки.

Тут знадобиться додатковий контекст. Для автентифікації/авторизації на Freeversity я використовую окремий опенсорс-сервіс Keycloak. Це чудове та надзвичайно потужне рішення. З ним можна дуже тонко налаштовувати доступи до ресурсів, а також керувати способами автентифікації через купу сторонніх OAuth-провайдерів. Чудо, а не технологія!

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

Повертаємось до того, що задавати серверні куки ми можемо лише в middleware, server actions та route handlers.

Server Actions

Виконуються тільки з завантаженої сторінки у відповідь на дію користувача. Не підходить.

Middleware

Виконується перед кожним запитом. В цілому підходить, але відривати авторизаційну логіку так далеко від API не хотілося б.

Route Handler

Саме тут нам і потрібна перевірка доступу. Виглядає як те, що треба.

І саме за такою логікою я почав вибудовувати архітектуру авторизації/автентифікації. Аж раптом виявилось, що перший запит на Route Handler завжди відправляється з Server Component. А в серверних компонентах встановлювати куки не можна.

Так, ми встановили куки в Route Handler, але запит відправляється з серверного компонента, тож до браузера користувача вони не дійдуть. Проблема більш детально описана та обговорена в топіку в репозиторії Next.js та є очікуваною поведінкою фреймворка (з відповідною кількістю негативних реакцій від розробників).

Рішенням стало дублювання роутінгу з авторизаційними запитами в middleware, де куки встановлювати дозволено. Відповідно до кожного окремого роуту. Тобто, у два рази більше коду і наразі це єдиний можливий спосіб додати HttpOnly-куки за першого запиту.

Serverless Functions

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

За своєю сутністю Vercel Serverless Functions є AWS Lambda-функціями. У світлі цього цікавою особливістю цінової політики Vercel є той факт, що на сайті ми бачимо лише три плани: Hobby (безкоштовний), Pro ($20/місяць) та Custom. Надзвичайно щедрий безкоштовний план переходить у все ще дуже щедрий дешевий план, а далі про порядок цін можна лише здогадуватись.

І це цілком логічно, бо найбільша цінова ефективність Lambda-функцій спостерігається саме за умови невеликих навантажень: Economics of ’Serverless’. А деплоймент Next.js-застосунків поза Vercel — це зовсім окрема історія, яку, однак, варто враховувати перед вибором Next.js для повноцінного комерційного проєкту.

Поза ціновою політикою та загальновідомими проблемами з холодним стартом Serverless Functions, особисто для мене найбільшою складністю стала невідповідність моєї ментальної моделі виконання серверного коду тому, що насправді відбувається в Next.js.

Це не звичний Node.js-сервер. Як я згадував раніше, кожне виконання такої функції має свій окремий контекст і не може зберігати внутрішній стан для різних запитів.

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

Тож якщо я використовую вищезгаданий сервіс Keycloak або базу даних Postgres — для кожного запиту ініціалізація з’єднань та запит токенів буде відбуватися наново. Частково вирішити цю проблему можна або витративши додаткові гроші на Redis, або окремим повноцінним сервером для бекенд-API (але це ускладнить використання Server Actions та підтримку міддлверу з авторизаційними куками).

Development experience

Особисто для мене найбільшою проблемою під час розробки стали недостатньо зрозумілі помилки. Особливо в продакшні. Також ці помилки можуть відтворюватися нерегулярно. Ба більше, вони можуть не відтворюватися локально, але крашити продакшн-середовище.

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

До того ж Next.js є настільки роздутим, що TypeScript-лінтер та ESLint не будуть працювати для всього проєкту в дев-режимі (і щось мені підказує, що необхідність білдити код під п’ять різних рантаймів одночасно є однією з причин такої незручності). Тому обов’язково перед кожним пушем в репозиторій перевіряйте, що продакшн-білд проходить без помилок.

Висновок

Чи варта оновлена імплементація загальновідомого Next.js описаних зусиль та незручностей, вирішувати вам для кожного конкретного проєкту. Next.js справді вирішує велику кількість проблем, пов’язаних з SSR, а в багатьох випадках спрощує процес розробки.

Але не дайте фальшивому відчуттю безпеки себе надурити. Це вже не той звичний Next.js, який вважався найбільш обкатаним та надійним рішенням для SSR.

App Router ламає через коліно всі звичні ментальні моделі. Щось з цих нових особливостей виглядає як зручний фокус. Інше — як чорна магія (імпорт куків клієнтського запита в серверному коді все ще викликає в мене відчуття глюку в матриці). А дещо дратує так сильно, що перевести проєкт на інший фреймворк видається не такою вже і складною задачею, порівняно з подальшою розробкою на Next.js.

В усіх трьох випадках причина в тому, що в Next.js майже нічого насправді не є тим, чим намагається здаватися. Рантаймів насправді не три, серверне середовище не є серверним, Fetch API не Fetch API, а клієнтські компоненти не тільки клієнтські. І навіть код, що працює локально, не завжди буде працювати після деплою.

👍ПодобаєтьсяСподобалось15
До обраногоВ обраному6
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Піти з NextJS на Remix це як шапку не одягти, щоб собі вуха відморозити на зло мамі.
Чекаємо через рік статтю «Як мігрувати з Remix на NextJS»

Тоді скоріше вже обмежуся кастомним SSR-сетапом :)

Дякую за статтю дізнався для себе багато цікавого, хоча з більшістю описаного я не стикався. Я новачок в розробці, підкажіть будь ласко яку архітектуру ви обрали для свого проекта для next.js

Трошки розчарований малим переліком недоліків :) Я зіштовхнувся з іншими болючими моментами, і головний з них — конфлікти CSS. При білді фреймворк збирав таким чином, що той самий чанк включався на сторінку кілька разів, і тоді, наприклад, розміри та кольори елементів стаються непередбачуваними, «стрибають» при переході між сторінками. І це пофіксили навіть не в 14 версії, а в 14.2.

З коробки немає підтримки мультимовності, все дописувати ручками самому. А ще в документації є примітка, що використання Suspense не впливає на SEO, та по факту воно вбиває повністю, як тільки вимикаєш js у браузері.

А що використовуєте для стилізації, якщо не секрет? В них були проблеми з css-in-js підтримкою на самому початку, але пофіксили доволі швидко. Я використовую Mui та css-modules для додаткової стилізації (хочу якось спробувати перевести все на stylex+radix, але поки не на часі)

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

CSS модулі, але то не дуже важливо. Скаржились на будь-що. Ось ця історія на гітхабі.
github.com/...​rcel/next.js/issues/51030

Ауч. Мати з таким справу на комерційному проєкті, напевно, дуже неприємний досвід

А з приводу локалізації, до речі, саме мої юзкейси зв’язкою nextjs + i18next поки що покривались непогано

не стикались з помилками гідрації коли імплементували i18next?

Час від часу бувають невідтворювані помилки локально, але на проді все наче ок.

А от коли допомогав знайомому налаштувати i18next з AppRouter на робочому проєкті — було весело. Довелося пояснювати всі ці приколи з рантаймами, після яких хлопець дивився на мене як на психа.

До того ж, перемикання мови звичними API i18next теж не працює. Тільки через перехід на іншу лінку з префіксом мови (що теж підійде не всім)

врешті-решт просто натикав suppressHydrationWarning

Якщо потрібен SSR краще взати Nuxt!

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

рекомендую автору написати щось своє і вже після цього зневажливо писати про nextjs )))

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

Так. Хоча б спробувати, щоб встати на місце розробників інструменту і побачити які проблеми виникають з їхнього боку. Але це швидше іронія. Сервер, клієнт, ssr, ssg, кешування, раутінг — дуже багато усього надає nextjs. Мені він подобається і на граблі на ньому я наступав рідко мабуть тому що кілька років перед цим мав справу з Gatsby який ще складніший у використанні і мав на той час меньше інструментів ніж nextjs зараз.

побачити які проблеми виникають з їхнього боку

якщо проблеми виникають (з’являються) з боку (зі сторони) розробників, то це, виходить, розробники створюють ці проблеми. логічно ж?
(надворі п’ятниця, єслішо, все не серйозно)

Спробуй порівняти з azure функцій v4?

Нажаль, ніколи до цього не використовував azure функції

Мені дуже сподобався стиль автора! З нетерпінням чекатиму нових статей!

Емм, я взагалі дивуюсь як ви з фронтенд-фреймворками працюєте. Те, що в бекенд фреймворку назвали б глибокою бетою, фронтендери гордо викочують як реліз, після чого виходить куча мінорних версій де фіксять баги. Коли все якось стабілізується, прийнято випустити версію з масою нових змін і все по новій.
У випадку Next.js викотили, по-факту, саме бету яку потім на ходу допилювали. В результаті визодило що ми з фронтендерами вирішуємо що робити — чекати чергового релізу де пофікшена бага (і, можливо, додані нові) чи пилити якись фікс самим. Тобто фронтенд деви, коли у них щось не працювало як очікувалось, спочатку лізли шукати аналогічну проблему в nextjs github issues, потім бажано було перечитати що ж уже пофікшено в canary релізах, і якщо пофікшено, прикинути скільки чекати на черговий реліз.
P.S.
Безвідносно до технології/мови програмування etc... — коли ти замість вирішувати бізнес задачі, витрачаєш кучу часу на боротьбу з проблемами фреймворку, це означає одне з двох: або ти не розумієш як фреймворк працює, або фреймворк — г... Втім, перше не виключає друге. ))

Тобто фронтенд деви, коли у них щось не працювало як очікувалось, спочатку лізли шукати аналогічну проблему в nextjs github issues

Швидше писали якійсь свої костилі або форк робили, тимчасово самі фіксили і використовували той форк

Та ні, який форк — хто його потім буде підтримувати.

Вони самі і будуть поки це не пофікситься в релізі. Або так або костиль

Таке враження що ми про різні речі говоримо. Обхідне рішення чи костиль чи ще щось — воно у вашому коді, а не в коді nextjs. А форк — ок, припустимо ми форкнули nextjs репозиторій, внесли зміни, через деякий час релізнули проект. Але реліз не означає що наступнизх релізів не буде. Як ви собі уявляєте наступну підтримку девелоперами форку nextjs репозиторію? Чи про апдейти на нові версії nextjs варто забути?

Робиш форк і правиш собі коли треба на вже а в них 10 кіл рев’ю. Далі підміняєш пакет в проекті своїм форком. Коли оригінал пофіксили то міняєш назад. Мова звичайно йде про шось разове шо в перспективі вони теж пофіксять

Дякую авторові за цікавий погляд на фреймворк. Недавно взявся за Next.js (мідл фронтенд-розробник, у вільний час навчаюся в напрямку фулстеку). Вибрав для свого пет-проджекту app router — як (нібито) новіший і актуальніший, і все дуже класно зайшло: server / client components, server actions, middleware, розширений fetch api з можливістю кешування, — дуже зручно та інтуїтивно зрозуміло. З pages-роутером наразі не пробував і не маю з чим порівняти. Авторизація за допомогою jsonwebtoken + bcrypt чудово працює (реалізував у middleware, жодних проблем). Стосовно запитів: я взагалі не використовую GET-запитів, а напряму звертаюсь до mysql бази даних із серверних компонентів, де рендериться html, що супер зручно — сторінка завантажується швидко, немає затримок (сайт + БД на одному VPS сервері). Читав, є такий підхід, рекомендуюсь саме так і робити. Тоді як у nested клієнтські компоненти за потреби вже передаю готові серіалізовані дані пропсами з серверного компонента. А POST / PATCH / PUT-запити вже відправляю з client components на API Routes на сервері.

P.S. Згідний з автором стосовно дивних помилок, які іноді проскакують на дев сервері — що помітив: у кількох client-компонентах, де використовую реакт контекст, при збереженні коду в дев режимі деколи контекст має значення null, і доводиться перезавантажувати сторінку у бравзері — тоді все ок. Також: кешування запитів та сторінок у дев-сервері перевірити неможливо: для цього треба збілдати всю апку, що було б не зовсім зручно (наразі кешування запитів та SSG не використовую, бо даних мало і все й так працює швидко, хоча бавився з тим і пробував, як працює). Що ще не сподобалось — не бачу, як оверрайдити layout.tsx в компонентах на глибшому рівні — дочірні лейаути стають нестед в parent. Звісно, обходжу це в інший, менш зручний спосіб. Ще не сильно приємна штука — дуже глибоко сховані налаштування вебпак, і без костилів їх нормально не змодифікуєш (можна додавати налаштування вебпак у next config файлі, але от якщо хочу змодифікувати налаштування одного з лоудерів, які вже використовуються глибоко під капотом, умовно хай `localIdentName` y css loader — то це вже дуже велика проблема). Це так, дрібниці, щоб придертись, а загалом вибором фреймворку для маленького сайтика задоволений)) До речі, SEO в Google пошуку чудово працює.

А як хендили саме запит пермішнів? Також на рівні міддвера?

Також на рівні міддвера?

Так, на рівні мідлвара. Можна створити функцію в окремому файлі і імпортувати в мідлвар, щоб все виглядало чисто. В мене в принципі дуже простенька апка всього з однією роллю (якщо авторизований — значить, адмін), і я просто перевіряю, чи дійсні refresh token & access token. Але якщо б треба було більше ролей, то не бачу аж якоїсь великої проблеми: я би просто додав роль в аксес токен і перевіряв би її в мідлварі. Знову-таки, я в бекенд тікльки почав вникати, а я фронтендер, тому в жодному випадку не прийміть мою особисту думку за авторитетну :)

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

І особисто в мене не вийшло запустити Axios в Edge з жодним з наявних Fetch API-адаптерів.

Ну можливо бета (v1.7.0-beta.1) на цьому тракторі заведеться без сторонніх адаптерів, але не факт, бо офіційно ми його не підтримуємо і відповідно тестування в його середовищі не проводиться.

npm i axios@next

Проблема в тому, що в едж немає ані XMLHttpRequest, ані node.js http. Тож якщо в цій версії fetch не почав використовуватись на рівні з іншими API, скоріш за все, в едж він так і не заведеться.

І наскільки я зрозумів з обговорень на гітхабі, використовувати fetch в axios за відсутності інших двох, задача нетривіальна через архітектурні особливісті axios та деякі фічі, які неможливо реалізувати через fetch.

Але, звісно, було б круто, якби axios міг і це.

Так чи інакше, для мене він так і залишиться дефолтним вибором для не-Next.js проєктів :)

Тож якщо в цій версії fetch не почав використовуватись на рівні з іншими API

Суть бети і полягає в додаванні fetch третім адаптером, який за замовчуванням використовується коли xhr і http адаптери недоступні.

скоріш за все, в едж він так і не заведеться.

Та наче заводиться, але мабуть не надто зручно використовувати, так як треба бути ручками обгортати в new Response()

та деякі фічі, які неможливо реалізувати через fetch.

підтримка прогресу там теж є, тобто він повторює функціонал xhr, але + request/response streams

Оу. Треба буде якось потестити. Хоча, звісно, переписувати все назад з cross-fetch на axios не буде занадто цікавою справою.

Так чи інакше, дякую за додаткову інфу. Трохи пізніше додам апдейт в статтю :)

переписувати все назад з cross-fetch на axios не буде занадто цікавою справою

ось в такі моменти варто задуматись над ще однією абстракцією навколо клієнта для запитів, щоб при зміні бібліотеки не потрібно було по всьому проекту бігати і в 1000 місцях міняти
використання + імпорти ;)

теж рік тому коли мігрував великий інтернет-магазин на nextjs 13+ постійно боровся з ним, кожна проблема переростала у великий виклик, хоча більшість найкритичніших проблем встиг вирішити, але мені пощастило — зробив MVP з nextjs і закінчив працювати в тій компанії, тому навіть не уявляю як там далі все це розвивалось та які челенжі могли ще спливти

Порівняно з CRA постійно відчувається боротьба з фреймворком.
Ще весело писати тести для цього міксу серверних і клієнтських компонентів)

Відверто кажучи, навіть кастомний сетап вебпака для мене ніколи не викликав такого відчуття боротьби, як новий Next.js :)

Після того як деприкейтнули cra, а vite використовувати не хотіли — то використовуємо next тільки як інструмент зборки для.... статікі spa застосунку..) це єдине що є хорошого і на що можна покластися «вдовгу».

Лише одна nextjs-page сторінка, корінь обертаємо в ssr:false, звичайний людський клієнт-сайд react-router, режим output: «export». І горя не знаємо. Хай іде лісом їх інфраструктура, регідрація, use-client та інша vendor-lock-магія!

Бекенд нормальний на nest.

В моєму випадку, нажаль, SSR потрібен для SEO, тож використовувати Next.js просто я бандлер — не варіант :)

SSG теж ок для SEO. Тим більше що як я розумію сторінки у вас не публічні, тож кравлер все одно їх побачити не зможе. Щодо пермішенів на serverless — їх можна і треба кешувати в глобальних змінних. Лямбди хоча й stateless але реюзаються для різних запитів. І до речі, middleware все ж краще місце для авторизації ніж роути.

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

З приводу serverless, хіба глобальні змінні не перестворюються для кожного нового запита? Бо я чітко бачу по логам, як, скажімо, кожен раз перезапитується токен для авторизаційного сервіса.

Авторизація в міддлвері була значно зручнішою в тому ж Express.js та Pages Routes.

З міддлвером в App Router в нас лише один ентрі для міддлверу і мені не подобається необхідність дублювати структуру роутінгу. Скажімо, в мене є такі сутності, як курси, статті та майстер-класи. Замість обробки авторизації в теці самого реквеста, мені треба на рівні міддлвера парсити урлу та з’ясовувати, в який роут і до якого саме ресурса йде запит. Тож якщо роут в майбутньому зміниться — треба апдейтити міддлвер.

Для ssg в мене всеж забагато інтерактивності. І зі збільшенням кількості контенту публічної інформації, доступної без автентифікації, буде більше.

Зазвичай публічні сторінки не дуже інтерактивні, і інтерактивність де треба в ssg може бути реалізована вже на клієнті, це не зробить SEO гіршим.

З приводу serverless, хіба глобальні змінні не перестворюються для кожного нового запита? Бо я чітко бачу по логам, як, скажімо, кожен раз перезапитується токен для авторизаційного сервіса.

Я звісно не можу 100% стверджувати саме за Vercel, але якщо це дійсно AWS Lambda то не повинні, і в принципі це не важко перевірити через те ж саме логування.

docs.aws.amazon.com/...​st/dg/best-practices.html

Дивіться на «Take advantage of execution environment reuse to improve the performance of your function».

Скажімо, в мене є такі сутності, як курси, статті та майстер-класи. Замість обробки авторизації в теці самого реквеста, мені треба на рівні міддлвера парсити урлу та з’ясовувати, в який роут і до якого саме ресурса йде запит. Тож якщо роут в майбутньому зміниться — треба апдейтити міддлвер.

З документації :

While only one middleware.ts file is supported per project, you can still organize your middleware logic modularly. Break out middleware functionalities into separate .ts or .js files and import them into your main middleware.ts file. This allows for cleaner management of route-specific middleware, aggregated in the middleware.ts

Насправді змішувати авторизацію з бізнес логікою — це погано з архітектурної точки зору, порушує архітектурні best practices.

Можна зробити правила авторизації для різних сутностей в різних модулях, заімпортити їх в middleware.ts і наприклад зробити статичний Map зі строки роуту на відповідну функцію авторизації. Отримати роут із запиту і по цій мапі авторизувати. Можливо в next.js зроблять middleware зручнішим колись, бо зараз це виглядає як затичка, буде нескладно переробити потім.

Зазвичай публічні сторінки не дуже інтерактивні, і інтерактивність де треба в ssg може бути реалізована вже на клієнті, це не зробить SEO гіршим.

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

docs.aws.amazon.com/...​st/dg/best-practices.html

Дивіться на «Take advantage of execution environment reuse to improve the performance of your function».

А от це дуже цікаво, дякую! Не маю зараз часу прочитати вдумливо, але за посиланням написано, що всі ініціалізації конекшнів треба виносити поза контекст виконання лямбди (саме з причин, які я вказав в статті). Маються на увазі якісь додаткові вбудовані інструменти на базі лямбд? Чи мова як раз про окремі сервери чи мікросервіси для авторизації/БД тощо?

Можна зробити правила авторизації для різних сутностей в різних модулях, заімпортити їх в middleware.ts і наприклад зробити статичний Map зі строки роуту на відповідну функцію авторизації.

Якось так і робив, але для динамічних роутів це все ще не зручно і простими ключами не обійтись :С

А от це дуже цікаво, дякую! Не маю зараз часу прочитати вдумливо, але за посиланням написано, що всі ініціалізації конекшнів треба виносити поза контекст виконання лямбди (саме з причин, які я вказав в статті). Маються на увазі якісь додаткові вбудовані інструменти на базі лямбд? Чи мова як раз про окремі сервери чи мікросервіси для авторизації/БД тощо?

Я не в курсі може у Vercel є якісь інструменти, але насправді це просто про зберігання та повторне використання будь яких реюзабельних речей, типу коннекшенів до бази або до KeyCloak або просто якісь спільні для всіх дані, типу довідників.

З того, що я бачив в обговореннях — все зазвичай зводиться до Vercel KV (Redis, по факту). Але проблема неприємна, тож буду інвестігейтити далі.

Таке враження що команда vercel напилила(недопилила) купу непотрібних фіч і їх успішно продає, я юзав NextJs як клієнта, і можу сказати що він без серверлес фіч і на своїй інфраструктурі прекрасно справляється з цією задачею.

Саме так, Vercel вирішив піти по шляху ускладнення фреймворка та інфраструктури в цілому.

Складно сказати, чи вони хотіли як краще, або як прибутковіше, але за особистими враженнями рівень складності виріс непропорційно до реальних бенефітів.

Так, але я намагаюсь бути чемним :)

Цікавий ракурс! Є про що поміркувати.

Підписатись на коментарі