От Create-React-App к Next

От Create-React-App к Next

От автора: недавно я перевел значительную кодовою базу с Create-React-App (сокращенно CRA) на Next и подумал, что хорошо бы было поделится своим опытом, потому что, поверьте мне, это было довольно долгое путешествие (и не всегда приятное).

Есть много причин, по которым вы можете захотеть перейти на Next с приложения CRA. Next обеспечивает рендеринг на стороне сервера (SSR) и даже инкрементную статическую регенерацию (ISR) при размещении на Vercel. Это всеобъемлющая структура со встроенной маршрутизацией, оптимизацией изображений, средой разработки и многим другим.

Этот пост представляет собой подробное руководство по переходу с CRA на Next. Вот что мы рассмотрим:

Шаблон HTML

Содержание заголовка

Маршрутизация

Разделение кода

Стилизация

CSR / SSR

Проверка кода на ошибки (линтинг)

Запуск обеих систем

Подведение итогов

Шаблон HTML

CRA использует файл index.html в папке public для настройки приложения. Next обрабатывает все в React через файл _document.js, поэтому его нужно перемещать вручную. К счастью, это относительно легко сделать, и документация Next содержит подробные указания.

Содержание заголовка

Для индивидуального управления заголовками на постраничной основе Next предлагает собственное решение next / head, а CRA — нет. Обычно используются react-helmet (или его чистая версия, react-helmet-async ) или более свежая hoofd. В любом случае я бы рекомендовал абстрагировать использование библиотеки на определенные компоненты или хуки, потому что в таком случае при переключении на Next нужно будет обновить код только в одном месте.

Например, вместо импорта Head из react-helmet на каждой странице импортируйте свой собственный компонент Head, который является оболочкой для react-helmet. Таким образом, вы можете обновить детали реализации, чтобы они работали с Next, не изменяя других компонентов.

Маршрутизация

CRA не имеет встроенной возможности маршрутизации, поэтому часто используется вместе с react-router-dom. Обычно у вас есть компонент Router, который объявляет все ваши маршруты и какой компонент визуализировать для каждого маршрута. Например:

import { BrowserRouter, Route, Switch } from 'react-router-dom'
import PostPage from '../PostPage'
import HomePage from '../HomePage' const Router = () => ( <BrowserRouter> <Switch> <Route path='/'> <HomePage /> </Route> <Route path='/post/:slug'> <PostPage /> </Route> </Switch> </BrowserRouter>
) export default Router

Далее идет собственный router. Более того: маршрутизация определяется структурой папок, поэтому декларация router/routes как таковая отсутствует. Чтобы переместить это в Next, вам нужно будет создать pages/index.js и pages/post/[slug]/index.js, которые будут выглядеть так:

// pages/index.js
import HomePage from '../components/HomePage' export default HomePage

// pages/post/[slug]/index.js
import PostPage from '../../../components/PostPage' export async function getStaticPaths() { // If you can compute possible paths ahead of time, feel free to, but you // shouldn’t need to do it to complete the migration to Next. return { paths: [], fallback: true }
} export async function getStaticProps(context) { // If you want to resolve the whole post data from the slug at build time // instead of runtime, feel free to, but you shouldn’t need to do it to // complete the migration to Next. return { props: { slug: context.params.slug } }
} export default PostPage

Тогда в компоненте PostPage, вместо того, чтобы читать данные из маршрутизатора с помощью useRouteMatch из react-router-dom, вы ожидаете, что они будут поступать из props. Вы можете сделать так:

const match = useRouteMatch()
const slug = props.slug || match.params.slug

Помимо самого определения маршрута, я думаю, что хороший способ перенести эту часть — это абстрагироваться от всего, связанного с маршрутизатором, на компоненты и перехватчики, чтоб при переключении на Next необходимо было провести только их обновления. Например, у вас есть компонент ссылки, который является оболочкой Link из react-router-dom, поэтому просто нужно обновить этот компонент с помощью next/link. То же самое и с useRouter и тому подобное.

Обратите внимание, на указания Next по миграции с react-router.

Разделение кода

И снова у Next есть решение для ручного разделения кода, оно называется next/dynamic, а у CRA — нет. Насколько я могу судить, отраслевым стандартом является @loadable /component (смотрите документацию Next). Обе библиотеки работают в основном одинаково, поэтому миграция должна быть выполнена с помощью нескольких операций поиска и замены:

- import loadable from '@loadable/component'
+ import dynamic from 'next/dynamic' - const MyComponent = loadable(() => import('./MyComponent'))
+ const MyComponent = dynamic(() => import('./MyComponent'))

Стилизация

