null

NextJS 14. Серверные компоненты и почему в них нельзя выставлять куки.

Прежде чем перейти к кукам, а что вообще из себя представляет серверный компонент?

Короткий ответ: это реактовый компонент с некоторыми ограничениями, который рендерится исключительно на сервере.

В современных реалиях, где функциональные компоненты доминируют, а классовые почти полностью ушли в историю, лично мне удобно думать о компоненте как об обычной функции, возвращающей некоторую разметку (jsx). Именно такой функцией и является любой серверный компонент. Раз уж серверный компонент может быть отрендерен (читайте, "вызван") только на сервере, то внутри него может быть использована любая серверная логика (например, прямой запрос в базу данных). Однако, этот же факт запрещает использовать в нём клиентскую логику (например, с использованием состояния c помощью useState).

Помимо прочего, ввели такой термин как "клиентский компонент". Тут всё просто: это самый классический реактовый компонент. Он ничем не отличается от тех компонентов, которые вы могли писать в своих SPA с CSR. Однако, его название не должно вводить вас в заблуждение о том, что он может рендериться только на клиенте. Как и в более ранних версиях некста, такой компонент будет отрендерен на сервере, если это возможно в контексте страницы, а затем гидрирован (hydration) на клиенте тем джаваскриптом, который вы указали в теле компонента. Почему тогда "клиентский компонент"? Потому что не "серверный" :)

Ключом к успеху является грамотное чередование серверных и клиентских компонентов между собой для получения желаемого результата. Делается это по определённым правилам, узнать о которых можно из документации NextJS.

 

А для чего сделали такое разделение?

Если вы имели опыт с некстом до 13 версии, то наверняка в курсе о getServerSideProps. Это была незаменимая функция для динамического SSR. Как правило, она включала в себя логику для получения данных и результат её выполнения передавался в компонент страницы для дальнейшего рендеринга.

Проблема такого подхода заключалась в том, что контент всей страницы мог быть отображён только целиком за раз и только после выполнения функции getServerSideProps, которая могла включать в себя много долгих запросов. Нельзя было (по крайней мере, без костылей) отобразить одну часть страницы, параллельно догружая другую. Именно эту проблему решили в NextJS 13 за счёт стриминга и серверных компонентов.

 

Несколько слов про стриминг в NextJS.

Обе стороны транзакции HTTP 1.1 могут передавать свои, возможно, неограниченные данные. Преимущество заключается в том, что отправитель может отправлять данные, размер которых превышает лимит его памяти, а получатель может обрабатывать поток данных по мере поступления, вместо того, чтобы ждать прибытия всех данных. По сути, происходит экономия места и времени. Подробнее можно узнать в англоязычной статье

В NextJS отдельные куски HTML рендерятся следующим образом:

  1. React преобразует серверные компоненты в специальный формат данных RSC Payload.
  2. Next использует RSC Payload и JavaScript-инструкции клиентских компонентов для рендеринга HTML на сервере.

Затем на клиенте:

  1. Сгенерированный сервером HTML используется для немедленного отображения неинтерактивной страницы.
  2. RSC Payload используется для согласования деревьев клиентских и серверных компонентов и обновления DOM.
  3. Инструкции JavaScript используются для гидрации (hydration) клиентских компонентов и делают страницу интерактивной.

 

Рассмотрим пример с медленным серверным компонентом:

const SlowComponent = async () => {
  await new Promise((resolve) => setTimeout(resolve, 3000));
  return <div>Я загрузился!</div>;
}

export default function Page() {
  return (
    <main>
      <p>Какой-то контент</p>
      <Suspense fallback={<div>Загрузка...</div>}>
        <SlowComponent/>
      </Suspense>
    </main>
  );
}

Сразу после открытия страницы имеем примерно следующий HTML (за исключением неинтересующих нас скриптов):

Через 3 секунды в рамках изначального запроса в ответ "достримятся" следующие теги: 

Как нетрудно догадаться, это отрендеренный серверный компонент и скрипт для его вставки в нужное место на странице.

 

Так что там с куками?

При попытке выставить куку из серверного компонента, будь то layout, page или что-то ещё, вы столкнётесь со следующей ошибкой:

Почему же так происходит?

Как и любой другой сервер, некст выставляет куки с помощью http-заголовка set-cookie. Делается это с помощью вызова cookies().set().

Напомню, как примерно выглядит http-ответ:

HTTP/1.1 200 OK

set-cookie: foo=bar; Path=/ Vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Accept-Encoding

link: </_next/static/media/c9a5bc6a7c948fb0-s.p.woff2>; rel=preload; as="font"; crossorigin=""; type="font/woff2"

Cache-Control: no-store, must-revalidate

X-Powered-By: Next.js

Content-Type: text/html; charset=utf-8

Content-Encoding: gzip

Date: Tue, 09 Jul 2024 23:22:10 GMT

Connection: keep-alive

Keep-Alive: timeout=5

Transfer-Encoding: chunked

 

<!DOCTYPE html>
<html lang="en">
    <head>....

Тут важно заметить, что тело ответа (в нашем случае, html) идёт в самом конце после списка заголовков. Когда мы уже начали стримить тело ответа, добавить новый заголовок становится невозможно.

Давайте вспомним пример с нашим медленным серверным компонентом. Его содержимое начинает стримиться через 3 секунды после того, как была загружена и отображена остальная часть страницы. Пытаясь выставить куку внутри него, мы по сути пытаемся добавить заголовок в http-ответ, достигший тела.

Next говорит, что куки выставлять можно только внутри Server Action или Route Handler, однако они имеют достаточно узкое применение. Если у вас, как и у меня, возникла необходимость возиться с куками авторизации, рекомендую обратить внимание на middleware и его методы по работе с куками.


 

Вперед

Коротко о себе:

 

Пишу джаваскрипт в компании Tune-it.

Ничего не найдено. n is 0