00:00:00(бодрая музыка) - Всем привет.
00:00:06Меня зовут Аврора.
00:00:07Я веб-разработчик из Норвегии.
00:00:09Я работаю консультантом в Crane Consulting и активно использую Next.js App Router в своем текущем проекте.
00:00:16Сегодня я расскажу о паттернах композиции,
00:00:18кэширования и архитектуры в современном Next.js,
00:00:21которые помогут обеспечить масштабируемость и производительность.
00:00:24Позвольте сначала напомнить о самом фундаментальном понятии для этого доклада: статическом и динамическом рендеринге.
00:00:30Мы сталкиваемся с обоими в Next.js App Router.
00:00:33Статический рендеринг позволяет создавать более быстрые сайты,
00:00:35поскольку предварительно отрендеренный контент можно кэшировать и глобально распространять,
00:00:40обеспечивая пользователям более быстрый доступ.
00:00:42Например, сайт Next.js Conf.
00:00:46Статический рендеринг снижает нагрузку на сервер,
00:00:48так как контент не нужно генерировать для каждого запроса пользователя.
00:00:51Предварительно отрендеренный контент также легче индексируется поисковыми роботами,
00:00:55поскольку он уже доступен при загрузке страницы.
00:00:58Динамический рендеринг,
00:00:59с другой стороны,
00:01:00позволяет нашему приложению отображать данные в реальном времени или часто обновляемые данные.
00:01:05Он также позволяет нам предоставлять персонализированный контент,
00:01:07такой как дашборды и профили пользователей.
00:01:09Например, дашборд Vercel.
00:01:12С динамическим рендерингом мы можем получить доступ к информации,
00:01:15которая известна только во время запроса.
00:01:16В данном случае,
00:01:17какой пользователь заходит в свой дашборд – это я.
00:01:20Существуют определенные API,
00:01:21которые могут вызывать динамический рендеринг страницы.
00:01:25Использование пропсов `params` и `searchParams`,
00:01:27передаваемых страницам,
00:01:28или их эквивалентных хуков,
00:01:30вызовет динамический рендеринг.
00:01:32Однако с `params` мы можем заранее определить набор предварительно отрендеренных страниц,
00:01:36используя общие статические `params`,
00:01:37а также кэшировать страницы по мере их генерации пользователями.
00:01:40Кроме того,
00:01:41чтение входящих куки и заголовков запроса приведет к динамическому рендерингу страницы.
00:01:46В отличие от `params`,
00:01:47попытка кэшировать или предварительно отрендерить что-либо с использованием заголовков или куки вызовет ошибки во время сборки,
00:01:52поскольку эта информация не может быть известна заранее.
00:01:56Наконец,
00:01:56использование `fetch` с конфигурацией кэша данных `no-store` также принудит к динамическому рендерингу.
00:02:00Итак,
00:02:01это лишь некоторые,
00:02:02есть и другие API,
00:02:03вызывающие динамический рендеринг,
00:02:05но эти встречаются чаще всего.
00:02:06В предыдущих версиях Next страница рендерилась либо полностью статически,
00:02:11либо полностью динамически.
00:02:13Один-единственный динамический API на странице приводил к динамическому рендерингу всей страницы.
00:02:17Например, простая проверка аутентификации по значению куки.
00:02:20Используя серверные компоненты React с Suspense,
00:02:22мы можем потоково загружать динамический контент,
00:02:24такой как персонализированный приветственный баннер или рекомендации,
00:02:28по мере их готовности,
00:02:29предоставляя только запасные варианты с Suspense,
00:02:31при этом отображая статический контент,
00:02:33например,
00:02:33рассылку.
00:02:34Однако,
00:02:34как только мы добавляем несколько асинхронных компонентов на динамическую страницу,
00:02:38например,
00:02:39рекомендованный продукт,
00:02:40они также будут выполняться во время запроса,
00:02:43хотя и не зависят от динамических API.
00:02:45Поэтому,
00:02:45чтобы избежать блокировки начальной загрузки страницы,
00:02:48мы бы также приостанавливали и потоково загружали эти компоненты,
00:02:51выполняя дополнительную работу,
00:02:53создавая скелетоны и беспокоясь о таких вещах,
00:02:55как смещение макета.
00:02:56Однако страницы часто представляют собой смесь статического и динамического контента.
00:03:01Например,
00:03:01приложение электронной коммерции,
00:03:03зависящее от информации о пользователе,
00:03:05но при этом содержащее в основном статические данные.
00:03:07Вынужденный выбор между статическим и динамическим рендерингом приводит к избыточной обработке на сервере контента,
00:03:14который никогда или очень редко меняется,
00:03:16и это не оптимально для производительности.
00:03:19Чтобы решить эту проблему,
00:03:21на прошлогодней конференции Next.js Conf была анонсирована директива `use cache`.
00:03:26И в этом году,
00:03:27как мы видели в основном докладе,
00:03:28она доступна в Next.js 16.
00:03:30Таким образом,
00:03:31с `use cache` страницы больше не будут принудительно рендериться статически или динамически.
00:03:36Они могут быть и теми, и другими.
00:03:37И Next.js больше не нужно угадывать тип страницы,
00:03:40основываясь на том,
00:03:40обращается ли она к таким вещам,
00:03:42как `params`.
00:03:43По умолчанию все динамично,
00:03:44а `use cache` позволяет нам явно включить кэширование.
00:03:47`use cache` обеспечивает компонуемое кэширование.
00:03:51Мы можем пометить страницу,
00:03:53компонент React или функцию как кэшируемые.
00:03:55Здесь мы можем кэшировать компонент рекомендованных продуктов,
00:03:59потому что он не требует запроса и обработки и не использует динамические API.
00:04:03И эти кэшированные сегменты могут быть предварительно отрендерены и включены в статическую оболочку с частичным предварительным рендерингом,
00:04:09что означает,
00:04:10что рекомендованные продукты теперь доступны при загрузке страницы и не требуют потоковой передачи.
00:04:14Итак,
00:04:15теперь,
00:04:15когда у нас есть эти важные базовые знания,
00:04:18давайте проведем демонстрацию.
00:04:19Улучшение кодовой базы с распространенными проблемами,
00:04:22часто встречающимися в приложениях Next.js.
00:04:24К ним относятся глубокий проброс пропсов,
00:04:25затрудняющий поддержку и рефакторинг функций,
00:04:27избыточный клиентский JavaScript и большие компоненты с множеством обязанностей,
00:04:30а также отсутствие статического рендеринга,
00:04:32что приводит к дополнительным затратам на сервер и снижению производительности.
00:04:36Итак, давайте начнем.
00:04:37И дайте мне секунду.
00:04:50Отлично.
00:04:54Это очень простое приложение.
00:04:56Оно вдохновлено платформой электронной коммерции.
00:04:59И позвольте мне провести здесь первоначальные демонстрации.
00:05:01Итак, я могу загрузить эту страницу.
00:05:03У меня есть контент, например, этот рекомендованный продукт.
00:05:06У меня есть категории продуктов,
00:05:08различные данные о продуктах.
00:05:09Также есть страница «Просмотреть все»,
00:05:13где я могу увидеть все продукты на платформе и переключаться между ними.
00:05:20Затем у нас есть страница «О нас», которая просто статична.
00:05:24Я также могу войти как пользователь.
00:05:27И это войдет в мой аккаунт.
00:05:30А также получить персонализированный контент на моей панели управления.
00:05:33Например,
00:05:34рекомендованные продукты или персонализированные скидки.
00:05:38Обратите внимание, здесь довольно хороший микс.
00:05:42О, еще одна страница, которую я забыл показать.
00:05:45Страница продукта, самая важная.
00:05:47Также здесь мы можем увидеть информацию о продукте,
00:05:49а затем сохранить ее,
00:05:50если хотим,
00:05:51для нашего пользователя.
00:05:52Итак,
00:05:52обратите внимание,
00:05:53что в этом приложении довольно хорошо сочетаются статический и динамический контент из-за всех наших функций,
00:05:58зависящих от пользователя.
00:05:59Давайте также посмотрим на код, который находится здесь.
00:06:05Итак, я использую App Router, конечно, в Next.js 16.
00:06:08У меня есть все мои разные страницы,
00:06:10такие как страница «О нас»,
00:06:11страница «Все»,
00:06:12наша страница продукта.
00:06:13У меня также есть...
00:06:14Я использую здесь разделение по фичам,
00:06:16чтобы папка `app` оставалась чистой..
00:06:17У меня есть разные компоненты и запросы,
00:06:20взаимодействующие с моей базой данных через Prisma.
00:06:23Так что да, и я намеренно все это замедлил.
00:06:25Вот почему у нас такой долгий этап загрузки,
00:06:28чтобы мы могли легче увидеть,
00:06:30что происходит.
00:06:31Итак,
00:06:31распространенные проблемы,
00:06:32над которыми мы хотели поработать здесь и которые фактически есть в этом приложении,
00:06:36это проброс пропсов,
00:06:37затрудняющий поддержку и рефакторинг функций,
00:06:39избыточный клиентский JavaScript и отсутствие статического рендеринга,
00:06:43что приводит к дополнительным затратам на сервер и снижению производительности.
00:06:47Итак,
00:06:47цель этой демонстрации — улучшить это приложение с помощью умных паттернов композиции,
00:06:52кэширования и архитектуры,
00:06:54чтобы исправить эти распространенные проблемы и сделать его быстрее,
00:06:58масштабируемее и проще в обслуживании.
00:07:01Итак, давайте начнем с этого.
00:07:02Первая проблема,
00:07:03которую мы хотим исправить,
00:07:04на самом деле связана с пробросом пропсов.
00:07:05И это будет здесь, на странице.
00:07:10Обратите внимание,
00:07:11здесь у меня есть переменная `loggedIn` вверху.
00:07:15И вы видите, что я передаю ее нескольким компонентам.
00:07:17Она фактически передается на несколько уровней в этот персонализированный баннер.
00:07:20Так что это затруднит повторное использование,
00:07:22потому что у нас всегда есть эта зависимость `loggedIn` для нашего приветственного баннера.
00:07:28Итак,
00:07:28с серверными компонентами лучшей практикой было бы перенести получение данных в компоненты,
00:07:34которые их используют,
00:07:35и разрешать промисы глубже в дереве.
00:07:37И чтобы получить аутентификацию,
00:07:39если это использует `fetch` или что-то вроде `React cache`,
00:07:42мы можем дублировать несколько вызовов этого и просто повторно использовать его в любом месте наших компонентов.
00:07:48Так что это будет абсолютно нормально для повторного использования.
00:07:51Итак,
00:07:51теперь мы можем фактически переместить это в персонализированный раздел.
00:07:54И нам больше не понадобится этот пропс.
00:07:57И просто поместить его прямо... ой... сюда.
00:08:01И нам больше не нужно будет это передавать.
00:08:04И поскольку мы теперь перемещаем этот асинхронный вызов в персонализированный раздел,
00:08:07мы больше не блокируем страницу.
00:08:09Мы можем просто приостановить его с помощью простого Suspense.
00:08:13И нам не понадобится этот запасной вариант.
00:08:16Что касается приветственного баннера,
00:08:19я полагаю,
00:08:20мы сделаем то же самое.
00:08:22Но попытка использовать...
00:08:23получить переменную или значение `loggedIn` здесь не работает,
00:08:27верно?.
00:08:27Потому что это клиентский компонент.
00:08:29Так что нам нужно решить это по-другому.
00:08:30И мы применим довольно умный паттерн для решения этой проблемы.
00:08:33Мы фактически перейдем в `layout` и обернем все здесь провайдером аутентификации.
00:08:39Итак,
00:08:39я просто оберну этим все мое приложение и получу эту переменную `loggedIn` здесь.
00:08:45И я определенно не хочу блокировать весь мой корневой `layout`.
00:08:48Давайте уберем `await` здесь.
00:08:50И просто передадим это как промис в этот провайдер аутентификации.
00:08:55И это может просто содержать этот промис.
00:08:57Он может просто находиться там,
00:08:59пока мы не будем готовы его прочитать.
00:09:01Итак, теперь у нас это настроено.
00:09:03Это означает,
00:09:04что мы можем пойти дальше и,
00:09:06прежде всего,
00:09:07избавиться от этого пропса.
00:09:09И мы избавимся от этого проброса до персонализированного баннера.
00:09:12И мы также избавимся от проброса пропсов здесь или сигнатуры.
00:09:16И теперь мы можем использовать этот провайдер аутентификации,
00:09:19чтобы получить значение `loggedIn` локально внутри персонализированного баннера с помощью `useAuth` с тем провайдером,
00:09:24который мы только что создали.
00:09:26И прочитать его с помощью `use`.
00:09:28Так что это фактически будет работать так,
00:09:30что нам нужно будет приостановить его,
00:09:32пока он разрешается.
00:09:33Итак,
00:09:33теперь я просто разместил эту небольшую выборку данных внутри персонализированного баннера.
00:09:37И мне не нужно передавать эти пропсы.
00:09:40И пока это разрешается,
00:09:41давайте просто приостановим и этот с запасным вариантом.
00:09:44И давайте просто сделаем общий баннер здесь,
00:09:47чтобы избежать странного кумулятивного сдвига.
00:09:51И, наконец, также избавимся от этого.
00:09:53Итак, теперь этот приветственный баннер компонуем.
00:09:58Он многоразовый.
00:09:59У нас нет никаких странных пропсов или зависимостей на главной странице.
00:10:02И поскольку мы можем так легко его повторно использовать,
00:10:06давайте добавим его также на эту страницу просмотра здесь,
00:10:10которая будет здесь.
00:10:11И я могу просто использовать его здесь без каких-либо зависимостей.
00:10:15Итак,
00:10:16с помощью этих паттернов мы можем поддерживать хорошую архитектуру компонентов,
00:10:22используя `React cache`,
00:10:23`React use`,
00:10:24и делать наши компоненты более пригодными для использования и компонуемыми.
00:10:30Хорошо.
00:10:31Давайте перейдем к следующей распространенной проблеме: избыточному клиентскому JavaScript и большим компонентам с множеством обязанностей.
00:10:40На самом деле, это также на странице «Все».
00:10:43И снова нам придется поработать над этим приветственным баннером.
00:10:46Сейчас это клиентский компонент.
00:10:48И причина,
00:10:48по которой это клиентский компонент,
00:10:50в том,
00:10:50что у меня здесь есть это очень простое состояние `dismissed`.
00:10:53Я могу просто нажать на это.
00:10:54Это приятное взаимодействие с пользовательским интерфейсом.
00:10:56Это нормально.
00:10:57Что не так хорошо,
00:10:58так это то,
00:10:59что из-за этого я превращаю весь этот компонент в клиентский компонент.
00:11:04И я даже использую `swr` для клиентской выборки.
00:11:07Теперь у меня есть этот API-слой.
00:11:08У меня больше нет типобезопасности в моих данных.
00:11:11Да, это не нужно.
00:11:12И мы также нарушаем здесь разделение ответственности,
00:11:14потому что мы смешиваем логику пользовательского интерфейса с данными.
00:11:18Итак,
00:11:18давайте используем еще один умный паттерн,
00:11:20чтобы это исправить.
00:11:21Он называется «паттерн пончика».
00:11:23По сути, я собираюсь извлечь это в клиентский обертку.
00:11:27Итак, давайте создадим новый компонент здесь.
00:11:29И назовем его `BannerContainer`.
00:11:32И он будет содержать нашу интерактивную логику с директивой `use client`.
00:11:37Мы можем создать сигнатуру.
00:11:38Мы можем вставить все, что у нас было раньше.
00:11:42И вместо использования этих баннеров,
00:11:44я просто вставлю сюда пропс,
00:11:46который будет `children`.
00:11:48Вот почему это называется «паттерн пончика».
00:11:50Мы просто создаем эту обертку с логикой пользовательского интерфейса вокруг серверного контента,
00:11:54или это может быть серверный контент.
00:11:56И затем,
00:11:57поскольку у нас больше нет этой клиентской зависимости,
00:12:00мы можем удалить `use client`.
00:12:01Вместо этого мы можем использовать нашу асинхронную функцию `isAuth`.
00:12:06Мы можем превратить это в асинхронный серверный компонент.
00:12:09Мы даже можем заменить клиентскую выборку данных серверной.
00:12:11Итак,
00:12:12позвольте мне просто получить данные о скидках прямо здесь.
00:12:16Данные о скидках.
00:12:18И просто использовать нашу обычную ментальную модель,
00:12:21как и раньше,
00:12:22с типобезопасностью.
00:12:24И это означает,
00:12:24что я также могу удалить этот API-слой,
00:12:26с которым я все равно не хочу работать.
00:12:29Наконец,
00:12:29для `isLoading` мы можем просто экспортировать новый приветственный баннер здесь с нашим контейнером баннера по паттерну пончика,
00:12:36содержащим серверный контент.
00:12:38И это означает, что нам больше не нужен `isLoading`.
00:12:40Итак,
00:12:41мы,
00:12:41по сути,
00:12:42рефакторили все это в серверный компонент и извлекли точку логики пользовательского интерфейса.
00:12:46Но что это?
00:12:48Похоже, у меня еще одна ошибка.
00:12:51На самом деле, это из-за Motion.
00:12:53Используйте Motion.
00:12:54Это действительно отличная библиотека анимации,
00:12:57но она требует директивы `useClient`.
00:12:59И снова,
00:12:59нам не нужно делать это `useClient` только для анимации.
00:13:03Мы можем снова создать обертку по паттерну пончика и просто извлечь обертки для этих анимаций.
00:13:10И это означает,
00:13:11что нам не нужно ничего здесь преобразовывать в клиентскую часть.
00:13:14И я, вероятно, что-то упустил здесь.
00:13:17Да.
00:13:18Вот так.
00:13:21Итак, теперь все здесь было преобразовано в сервер.
00:13:23У нас то же взаимодействие.
00:13:24У нас по-прежнему есть наша интерактивная логика,
00:13:26но теперь у нас есть этот единственный способ получения данных.
00:13:29И у нас гораздо меньше клиентского JS.
00:13:31На самом деле,
00:13:32я сам использую этот паттерн пончика для этого вспомогательного элемента границы пользовательского интерфейса,
00:13:41который выглядит так.
00:13:42Вы это видите?
00:13:43Так что это снова показывает, что я имею в виду, верно?
00:13:45С паттерном пончика у нас есть этот клиентский компонент вокруг серверного компонента.
00:13:49Я также пометил многие другие свои компоненты этим вспомогательным элементом пользовательского интерфейса.
00:13:53Также здесь у меня есть больше серверных компонентов.
00:13:56Давайте также улучшим их,
00:13:58поскольку мы уже довольно хорошо справляемся с этим.
00:14:01Они находятся в футере.
00:14:04Эти категории...
00:14:04Я имею в виду,
00:14:05у меня есть этот хороший компонент,
00:14:06который получает свои собственные данные..
00:14:08И я просто хотел добавить эту функцию `showMore`,
00:14:11на случай,
00:14:12если она станет очень длинной.
00:14:14И с паттерном пончика я могу просто обернуть компонент `ShowMore` здесь.
00:14:20И это будет содержать мою логику пользовательского интерфейса.
00:14:23И это выглядит так, верно?
00:14:27Довольно круто.
00:14:28И теперь это содержит клиентскую логику,
00:14:30позволяя нам использовать состояние.
00:14:33Мы используем `children count` и `to array`,
00:14:35чтобы нарезать это.
00:14:36И что здесь так круто,
00:14:37так это то,
00:14:38что эти два компонента теперь полностью компонуемы,
00:14:40многоразовые и работают вместе вот так.
00:14:42Так что это действительно красота этих паттернов,
00:14:44которые мы здесь изучаем.
00:14:45Вы можете использовать это для чего угодно.
00:14:50Я также использую его для этого модального окна здесь.
00:14:52Да,
00:14:52просто помните об этом в следующий раз,
00:14:54когда будете рассматривать возможность добавления какой-либо клиентской логики в свои серверные компоненты.
00:14:59Хорошо, мы знаем паттерн пончика.
00:15:01Мы знаем,
00:15:01как его использовать для создания этих компонуемых компонентов и избегания клиентского JavaScript,
00:15:07так что мы можем перейти к последней проблеме.
00:15:10Позвольте мне снова закрыть это.
00:15:13Итак,
00:15:14это будет связано с отсутствием стратегий статического рендеринга,
00:15:18верно?
00:15:18Глядя на вывод моей сборки,
00:15:20я вижу,
00:15:21что каждая страница здесь является динамической.
00:15:24Это означает,
00:15:24что всякий раз,
00:15:25когда я что-то загружаю здесь,
00:15:26это будет выполняться для каждого пользователя.
00:15:29Извините.
00:15:30Каждый пользователь,
00:15:30который открывает это,
00:15:31будет получать это состояние загрузки.
00:15:33Это будет тратить серверные ресурсы,
00:15:35ухудшая производительность.
00:15:37И это также означает,
00:15:38что что-то внутри моих страниц вызывает динамический рендеринг или принуждает к динамическому рендерингу для всех моих страниц.
00:15:45На самом деле, это внутри моего корневого `layout`.
00:15:49Не знаю, сталкивались ли вы с этим.
00:15:51Это здесь.
00:15:53В моем заголовке у меня есть этот профиль пользователя.
00:15:57И это,
00:15:57конечно,
00:15:58использует куки для получения текущего пользователя,
00:16:00а это означает,
00:16:01что все остальное также рендерится динамически.
00:16:02Потому что,
00:16:03опять же,
00:16:03страницы могут быть либо динамическими,
00:16:05либо статическими,
00:16:06верно?
00:16:06Это довольно распространенная проблема,
00:16:08и она уже решалась в предыдущих версиях Next,
00:16:11так что давайте посмотрим,
00:16:12что мы можем сделать.
00:16:13Одно,
00:16:14что мы могли бы сделать,
00:16:15это создать группу маршрутов и разделить наше приложение на статические и динамические разделы,
00:16:20что позволило бы мне извлечь мою страницу «О нас».
00:16:23Я мог бы отрендерить это статически.
00:16:25Это нормально для некоторых приложений,
00:16:27но в моем случае важная страница — это страница продукта,
00:16:30и она все еще динамическая,
00:16:32так что не очень полезно.
00:16:33Как насчет этой стратегии?
00:16:35Итак,
00:16:35здесь я создаю этот параметр контекста запроса,
00:16:38кодирующий определенное состояние в мой URL,
00:16:41а затем я могу использовать `generateStaticParams` для генерации всех различных вариантов моих страниц.
00:16:46Это фактически,
00:16:47в сочетании с клиентской выборкой пользовательских данных,
00:16:51позволило бы мне кэшировать это на моей странице продукта.
00:16:54Определенно жизнеспособный паттерн.
00:16:55Он рекомендован Vercel Flags SDK и называется,
00:16:58кажется,
00:16:58паттерном предварительного вычисления.
00:17:00Но это действительно сложно,
00:17:01и у меня есть несколько способов получения данных.
00:17:04И на самом деле,
00:17:04я не хочу переписывать все свое приложение под это.
00:17:07Что,
00:17:07если бы нам не пришлось использовать ни один из этих обходных путей?
00:17:10Что, если бы был более простой способ?
00:17:12Что ж, он есть.
00:17:14Давайте снова вернемся к нашему приложению.
00:17:17Итак,
00:17:17мы можем перейти к `next.config` и просто включить `cache components`.
00:17:23О, отлично.
00:17:25Хорошо,
00:17:25и что это делает,
00:17:26как вы знаете из основного доклада,
00:17:28это фактически переводит все наши асинхронные вызовы во время запроса или в динамический режим.
00:17:34И это также будет выдавать ошибки всякий раз,
00:17:36когда у нас есть какой-либо асинхронный вызов,
00:17:39который не приостановлен,
00:17:40и это даст нам эту директиву `use cache`,
00:17:43которую мы можем использовать для детального кэширования страницы,
00:17:46функции или компонента.
00:17:48Так что да, давайте воспользуемся этим.
00:17:51Мы можем начать с главной страницы.
00:17:55Давайте посмотрим.
00:17:56Итак,
00:17:56снова у меня есть эта смесь статического и динамического контента.
00:17:59У меня есть мой приветственный баннер для меня,
00:18:02что-то для вас также для меня.
00:18:03Давайте снова посмотрим на это с помощью этого вспомогательного элемента пользовательского интерфейса.
00:18:06Итак,
00:18:06например,
00:18:07баннер динамически рендерится с помощью этого здесь.
00:18:10Тогда как я пометил это как гибридный рендеринг,
00:18:13потому что герой получает эту асинхронную вещь,
00:18:16и это происходит довольно медленно.
00:18:18Но это не зависит от каких-либо пользовательских данных или динамических API.
00:18:21Это означает,
00:18:22что все,
00:18:22что здесь гибридно отрендерено,
00:18:24может быть повторно использовано для разных запросов и разных пользователей.
00:18:27И мы можем использовать директиву `use cache` для этого.
00:18:30Итак,
00:18:30давайте добавим директиву `use cache` здесь и пометим это как кэшированное.
00:18:35И это позволит мне...
00:18:37всякий раз, когда я перезагружаю эту страницу...
00:18:41Я не сохранил это..
00:18:43Вот так.
00:18:44Эта часть не будет перезагружаться,
00:18:45потому что она кэширована.
00:18:47Теперь это статично, верно?
00:18:49И есть также другие связанные API,
00:18:52такие как `cache tag`,
00:18:53чтобы позволить мне типизировать это или детально проверять конкретную запись кэша или определять мой период обновления.
00:19:01Но для этой демонстрации давайте сосредоточимся только на простой директиве.
00:19:05Теперь,
00:19:05когда у меня есть эта директива `use cache`,
00:19:07я могу фактически удалить границу Suspense вокруг этого героя.
00:19:10И это означает...
00:19:11ну,
00:19:12что это сделает,
00:19:13так это то,
00:19:13что частичный предварительный рендеринг может фактически включить это в статически предварительно отрендеренную оболочку,
00:19:20так что этот герой,
00:19:21в этом случае,
00:19:22будет частью моего вывода сборки..
00:19:23Давайте сделаем то же самое для всего остального на этой странице,
00:19:26что можно совместно использовать.
00:19:28Например, у меня есть эти категории продуктов здесь.
00:19:31Давайте сделаем то же самое и там.
00:19:33И добавим директиву `use cache` и пометим это как кэшированное.
00:19:37Вот так.
00:19:39И мы можем удалить границу Suspense.
00:19:40Нам это больше не понадобится.
00:19:43То же самое для рекомендованных продуктов.
00:19:44Давайте добавим `use cache` и пометим это как кэшированное.
00:19:48Ой.
00:19:50А затем удалим границу Suspense.
00:19:52Итак,
00:19:53обратите внимание,
00:19:53сколько сложности я просто могу здесь удалить.
00:19:55Мне не нужно беспокоиться о моих скелетонах,
00:19:57о кумулятивном смещении макета,
00:19:59которое я делал раньше.
00:20:00И страница больше не...
00:20:01или у нас больше нет этого ограничения на уровне страницы: статический против динамического..
00:20:07Итак,
00:20:08теперь,
00:20:08когда я загружаю эту страницу,
00:20:10вы увидите,
00:20:11что все здесь кэшировано,
00:20:12за исключением этого действительно специфичного для пользователя контента.
00:20:16Верно.
00:20:18Так что это довольно круто.
00:20:19Давайте перейдем на страницу «Просмотр» и сделаем то же самое там.
00:20:24Да.
00:20:25Я уже пометил все свои границы здесь,
00:20:26чтобы вы могли легко понять,
00:20:28что происходит.
00:20:29И я хочу хотя бы кэшировать эти категории.
00:20:33Похоже, у меня ошибка.
00:20:37Может быть, вы это узнаете.
00:20:38Значит, у меня блокирующий маршрут.
00:20:40И я не использую границу Suspense, когда должен был бы.
00:20:43Обновляю это, это правда, да?
00:20:46Это действительно медленно.
00:20:47И это вызывает проблемы с производительностью и плохой UX.
00:20:50Так что это здорово.
00:20:51`use cache` или `cache components` помогает мне идентифицировать мои блокирующие маршруты.
00:20:55Давайте посмотрим, что там происходит.
00:20:57Итак, это проблема, верно?
00:20:59Я получаю эти категории на верхнем уровне,
00:21:01и у меня нет никакой границы Suspense над ними.
00:21:03По сути, нам нужно сделать выбор.
00:21:05Либо мы добавляем границу Suspense выше,
00:21:07либо выбираем кэширование.
00:21:09Давайте сначала сделаем простое: добавим `loading.tsx` здесь.
00:21:12И давайте добавим страницу загрузки здесь,
00:21:17какой-нибудь приятный скелетный UI.
00:21:21Это довольно хорошо.
00:21:21Это решило ошибку,
00:21:22но на этой странице ничего полезного не происходит,
00:21:25пока я жду.
00:21:25Я даже не могу искать.
00:21:27Итак, с `cache components` динамический...
00:21:30или статический против динамического — это как шкала..
00:21:33И нам решать, сколько статики мы хотим в наших страницах.
00:21:37Итак,
00:21:37давайте сдвинем эту страницу больше в сторону статики и снова удалим этот `loading.tsx`.
00:21:43А затем используем паттерны,
00:21:44которые мы изучали ранее,
00:21:45чтобы перенести эту выборку данных в компонент и разместить ее вместе с пользовательским интерфейсом.
00:21:50Итак,
00:21:51переместим это в мои адаптивные фильтры категорий здесь.
00:21:54У меня два, потому что адаптивный дизайн.
00:21:57Я могу просто добавить это сюда.
00:22:01Ой.
00:22:03И импортировать это.
00:22:05Мне больше не нужен этот запрос.
00:22:06На самом деле, мой компонент становится более компонуемым.
00:22:09И вместо того,
00:22:10чтобы приостанавливать его,
00:22:11давайте просто добавим директиву `use cache`.
00:22:14И этого должно быть достаточно.
00:22:16Итак,
00:22:17обратите внимание,
00:22:17как меня заставляют больше думать о том,
00:22:19где я разрешаю свои промисы,
00:22:21и фактически улучшать архитектуру моих компонентов благодаря этому.
00:22:24Мне не нужно это приостанавливать.
00:22:25Это просто будет включено в статическую оболочку здесь.
00:22:28Список продуктов,
00:22:30позвольте мне просто поддерживать его свежим.
00:22:35Так что я могу перезагружать это каждый раз.
00:22:37Тогда как категории внизу, я также хочу их кэшировать.
00:22:41Итак, давайте перейдем в футер.
00:22:44И поскольку я использую здесь паттерн пончика,
00:22:47это фактически может быть кэшировано,
00:22:49даже если это находится внутри этой интерактивной части пользовательского интерфейса.
00:22:54Так что это совершенно нормально.
00:22:55Так что этот паттерн был хорош не только для композиции,
00:22:58но и для кэширования.
00:22:58Думаю, у меня там еще одна ошибка.
00:23:03Давайте посмотрим, что это.
00:23:04Все еще есть эта ошибка.
00:23:08На самом деле, это из-за этих параметров поиска.
00:23:10Параметры поиска, как мы знаем, это динамический API.
00:23:12Я не могу это кэшировать.
00:23:13Но я могу разрешить его глубже,
00:23:14чтобы показать больше моего пользовательского интерфейса и сделать его статическим.
00:23:18Итак,
00:23:18давайте переместим это вниз,
00:23:20передадим это как промис в список продуктов.
00:23:24Мы сделаем это типизированным как промис здесь, вот так.
00:23:30Давайте разрешим его внутри списка продуктов,
00:23:33используем разрешенные параметры поиска здесь и здесь.
00:23:36И поскольку это приостановлено здесь, ошибка исчезнет.
00:23:40Итак,
00:23:41при перезагрузке здесь перезагружается только та часть,
00:23:44которую я специально выбрал как динамическую.
00:23:48Все остальное можно кэшировать.
00:23:49И это означает,
00:23:50что я могу взаимодействовать со своим баннером или даже искать,
00:23:53потому что эта часть уже была предварительно отрендерена.
00:23:57Хорошо,
00:23:57давайте сделаем последнюю страницу здесь,
00:24:00это страница продукта,
00:24:02которая является самой сложной и самой важной.
00:24:05Сейчас это очень плохо.
00:24:08Это,
00:24:08по-видимому,
00:24:09очень важно для платформы электронной коммерции.
00:24:11Хорошо, давайте исправим и это.
00:24:15Итак, здесь у меня есть эта страница продукта.
00:24:18Давайте начнем кэшировать только повторно используемый контент здесь,
00:24:22например,
00:24:22сам продукт.
00:24:23И просто добавим `use cache` здесь и пометим это как кэшированное.
00:24:27Это должно быть нормально.
00:24:28Это означает, что мы можем удалить границу Suspense здесь.
00:24:33Хорошо,
00:24:33и это больше не перезагружается при каждом запросе,
00:24:37верно?
00:24:38Для деталей продукта давайте сделаем то же самое.
00:24:40Давайте добавим `use cache`.
00:24:41Давайте пометим его как кэшированное и посмотрим,
00:24:45сработает ли это тоже.
00:24:47Нет.
00:24:48На самом деле, это другая ошибка.
00:24:50Она говорит мне,
00:24:50что я пытаюсь использовать динамические API внутри этого кэшированного сегмента.
00:24:54И это правда.
00:24:54Я использую кнопку «Сохранить продукт», верно?
00:24:56Это позволило мне нажимать и переключать состояние сохранения.
00:25:00Итак, что, по вашему мнению, мы можем с этим сделать?
00:25:03Мы можем снова использовать паттерн пончика.
00:25:06На самом деле,
00:25:06мы также можем вставлять динамические сегменты в кэшированные сегменты.
00:25:10Так что мы чередуем их, как и раньше, но с кэшем.
00:25:12Так что это довольно круто.
00:25:14Давайте добавим `children` сюда вот так.
00:25:19И это устранит ошибку.
00:25:21И я могу просто обернуть это вокруг этого одного динамического сегмента моей страницы,
00:25:27удалить границу Suspense и добавить очень маленький UI закладки для этой одной динамической части страницы.
00:25:34И давайте посмотрим, как это выглядит сейчас.
00:25:40Итак,
00:25:40обратите внимание,
00:25:41как почти весь пользовательский интерфейс доступен,
00:25:43но у меня есть этот один небольшой фрагмент,
00:25:45который является динамическим,
00:25:46и это нормально.
00:25:47Все остальное по-прежнему там.
00:25:48И давайте оставим отзывы динамическими,
00:25:51потому что мы можем поддерживать их свежими.
00:25:53Есть еще одна ошибка.
00:25:54Давайте быстро разберемся с этим.
00:25:56Опять же, это `params`.
00:25:58Мне подсказывают,
00:25:59что мне нужно сделать выбор: либо добавить запасной вариант загрузки,
00:26:03либо кэшировать это.
00:26:04Давайте просто используем `generateStaticParams` в этом случае.
00:26:07Это зависит от вашего варианта использования и вашего набора данных.
00:26:10Но в этом случае я просто добавлю пару предварительно отрендеренных предопределенных страниц,
00:26:14а затем просто кэширую остальное по мере их генерации пользователями.
00:26:17И это устранит мою ошибку здесь.
00:26:20Итак, я думаю, что я фактически закончил с рефакторингом.
00:26:22Давайте посмотрим на развернутую версию и увидим,
00:26:25как это выглядит.
00:26:26Итак, я только что развернул это на Vercel.
00:26:27И помните, я намеренно замедлил здесь много выборок данных.
00:26:35И все же,
00:26:36когда я загружаю эту страницу изначально,
00:26:39все уже доступно.
00:26:40Единственное здесь — это те несколько динамических сегментов,
00:26:44таких как скидки и «для вас».
00:26:46То же самое с «просмотреть все».
00:26:47Весь пользовательский интерфейс уже доступен.
00:26:50А для самого продукта это просто мгновенно.
00:26:54И помните,
00:26:55опять же,
00:26:55что все эти кэшированные сегменты будут включены в статическую оболочку с частичным предварительным рендерингом.
00:27:00И это может быть предварительно загружено с использованием улучшенной предварительной загрузки в новом клиентском маршрутизаторе Next 16.
00:27:05Так что это означает, что каждая навигация просто...
00:27:07она просто ощущается такой быстрой, верно?.
00:27:09Хорошо,
00:27:10подводя итог,
00:27:11с `cache components` больше нет статического против динамического.
00:27:17И нам не нужно избегать динамических API или компрометировать динамический контент.
00:27:28И мы можем пропустить эти сложные хаки и обходные пути с использованием нескольких стратегий получения данных только для этого одного...
00:27:35этого попадания в кэш, как я вам показал..
00:27:37Итак,
00:27:38в современном Next.js динамический против статического — это шкала.
00:27:40И мы решаем, сколько статики мы хотим в наших приложениях.
00:27:43И пока мы следуем определенным паттернам,
00:27:45у нас может быть одна ментальная модель,
00:27:47которая по умолчанию производительна,
00:27:49компонуема и масштабируема.
00:27:50Итак, давайте вернемся к слайдам.
00:27:53Итак,
00:27:53если вы еще не впечатлены скоростью этого,
00:27:55это оценка Lighthouse.
00:27:56Итак,
00:27:57я собрал некоторые полевые данные с помощью Vercel Speed Insights.
00:28:00Итак,
00:28:00у нас 100 баллов на всех наиболее важных страницах: главной странице,
00:28:04странице продукта и списке продуктов,
00:28:06хотя они и являются очень динамичными.
00:28:08Итак,
00:28:08давайте,
00:28:09наконец,
00:28:09подведем итог паттернам,
00:28:11которые обеспечат масштабируемость и производительность в приложениях Next.js и позволят нам использовать новейшие инновации и получать такие оценки.
00:28:18Во-первых,
00:28:19мы можем улучшить нашу архитектуру,
00:28:20разрешая промисы глубоко в дереве компонентов и получая данные локально внутри компонентов,
00:28:25используя `React Cache` для устранения дублирующей работы.
00:28:28Мы можем избежать избыточной передачи пропсов клиентским компонентам,
00:28:31используя контекстные провайдеры в сочетании с `React Use`.
00:28:35Во-вторых,
00:28:35мы можем компоновать серверные и клиентские компоненты,
00:28:37используя паттерн пончика,
00:28:38чтобы уменьшить клиентский JavaScript,
00:28:40сохранить четкое разделение ответственности и обеспечить повторное использование компонентов.
00:28:43И этот паттерн позволит нам в дальнейшем кэшировать наши составные серверные компоненты.
00:28:50И,
00:28:50наконец,
00:28:51мы можем кэшировать и предварительно рендерить с помощью `use cache` либо по странице,
00:28:54компоненту,
00:28:55либо функции,
00:28:55чтобы устранить избыточную обработку,
00:28:56повысить производительность и SEO,
00:28:58и позволить частичному предварительному рендерингу статически рендерить эти сегменты приложения.
00:29:01И если наш контент действительно динамический,
00:29:03мы можем приостановить его с соответствующими запасными вариантами загрузки.
00:29:07И помните, что все это взаимосвязано.
00:29:09Итак,
00:29:09чем лучше ваша архитектура,
00:29:10тем легче компоновать,
00:29:11и тем легче будет кэшировать и предварительно рендерить с наилучшими результатами.
00:29:15Например,
00:29:15разрешение динамических API глубоко в дереве позволит вам создать более крупную частично отрендеренную статическую оболочку.
00:29:22И с этим, это репозиторий завершенной версии приложения.
00:29:25Там так много всего,
00:29:26что я даже не показал,
00:29:27что вы можете посмотреть.
00:29:29И вы можете отсканировать QR-код,
00:29:31чтобы найти мои социальные сети там,
00:29:32рядом с репозиторием,
00:29:33если не хотите фотографировать и вводить его самостоятельно.
00:29:36Так что да, это все от меня.
00:29:37Спасибо Next.js Conf за приглашение.
00:29:39[ИГРАЕТ МУЗЫКА]