В данной небольшой заметке я бы хотел показать, как можно достаточно быстро развернуть и настроить проект на NextJS 11
Штатным и самым быстрым способом создания проекта является использование штатной утилиты create-next-app, которая, по аналогии со всем известной CRA создаст проект за считанные секунды.
Я же хочу показать другой путь — чуть более сложный, но позволяющий (через некоторое количество ручной работы) намного лучше понять, из чего проект строится и как настраивается, как устанавливается и настраивается компилятор TypeScript и линтер ESLint.
Немного о фреймворке NextJS
NextJS (https://nextjs.org/) — это javascript-фреймворк для создания универсальных веб-приложений (таких, которые могут рендерить html как на клиентской стороне, так и на серверной). Что он дает?
-
Простой, интуитивно понятный роутинг по страницам приложения, основанный на файлах
-
Поддержку SSR — server-side rendering, что позволяет разгрузить клиентские устройства и каналы связи ценой нагрузки на сервер. Клиенты на слабых устройствах (в том числе, и в особенности — мобильных) и клиенты на слабых и нестабильных каналах связи получают намного более высокий user experience за счет в разы более быстрого отображения контента
-
Поддержку SSG — static site generation, что позволяет много всего интересного: корректно индексировать контент сайта без танцев с бубном вокруг nginx, эффективно кэшировать данные и пользоваться сетями доставки контента (CDN)
-
Поддержку фичей Webpack 5 (который во фреймворке используется как бортовой сборщик)
-
Возможность в рамках одного проекта создавать как фронтендовую часть, так и бэкендовую (за счет создания api-эндпоинтов)
-
Поддержку TypeScript «из коробки»
-
Оптимизацию изображений (и использование изображений как React-компонентов)
-
CSS Modules, Sass/SCSS (через установку препроцессора)
-
и много всего интересного, о чем можно прочитать на официальном сайте и в документации
Результаты (с более-менее последовательной и соответствующей тексту пошаговой разбивкой в виде коммитов) — в репозитории
Создаем проект, устанавливаем react и next
Для работы NextJS требует Node.js версии 12.0 и выше, но я рекомендую использовать релиз Node.js 14.17 как наиболее стабильный и вообще LTS.
Первым шагом необходимо инициализировать проект и установить фреймворки
mkdir nextjs-app && cd nextjs-app npm init -y npm i react react-dom next
После установки файл package.json проекта будет выглядеть примерно так:
{ "name": "nextjs-app", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "next": "^11.0.1", "react": "^17.0.2", "react-dom": "^17.0.2" } }
Далее, необходимо в секции scripts указать все скрипты, необходимые для проекта — запуска dev-сервера, сборки, запуска собранного проекта и запуска линтера (небольшой тюннинг линтера будет показан чуть позже):
"scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint" }
Если вдруг у вас (как и у меня) порт по умолчанию (3000) перманентно чем-то занят, можно переопределить его ключом -p:
"scripts": { "dev": "next dev -p 9993", "build": "next build", "start": "next start", "lint": "next lint" }
Базовая преднастройка закончена, теперь пришло время установить и настроить typescript
TypeScript
Устанавливаем TypeScript и типы для React и его DOM
npm i -D typescript @types/react @types/react-dom
Далее необходимо в корне проекта создать файл конфигурации компилятора — tsconfig.json
Я использую следующую конфигурацию (стоит ли ее использовать — вопрос вкуса):
{ "compilerOptions": { "target": "es5", "lib": [ "dom", "dom.iterable", "esnext" ], "allowJs": false, "skipLibCheck": true, "strict": true, "strictPropertyInitialization": false, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve" }, "include": [ "**/*.ts", "**/*.tsx" ], "exclude": [ "node_modules" ] }
Немного про опции файла конфигурации
-
target— целевая версия ECMAScript. Для поддержки более старых браузеров можно использовать версию ES5 (как в примере). Подробнее -
lib— набор библиотек определений типов, нужных для работы приложения. Наше приложение работает в браузере, поэтому нам нужны dom (основные типы DOM браузера), dom.iterable (итерационные типы DOM) и esnext (современные API ECMAScript). Подробнее -
allowJs— разрешен ли импорт JS-файлов в TS- и TSX-файлы. Подробнее -
skipLibCheck— пропускать ли полную проверку *.d.ts-файлов (файлов определений). Еслиtrue, компилятор будет проверять только библиотеки, непосредственно импортируемые в проекте. Подробнее -
strict— включает пачку режимов строгой проверки приложения. После включения можно прицельно отключить те проверки, которые в данном проекте не нужны. Подробнее -
strictPropertyInitialization— отключает проверку объявленного, но не установленного в конструкторе класса свойства. Подробнее -
forceConsistentCasingInFileNames— включает форсированную чувствительность к регистру имени файла. Актуально, если разработчики работают в разных операционных системах. Подробнее -
noEmit— не создавать результирующие файлы компиляции. У нас итоговой сборкой рулит NextJS и Webpack, поэтому промежуточные файлы нам не нужны. Подробнее -
esModuleInterop— позволяет импортировать файлы по стандартам ES6. Подробнее -
module— используемая модульная система. Подробнее -
moduleResolution— используемая стратегия разрешения импортов. Подробнее -
resolveJsonModule— разрешен ли импорт JSON-файлов. Подробнее -
isolatedModules— нужно ли компилятору обрабатывать каждый файл как изолированный модуль. Если в файле не хватает импортов и определений (или по какой-то еще причине компилятор не будет понимать, что это за класс/тип/ ит.д.) — при компиляции будет выдаваться ошибка. Подробнее -
jsx— какой стратегии будет придерживаться компилятор, когда встретит JSX-код. Он, например, может пытаться заменять JSX на эквивалентные конструкции вида React.createElement или, как в нашем случае — выдавать JSX-код без изменений. Подробнее -
include— какие файлы будут использоваться при компиляции -
exclude— а какие — не будут
Конфигурация NextJS, структура директорий и запуск
После настройки компилятора самое время перейти к донастройке проекта, первому роуту и первому запуску
Для начала в корне проекта создадим файл определений — next-env.d.ts, содержащий референсы на определения типов NextJS
/// <reference types="next" /> /// <reference types="next/types/global" /> /// <reference types="next/image-types/global" />
и конфигурационный файл — next.config.js
module.exports = { reactStrictMode: true, }
в котором мы указываем, что React должен находиться в строгом режиме (в этом режиме любые небезопасные и некорректные с точки зрения React моменты вроде сайд-эффектов будут считаться ошибками)
Теперь необходимо воссоздать для NextJS его привычную структуру директорий. По умолчанию он требует наличия директории pages/ в корне проекта. Данная директория используется для работы роутера: каждый файл в ней является обработчиком какого-то роута (файл index.tsx, например, будет индексным файлом и отрабатывать на роуте /).
Директория pages в корне проекта — это удобно, но только пока проект пуст. Постепенно к ней добавится куча других директорий — components, utils, helpers и прочего. Их нельзя прятать внутрь директории pages — там NextJS ожидает увидеть только обработчики, и любой файл/директорию будет интерпретировать именно так, рождая ошибки. Поэтому NextJS позволяет создать (но при работе create-next-app, что характерно, сам не создает) директорию src и в нее уже спрятать весь код приложения. Сделаем именно так:
mkdir -p src/pages
В директории pages создадим файл _app.tsx (дефолтный компонент приложения). В нем мы создаем базовую структуру компонента приложения и используем встроенный компонент NextJS — Head, чтобы передать title в заголовок страницы
import { AppProps } from 'next/dist/next-server/lib/router/router'; import React from 'react'; import Head from 'next/head'; const MyApp = ({ Component, pageProps }: AppProps): JSX.Element => { return ( <> <Head> <title>NextJS App From Scratch</title> </Head> <Component {...pageProps} /> </> ); }; export default MyApp;
А теперь — создаем индексный модуль index.tsx с компонентом домашней страницы
const Home = (): JSX.Element => { return ( <div> Hello, NextJS! </div> ); }; export default Home;
Теперь можно запустить проект командой
npm run dev
и радоваться «Hello, NextJS!» в браузере
Установка, настройка и запуск линтера
Линтинг — это хорошо. Линтинг позволяет нам не забывать про импорты, типы и точки с запятой. Давайте настроим линтинг.
Под капотом NextJS использует в качестве линтера, как это ни странно, ESLint. Для корректной работы ESlint с typescript-кодом необходимо установить несколько плагинов и создать файл конфигурации .eslintrc
Для начала — установка плагинов и парсеров
npm i -D eslint eslint-config-next npm i -D @typescript-eslint/eslint-plugin @typescript-eslint/parser
И теперь сконфигурируем его
{ "root": true, "parser": "@typescript-eslint/parser", "plugins": [ "@typescript-eslint" ], "rules": { "semi": "off", "@typescript-eslint/semi": [ "error" ], "@typescript-eslint/no-empty-interface": [ "error", { "allowSingleExtends": true } ] }, "extends": [ "next", "next/core-web-vitals", "eslint:recommended", "plugin:@next/next/recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended" ] }
Здесь мы устанавливаем в качестве парсера typescript-eslint/parser, в правилах отключаем стандартную обработку точек с запятой и назначаем свою (мне нравится, когда линтер ругается на отсутствие точки с запятой с помощью error, но вы можете поменять typescript-eslint/semi на warn, например). Кроме того, в секции extends мы подключаем много разных конфигураций как из NextJS, так и из плагинов — так мы будем получать рекомендации по созданию качественного кода. На данном этапе IDE (особенно если у вас VSCode, как у меня) уже должна отображать результаты линтинга корректно.
Для запуска проверки проекта линтером (например, перед сборкой) есть два пути: можно пользоваться штатной оберткой next lint (которая есть просто запуск eslint с некоторыми предопределенными в глубинах NextJS настройками), либо — запускать eslint напрямую, указав директорию, в которой он должен анализировать код. Я предпочитаю второй вариант (одной из причин является то, что next lint по умолчанию проверяет только директории pages/, components/ и lib/ проект, а чтобы добавить к ним, например, utils/, необходимо писать длинную портянку ключей next lint -d pages -d utils ….)
Посему, необходимо обновить скрипт линтинга в файле package.json на следующий:
"scripts": { "dev": "next dev -p 9993", "build": "next build", "start": "next start", "lint": "eslint src/**/*.{ts,tsx}" },
Этим мы говорим, что хотим натравить eslint на все директории внутри src/, в которых лежат файлы ts и tsx.
После этого запуск npm run lint будет выводить нам все ошибки во всех typescript-файлах проекта
Настройка использования SVG
По умолчанию NextJS умеет работать с изображениями, как с компонентами. Что, надо признать, достаточно удобно. Однако, если возникнет необходимость использовать векторные изображения в формате SVG, потребуется некоторый тюннинг конфигурационных файлов. Сделаем это загодя.
Благодаря Webpack в качестве бортового сборщика подключение загрузчика SVG не представляет серьезной проблемы. Сначала необходимо установить пакет загрузчика в dev-зависимости:
npm i -D @svgr/webpack
и затем — добавить webpack-правило для этого загрузчика в конфигурационный файл сборщика (next.config.js)
module.exports = { reactStrictMode: true, webpack(config, options) { config.module.rules.push({ test: /\.svg$/i, issuer: { and: [/\.(ts)x?$/] }, use: [ { loader: "@svgr/webpack", options: { svgoConfig: { plugins: [{ removeViewBox: false }] }, }, }, ], }); return config; } }
Если бы мы использовали JS вместо TypeScript, на этом настройка SVG бы завершилась. Но нет, за типизацию надо платить.
Для того, чтобы компилятор подтягивал к компоненту SVG-изображения корректные типы, необходимо внести изменения в файл определений проекта — next-env.d.ts. И в NextJS 10 мы бы так и сделали. Но в 11 версии данный файл стал автогенерируемым и сборщик перетирает его каждый раз при сборке, что несколько усложняет дело. Нужно создавать отдельный файл определений.
Создаем в корне проекта директорию @types, а в ней — файл images.d.ts следующего содержания:
declare module "*.svg" { const component: React.FC<React.SVGProps<SVGAElement>>; export default component; }
Этим мы сообщаем компилятору, что модули с расширением *.svg — это не объекты типа any (как в типах по-умолчанию), а вполне себе функциональные React-компоненты.
После этого компилятор подтянет для компонентов-изображений корректный тип
Итоги
Следуя данной заметке, можно достаточно быстро (ну, второй раз выходит и правда быстро!) создать полностью настроенный NextJS-проект с подключенным и настроенным TypeScript, реализованной поддержкой SVG и линтингом.
Спасибо за внимание!
ссылка на оригинал статьи https://habr.com/ru/articles/572230/
Добавить комментарий