Привет, друзья! В этой серии из 2 статей-туториалов мы с вами продолжаем разрабатывать клиент-серверное (фуллстек — fullstack) приложение с помощью Next.js и TypeScript. В первом туториале мы подготовили и настроили проект, а также реализовали серверную часть приложения с помощью интерфейса роутов (API Routes), во втором — разработаем клиента и проверим работоспособность приложения. Обратите внимание: данный туториал рассчитан на разработчиков, которые имеют некоторый опыт работы с React и Node.js. Для тех, кого интересует только код, вот соответствующий репозиторий. Интересно? Тогда прошу под кат. Why Did You Render — утилита для отладки React-приложений, позволяющая определить причину повторного рендеринга компонента. Для того, чтобы иметь возможность использовать эту утилиту в Next.js-приложении необходимо сделать 2 вещи: Настраиваем пресет Babel в файле babel.config.js в корне проекта: Инициализируем WDYR в файле utils/wdyr.ts: Импортируем WDYR в файле _app.tsx: После этого для отладки в файле компонента достаточно добавить такую строчку: Material UI — самая популярная библиотека компонентов React. Для ее правильного использования в Next.js-приложении необходимо сделать 2 вещи: Настраиваем плагин Babel в файле babel.config.js: Для чего нужен этот плагин? Для уменьшения размера клиентской сборки. Проблема в том, что при импорте компонента MUI по названию, например: В сборку попадет весь пакет @mui/material, т.е. все компоненты MUI независимо от того, используются они в приложении или нет. babel-plugin-import преобразует именованный импорт в дефолтный, т.е. на выходе мы получаем, например: Таким образом, в сборку попадают только компоненты, которые используются в приложении. Настройка кэша Emotion необходима для предотвращения вспышки нестилизованного контента (flash of unstyled content), например, когда сначала загружаются дефолтные стили браузера и только потом стили MUI, а также для обеспечения возможности легкой перезаписи стилей MUI, т.е. кастомизации компонентов (источник решения). Определяем утилиту для создания кэша Emotion в файле utils/createEmotionCache.ts: Кэш необходимо создавать при запуске приложения как на сервере, так и на клиенте. Настраиваем рендеринг документа в файле _document.tsx (создание кэша на сервере): Настраиваем рендеринг компонентов в файле _app.tsx (создание кэша на клиенте): В нашем приложении будет использоваться несколько "глобальных" компонентов: У нас будет общий макет (layout) для всех страниц приложения. Мы сформируем его прямо в _app.tsx. Кроме того, мы будем анимировать переход между страницами с помощью @formkit/auto-animate (данную утилиту можно рассматривать как современную альтернативу React Transition Group). Импортируем компоненты и стили: Формируем структуру компонентов: Компонент для добавления метаданных в раздел head документа (components/head.tsx): Резервный компонент (components/ErrorFallback.tsx): Подвал сайта (components/Footer.tsx): Шапка сайта (components/Header.tsx): Десктопное меню (components/Menu/Desktop.tsx): Данный компонент представляет собой список ссылок и кнопку профиля. Мобильное меню (components/Menu/Mobile.tsx): Данный компонент представляет собой боковую панель со списком ссылок (+ кнопка для открытия меню) и кнопку профиля. О ProfileButton мы поговорим в разделе про аутентификацию и авторизацию. С вашего позволения, в дальнейшем мы не будет рассматривать каждый используемый компонент. Результат: Десктоп 
 Мобайл (меню закрыто) 
 Мобайл (меню открыто) 
 Генерация статического контента (или статической страницы) (static-site generation, SSG) — это процесс, в результате которого сервер генерирует готовую к использованию разметку (HTML) на этапе сборки приложения. Готовность к использованию означает, что, во-первых, клиент мгновенно получает страницу в ответ на запрос, во-вторых, такие страницы хорошо индексируются поисковыми ботами (SEO). Статический контент бывает 2 видов: с данными и без. Статика без данных — это просто разметка. Статика с данными — это разметка, для генерации которой используются данные, доступные на этапе сборки (данные могут храниться как локально, так и удаленно). Еще раз: страница генерируется на основе данных, актуальных на момент сборки. По общему правилу, это означает невозможность обновления страницы свежими данными без создания новой сборки. Next.js позволяет обойти это ограничение с помощью генерации статического контента с инкрементальной (частичной) регенерацией. В нашем приложении статическими являются главная страница и страница контактов. Для генерации обеих этих страниц используются данные. Данные для главной страницы хранятся локально. Предполагается, что они обновляются между сборками. Данные для страницы контактов хранятся удаленно (на JSONBin.io). Предполагается, что они обновляются каждые 12 часов. Для обновления страницы контактов каждые 12 часов запускается процесс инкрементальной регенерации. Главная страница (pages/index.tsx) состоит из слайдера и 4 информационных блоков и генерируется с помощью данных, которые находятся в файле public/data/home.json. Для передачи данных компоненту страницы используется функция getStaticProps, а для чтения данных — модуль Node.js fs: Результат: 
 Страница контактов (pages/about.tsx) состоит из блока с приветствием и 6 новостных блоков и генерируется на основе данных, хранящихся на JSONBin.io. Для получения данных используется fetch. У каждой новости имеется собственная страница (pages/news/[id].tsx). Для передачи данных компоненту страницы контактов используется функция getStaticProps. А для передачи данных странице новости — функции getStaticProps и getStaticPaths. getStaticPaths сообщает Next.js о том, сколько у нас новостей, т.е. сколько новостных страниц необходимо сгенерировать на этапе сборки приложения. Начнем со страницы контактов (pages/about.tsx): Благодаря настройке revalidate страница генерируется на этапе сборки и обновляется каждые 12 часов. Это означает следующее: Страница новости (pages/news/[id].tsx): Результат: Страница контактов 
 Страница новости 
 При запуске приложение запрашивает у сервера данные пользователя. Это единственные данные, за изменением которых "наблюдает" приложение. Запрос данных пользователя реализован с помощью SWR. SWR позволяет кэшировать данные и мутировать их при необходимости, например, после регистрации пользователя. Благодаря SWR мы можем обойтись без инструмента для управления состоянием приложения (state manager). Определяем абстракцию над SWR для получения данных пользователя в файле utils/swr.ts: В шапке сайте имеется кнопка профиля (ProfileButton): Функционал регистрации, авторизации, загрузки аватаров и выхода из системы инкапсулирован в модальном окне (components/Modal.tsx): При отсутствии данных пользователя содержимым модалки являются вкладки аутентификации (components/AuthTabs.tsx): Форма регистрации (components/Forms/Register.tsx): Форма авторизации почти идентична форме регистрации. Результат: Форма регистрации 
 Форма авторизации 
 При наличии данных пользователя содержимым модалки, которая рендерится при нажатии кнопки профиля, является пользовательская панель (components/UserPanel.tsx), содержащая форму для загрузки аватара и кнопку для выхода пользователя из системы: Форма загрузки аватара (components/Forms/Upload.tsx): Кнопка для выхода из системы (components/Buttons/Logout.tsx): Результат: Без превью 
 С превью 
 После загрузки аватар пользователя отображается в шапке сайте на месте кнопки профиля. Для генерации страницы блога и страниц постов используется рендеринг на стороне сервера с помощью функции getServerSideProps. Данная функция позволяет выполнять серверный код и вызывается при каждом запросе страницы. На странице блога (pages/posts/index.tsx) рендерится кнопка для создания нового поста и список постов (при наличии): Кнопка создания поста (components/Button/CreatePost.tsx): Форма создания поста (components/Forms/CreatePost.tsx): Страница поста (pages/posts/[id].tsx): Кнопка лайка поста (components/Buttons/LikePost.tsx): Кнопка удаления поста (components/Buttons/RemovePost.tsx): Кнопка редактирования поста (components/Buttons/EditPost.tsx): При нажатии этой кнопки рендерится модалка с формой для редактирования поста (components/Forms/EditPost.tsx), которая почти идентична форме создания поста. Кнопки лайка, редактирования и удаления поста дублируются на странице блога в карточках превью постов в виде иконок. Результат: Форма создания поста 
 Страница поста 
 Форма редактирования поста 
 Страница блога 
 На этом разработку нашего приложения можно считать завершенной. Надеюсь, вы узнали что-то новое и не зря потратили время. Приветствуются любые замечания и предложения. Благодарю за внимание и happy coding!  JavaScript Developer