От Create-React-App к Next
Есть много причин, по которым вы можете захотеть перейти на Next с приложения CRA. Next обеспечивает рендеринг на стороне сервера (SSR) и даже инкрементную статическую регенерацию (ISR) при размещении на Vercel. Это всеобъемлющая структура со встроенной маршрутизацией, оптимизацией изображений, средой разработки и многим другим.
Этот пост представляет собой подробное руководство по переходу с CRA на Next. Вот что мы рассмотрим:
Шаблон HTML
Содержание заголовка
React JS. Основы
Изучите основы ReactJS на практическом примере по созданию учебного веб-приложения
Получить курс сейчас!
Маршрутизация
Разделение кода
Стилизация
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, который объявляет все ваши маршруты и какой компонент визуализировать для каждого маршрута. Например:
JavaScript 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
123456789101112131415161718 | 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, которые будут выглядеть так:
JavaScript // pages/index.js import HomePage from ‘../components/HomePage’ export default HomePage
1234 | // pages/index.jsimport HomePage from ‘../components/HomePage’ export default HomePage |
JavaScript // 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
1234567891011121314151617 | // pages/post/[slug]/index.jsimport 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. Вы можете сделать так:
JavaScript const match = useRouteMatch() const slug = props.slug || match.params.slug
12 | const match = useRouteMatch()const slug = props.slug || match.params.slug |
Помимо самого определения маршрута, я думаю, что хороший способ перенести эту часть — это абстрагироваться от всего, связанного с маршрутизатором, на компоненты и перехватчики, чтоб при переключении на Next необходимо было провести только их обновления. Например, у вас есть компонент ссылки, который является оболочкой Link из react-router-dom, поэтому просто нужно обновить этот компонент с помощью next/link. То же самое и с useRouter и тому подобное.
React JS. Основы
Изучите основы ReactJS на практическом примере по созданию учебного веб-приложения
Получить курс сейчас!
Обратите внимание, на указания Next по миграции с react-router.
Разделение кода
И снова у Next есть решение для ручного разделения кода, оно называется next/dynamic, а у CRA — нет. Насколько я могу судить, отраслевым стандартом является @loadable /component (смотрите документацию Next). Обе библиотеки работают в основном одинаково, поэтому миграция должна быть выполнена с помощью нескольких операций поиска и замены:
JavaScript – import loadable from ‘@loadable/component’ + import dynamic from ‘next/dynamic’ – const MyComponent = loadable(() => import(‘./MyComponent’)) + const MyComponent = dynamic(() => import(‘./MyComponent’))
12345 | – 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, что а) кажется ужасно догматичным и б) нереалистичным для миграции.
JavaScript { “extends”: “next”, “rules”: { “@next/next/no-img-element”: “off” } }
123456 | { “extends”: “next”, “rules”: { “@next/next/no-img-element”: “off” }} |
Запуск обеих систем
Одна вещь, которую я понял только после того, как завершил миграцию, заключается в том, что вы можете запустить обе системы на одной кодовой базе с минимальными усилиями, если вы не можете выполнить миграцию за один раз.
CRA использует единственную точку входа (обычно src/index.js), тогда как Next полагается на каталог pages, поэтому у них нету конфликта. CRA проигнорирует pages, а Next проигнорирует входной файл.
Если вы абстрагировали на хуки и компоненты все, что касается маршрутизации и управления заголовками, вы можете использовать переменную среды в указанных компонентах, чтобы использовать правильные библиотеки. Небольшое подтверждение концепции (не проверено, будьте осторожны):
JavaScript 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
12345678910111213141516171819 | 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-variablesconst 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
Источник: webformyself.com