CRA имеет встроенную поддержку CSS . Это означает, что вы можете импортировать файл CSS внутри компонента React, и CRA без проблем объединит CSS. К сожалению, Next не выходит за рамки глобальных таблиц стилей. Единственное место, где Next позволяет импортировать таблицы стилей, — это файл _app.js. Так что, если ваша кодовая база повсюду использует файлы CSS, вас ждет болезненная миграция (о чем, по сути, и говорится в документации).

Простой, но грязный выход — импортировать все ваши файлы CSS внутри _app.js, но такой вид нарушает разделение задач, поскольку ваши компоненты больше не отвечают за свои собственные стили. Если вы в конечном итоге удаляете компонент, не забудьте удалить его импортированные стили в файлах _app.js. В целом – это не очень хорошо.

Лучшим подходом будет миграция. К счастью, обе системы поддерживают модули CSS, поэтому можно было бы вручную преобразовать каждый файл CSS в модуль CSS. Другой подход — переместить слой стилей в решение CSS-in-JS, такое как styled-components, Fela или что-то еще. В любом случае, это будет ручная миграция, и это будет обременительно. Безусловно, самая сложная часть.

CSR / SSR

Поскольку CRA не имеет рендеринга на стороне сервера (SSR) и использует только рендеринг на стороне клиента (CSR), легко создать код, который в Next работать не будет (во время предварительного рендеринга). Например, доступ к API-интерфейсам браузера в процессе рендеринга (например window, localStorageи т.п.) или инициализация состояний с использованием информации, специфичной для клиента, вместо того, чтобы делать это при монтировании.

В этой части глубокое знание кодовой базы поможет сделать вещи дружественными к SSR. Это должно быть относительно легко сделать, и хороший набор тестов поможет выявить случаи, когда Next не удается выполнить предварительный рендеринг страницы. Более жесткий подход — запустить next build и смотреть, где он потерпит неудачу.

Проверка кода на ошибки (линтинг)

И Next, и CRA поставляются со встроенным линтингом как часть среды разработки и этапа сборки. К сожалению, конфигурация не совсем та. К счастью, линтинг CRA немного более строгий, чем Next, поэтому миграция не должна быть слишком сложной. Я подозреваю, что наоборот — сложнее.

Возможно, вы захотите отказаться от правила @next/next/no-img-element, потому что оно предполагает, что каждое изображение будет написано с использованием next/image, что а) кажется ужасно догматичным и б) нереалистичным для миграции.

{ "extends": "next", "rules": { "@next/next/no-img-element": "off" }
}

Запуск обеих систем

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

CRA использует единственную точку входа (обычно src/index.js), тогда как Next полагается на каталог pages, поэтому у них нету конфликта. CRA проигнорирует pages, а Next проигнорирует входной файл.

Если вы абстрагировали на хуки и компоненты все, что касается маршрутизации и управления заголовками, вы можете использовать переменную среды в указанных компонентах, чтобы использовать правильные библиотеки. Небольшое подтверждение концепции (не проверено, будьте осторожны):

import NextLink from 'next/link'
import { RRLink } from 'react-router-dom' // See: https://nextjs.org/docs/basic-features/environment-variables
// See: https://create-react-app.dev/docs/adding-custom-environment-variables
const FRAMEWORK = process.env.NEXT_PUBLIC_FRAMEWORK || process.env.REACT_APP_FRAMEWORK const Link = props => { return FRAMEWORK === 'next' ? ( <NextLink href={props.to} passHref> <a>{props.children}</a> </NextLink> ) : ( <RRLink to={props.to}>{props.children}</RRLink> )
} export default Link

Таким образом, вы можете запустить REACT_APP_FRAMEWORK=cra react-scripts build и развернуть его в производственной среде, пока вы медленно переносите свою кодовую базу на Next. Вы можете делать промежуточные / бета-сборки, NEXT_PUBLIC_FRAMEWORK=next next build пока не завершите миграцию.

Если у вас была конфигурация ESLint для CRA, вам может потребоваться сделать файл JavaScript вместо JSON, а также передать и использовать в нем переменную среды, чтобы вы могли выбрать правильную конфигурацию.

Подведение итогов

Я не собираюсь врать: это потребует времени и усилий и не будет безболезненным. Хотя обе структуры имеют много общего, они также принципиально различаются подходом к рендерингу (что является своего рода преимуществом Next), поэтому многое придется обновить. Самая раздражающая часть определенно — это миграция CSS, если ваше приложение CRA использует CSS.

Обязательно прочтите руководство Миграция на Next с CRA, поскольку оно содержит много полезной информации о том, как перейти с одной системы на другую, которую я не рассматривал в этой статье.

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

Автор: Kitty Giraudel

Источник: kittygiraudel.com

Редакция: Команда webformyself.

Читайте нас в Telegram, VK, Яндекс.Дзен