Привет, друзья!
Хочу поделиться с вами заметками о Next.js
(надеюсь, кому-нибудь пригодится).
Next.js
— это основанный на React
фреймворк, предназначенный для разработки веб-приложений, обладающих функционалом, выходящим за рамки SPA
, т.е. так называемых одностраничных приложений.
Как известно, основным недостатком SPA
являются проблемы с индексацией страниц таких приложений поисковыми роботами, что негативно влияет на SEO
.
Впрочем, по моим личным наблюдениям, в последнее время ситуация стала меняться к лучшему, по крайней мере, страницы моего небольшого SPA-PWA-приложения
нормально индексируются.
Кроме того, существуют специальные инструменты, такие как react-snap
, позволяющие превратить React-SPA
в многостраничник путем предварительного рендеринга приложения в статическую разметку. Метаинформацию же можно встраивать в head
с помощью таких утилит, как react-helmet
. Однако Next.js
существенно упрощает процесс разработки многостраничных и гибридных приложений (последнего невозможно добиться с помощью того же react-snap
). Он также предоставляет множество других интересных возможностей.
Обратите внимание: данная статья предполагает, что вы обладаете некоторым опытом работы с React
. Также обратите внимание, что заметки не сделают вас специалистом по Next.js
, но позволят получить о нем исчерпывающее представление.
Заметки состоят из 2 частей. Это часть номер два.
Ремарка: ссылки ниже могут оказаться нерабочими из-за особенностей парсинга MD
хабровским редактором.
Продвинутые возможности
Динамическая маршрутизация
Next.js
поддерживает динамический import
. Он позволяет загружать модули по необходимости. Это также работает с SSR
.
В следующем примере мы реализуем неточный (fuzzy) поиск с помощью fuse.js
, загружая этот модуль динамически после того, как пользователь начинает вводить данные в поле для поиска:
import { useState } from 'react'
const names = ['John', 'Jane', 'Alice', 'Bob', 'Harry']
export default function Page() {
const [results, setResults] = useState([])
return (
<div>
<input
type="text"
placeholder="Поиск"
onChange={async ({ currentTarget: { value } }) => {
// загружаем модуль динамически
const Fuse = (await import('fuse.js')).default
const fuse = new Fuse(names)
setResults(fuse.search(value))
}}
/>
<pre>Результаты поиска: {JSON.stringify(results, null, 2)}</pre>
</div>
)
}
Динамический импорт — это способ разделения кода на части (code splitting), позволяющий загружать только тот код, который фактически используется на текущей странице.
Для динамического импорта React-компонентов
рекомендуется использовать next/dynamic
.
Обычное использование
В следующем примере мы динамически загружаем модель ../components/Hello
:
import dynamic from 'next/dynamic'
import Header from '../components/Header'
const DynamicHello = dynamic(() => import('../components/Hello'))
export default function Home() {
return (
<div>
<Header />
<DynamicHello />
<h1>Главная страница</h1>
</div>
)
}
Динамический импорт именованного компонента
Предположим, что у нас имеется такой компонент:
// components/Hello.js
export const Hello = () => <p>Привет!</p>
Для его динамического импорта его необходимо вернуть из Promise
, возвращаемого import
:
import dynamic from 'next/dynamic'
import Header from '../components/Header'
const DynamicHello = dynamic(() =>
import('../components/Hello')
.then((module) => module.Hello)
)
export default function Home() {
return (
<div>
<Header />
<DynamicHello />
<h1>Главная страница</h1>
</div>
)
}
Индикатор загрузки
Для рендеринга состояния загрузки можно использовать настройку loading
:
import dynamic from 'next/dynamic'
import Header from '../components/Header'
const DynamicHelloWithCustomLoading = dynamic(
() => import('../components/Hello'),
{ loading: () => <p>Загрузка...</p> }
)
export default function Home() {
return (
<div>
<Header />
<DynamicHelloWithCustomLoading />
<h1>Главная страница</h1>
</div>
)
}
Отключение SSR
Для отключения импорта модуля на стороне сервера можно использовать настройку ssr
:
import dynamic from 'next/dynamic'
import Header from '../components/Header'
const DynamicHelloWithNoSSR = dynamic(
() => import('../components/Hello'),
{ ssr: false }
)
export default function Home() {
return (
<div>
<Header />
<DynamicHelloWithNoSSR />
<h1>Главная страница</h1>
</div>
)
}
Настройка suspense
позволяет загружать компонент лениво подобно тому, как это делает сочетание React.lazy
и <Suspense>
. Обратите внимание, что это работает только на клиенте или на сервере с fallback
. Полная поддержка SSR
в конкурентном режиме находится в процессе разработки:
import dynamic from 'next/dynamic'
import Header from '../components/Header'
const DynamicLazyHello = dynamic(() => import('../components/Hello'), { suspense: true })
export default function Home() {
return (
<div>
<Header />
<DynamicLazyHello />
<h1>Главная страница</h1>
</div>
)
}
Автоматическая статическая оптимизация
Если на странице присутствуют getServerSideProps
, Next.js
будет рендерить страницу при каждом запросе (рендеринг на стороне сервера).
В противном случае, Next.js
выполняет предварительный рендеринг страницы в статическую разметку.
В процессе предварительного рендеринга объект роутера query
будет пустым. После гидратации Next.js
запускает обновление приложения для заполнения query
параметрами маршрутизации.
next build
генерирует HTML-файлы для статически оптимизированных страниц. Например, результатом для pages/about.js
будет:
.next/server/pages/about.html
А если на эту страницу добавить getServerSideProps
, то результат будет таким:
.next/server/pages/about.js
Заметки
- при наличии кастомного
App
оптимизация для страниц без статической генерации отключается - при наличии кастомного
Document
сgetInitialProps
убедитесь в определенииctx.req
перед рендерингом страницы на стороне сервера. Для страниц, которые рендерятся предварительно,ctx.req
будет иметь значениеundefined
Экспорт статической разметки
next export
позволяет экспортировать приложение в статический HTML
. Такое приложение может запускаться без сервера.
Такое приложение поддерживает почти все возможности, предоставляемые Next.js
, включая динамическую маршрутизацию, предварительный запрос и получение данных и динамический импорт.
next export
работает за счет предварительного рендеринга всех страниц в HTML
. Для динамических роутов страница может экспортировать функцию getStaticPaths
, чтобы Next.js
мог определить, какие страницы следует рендерить для данного роута.
next export
рассчитан для приложений без рендеринга на стороне сервера или инкрементального рендеринга. Такие фичи, как инкрементальная статическая генерация и регенерация при использовании next export
будут отключены.
Для экспорта приложения в статику после разработки приложения следует выполнить команду:
next build && next export
Статическая версия приложения будет записана в директорию out
.
По умолчанию next export
не требует настройки. HTML-файлы генерируются для всех страниц, определенных в директории pages
. Для более продвинутых сценариев можно определить параметр exportPathMap
в next.config.js
для настройки генерируемых страниц.
Заметки
- с помощью
next export
мы генерируем HTML-версию приложения. Во время сборки для каждой страницы вызывается функцияgetStaticProps
, результаты выполнения которой передаются компоненту страницы в виде пропов. ВместоgetStaticProps
можно использовать старый интерфейсgetInitialProps
, но это имеет некоторые ограничения - режим
fallback: true
вgetStaticProps
не поддерживается - интерфейс маршрутизации не поддерживается
-
getServerSideProps
не поддерживается - локализованный роутинг не поддерживается
- дефолтный индикатор загрузки компонента
Image
не поддерживается (другие настройкиloader
поддерживаются)
Абсолютный импорт и синонимы путей модулей
Next.js
поддерживает настройки baseUrl
и paths
в файлах tsconfig.json
и jsconfig.json
.
baseUrl
позволяет импортировать модули напрямую из корневой директории.
Пример:
// `tsconfig.json` или `jsconfig.json`
{
"compilerOptions": {
"baseUrl": "."
}
}
// components/button.js
export const Button = () => <button>Click Me</button>
// pages/index.js
import { Button } from 'components/button'
export default function Index() {
return (
<>
<h1>Привет, народ!</h1>
<Button />
</>
)
}
paths
позволяет определять синонимы для путей модулей. Например:
// `tsconfig.json` или `jsconfig.json`
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["components/*"]
}
}
}
// components/button.js
export const Button = () => <button>Click Me</button>
// pages/index.js
import { Button } from '@/button'
export default function Index() {
return (
<>
<h1>Привет, народ!</h1>
<Button />
</>
)
}
Кастомный сервер
Пример кастомного сервера:
// server.js
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer((req, res) => {
// `true` обеспечивает разбор строки запроса
const parseUrl = parse(req.url, true)
const { pathname, query } = parseUrl
switch (pathname) {
case '/a':
return app.render(req, res, '/a', query)
case '/b':
return app.render(req, res, '/b', query)
default:
handle(req, res, parsedUrl)
}
}).listen(3000, (err) => {
if (err) throw err
console.log('Запущен по адресу: http://localhost:3000')
})
})
Для запуска этого сервера необходимо обновить раздел scripts
в файле package.json
следующим образом:
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
}
Кастомный сервер использует такой импорт для подключения к Next.js-приложению
:
const next = require('next')
const app = next({})
next
— это функция, которая принимает объект со следующими настройками:
-
dev: boolean
— запуск сервера в режиме для разработки -
dir: string
— корневая директория проекта (по умолчанию.
) -
quiet: boolean
— еслиtrue
, ошибки сервера не отображаются (по умолчаниюfalse
) -
conf: object
— такой же объект, что используется вnext.config.js
После этого app
может использоваться для обработки входящих запросов.
По умолчанию Next.js
обслуживает каждый файл в директории pages
по пути, совпадающему с названием файла. При использовании кастомного сервера это может привести к возвращению одинакового контента для разных путей.
Для отключения маршрутизации, основанной на файлах в pages
, используется настройка useFileSystemPublicRoutes
в next.config.js
:
module.exports = {
useFileSystemPublicRoutes: false
}
Обратите внимание: это отключает роутинг по названиям файлов только для SSR
. Маршрутизация на стороне клиента по-прежнему будет иметь доступ к этим путям.
Кастомное приложение
Next.js
использует компонент App
для инициализации страниц. Кастомизация этого компонента позволяет делать следующее:
- распределять макет (layout) между страницами
- сохранять состояние при переключении между страницами
- обрабатывать ошибки с помощью
componentDidCatch
- внедрять в страницы дополнительные данные
- добавлять глобальные стили
Для перезаписи дефолтного App
необходимо создать файл pages/_app.js
следующего содержания:
// pages/_app.js
import App from 'next/app'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
MyApp.getInitialProps = async (appContext) => {
const appProps = await App.getInitialProps(appContext)
return { ...appProps }
}
export default MyApp
Проп Component
— это активная страница. pageProps
— это объект с начальными пропами, которые предварительно загружаются для страницы с помощью одного из методов для получения данных, в противном случае, данный объект будет пустым.
Заметки
- при добавлении кастомного
App
(создании файлаpages/_app.js
) при запущенном приложении может потребоваться перезапуск сервера для разработки - добавление кастомного
getInitialProps
вApp
отключает автоматическую статическую оптимизацию страниц без статической генерации - при добавлении
getInitialProps
в кастомное приложение следуетimport App from 'next/app'
, вызватьApp.getInitialProps(appContext)
внутриgetInitialProps
и объединить объект ответа с возвращаемым значением - в настоящее время
App
не поддерживает такие методы для получения данных, какgetStaticProps
илиgetServerSideProps
Кастомные страницы ошибок
Next.js
предоставляет дефолтные страницы для ошибок 404
и 500
. Для кастомизации этих страниц используются файлы pages/404.js
и pages/500.js
, соответственно:
// pages/404.js
export default function Custom404() {
return <h1>404 - Страница не найдена</h1>
}
// pages/500.js
export default function Custom500() {
return <h1>500 - Ошибка на сервере</h1>
}
Для получения данных во время сборки на этих страницах может использоваться getStaticProps
.
Ошибки 500 обслуживаются как на стороне клиента, так и на стороне сервера компонентом Error
. Для перезаписи этого компонента необходимо создать файл pages/_error.js
и добавить в него код вроде следующего:
export default function Error({ statusCode }) {
return (
<p>
{statusCode
? `На сервере возникла ошибка ${statusCode}`
: `Ошибка возникла на клиенте`}
</p>
)
}
Error.getInitialProps = ({ res, err }) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404
return { statusCode }
}
Обратите внимание: pages/_error.js
используется только в продакшне. В режиме для разработки ошибка возвращается вместе со стеком вызовов для обеспечения возможности определения места ее возникновения.
Для рендеринга встроенной страницы ошибки также можно использовать компонент Error
:
import Error from 'next/error'
export async function getServerSideProps() {
const res = await fetch('https://api.github.com/repos/harryheman/react-total')
const errorCode = res.ok ? false : res.statusCode
const data = await res.json()
return {
props: {
errorCode,
stars: data.stergazers_count
}
}
}
export default function Page({ errorCode, stars }) {
if (errorCode) {
return <Error statusCode={errorCode} />
}
return <div>Количество звезд: {stars}</div>
}
Компонент Error
также принимает проп title
для передачи текстового сообщения в дополнение к статус-коду.
Директория src
Страницы приложения могут помещаться в директорию src/pages
вместо корневой директории pages
.
Заметки
-
src/pages
будет игнорироваться при наличииpages
- файлы с настройками, такие как
next.config.js
иtsconfig.json
должны находиться в корневой директории проекта. Тоже самое касается директорииpublic
Локализованная маршрутизация (интернационализация)
Next.js
имеет встроенную поддержку локализованного роутинга. Достаточно указать список локалей, дефолтную локаль и привязанные к домену локали.
Поддержка роутинга i18n
означает интеграцию с такими библиотеками, как react-intl
, react-i18next
, lingui
, rosetta
, next-intl
и др.
Начало работы
Для начала работы необходимо настроить i18n
в файле next.config.js
. Идентификатор локали выглядит как язык-регион-скрипт
, например:
-
en-US
— американский английский -
nl-NL
— нидерландский (голландский) -
nl
— нидерландский без учета региона
// next.config.js
module.exports = {
i18n: {
// Локали, поддерживаемые приложением
locales: ['en-US', 'fr', 'nl-NL'],
// Дефолтная локаль, которая будет использоваться
// при посещении пользователем пути без префикса,
// например, `/hello`
defaultLocale: 'en-US',
// Список доменов и привязанных к ним локалей
// (требуется только в случае настройки маршрутизации на основе доменов)
// Обратите внимание: поддомены должны включаться в значение домена,
// например, `fr.example.com`
domains: [
{
domain: 'example.com',
defaultLocale: 'en-US'
},
{
domain: 'example.nl',
defaultLocale: 'nl-NL'
},
{
domain: 'example.fr',
defaultLocale: 'fr',
// Опциональное поле `http` может использоваться для локального тестирования
// локали, привязанной к домену (т.е. по `http` вместо `https`)
http: true
}
]
}
}
Стратегии локализации
Существует 2 стратегии локализации: маршрутизация на основе субпутей (subpaths) и роутинг на основе доменов.
Роутинг на основе субпутей
В этом случае локаль помещается в url
:
// next.config.js
module.exports = {
i18n: {
locales: ['en-US', 'fr', 'nl-NL'],
defaultLocale: 'en-US'
}
}
Здесь en-US
, fr
и nl-NL
будет доступны для перехода, а en-US
будет использоваться по умолчанию. Для страницы pages/blog
будут доступны следующие url
:
/blog
/fr/blog
/nl-nl/blog
Дефолтная локаль не имеет префикса.
Роутинг на основе доменов
В этом случае локали будут обслуживаться разными доменами:
// next.config.js
module.exports = {
i18n: {
locales: ['en-US', 'fr', 'nl-NL', 'nl-BE'],
defaultLocale: 'en-US',
domains: [
{
domain: 'example.com',
defaultLocale: 'en-US',
},
{
domain: 'example.fr',
defaultLocale: 'fr',
},
{
domain: 'example.nl',
defaultLocale: 'nl-NL',
// Список указанных локалей будет обслуживаться этим доменом
locales: ['nl-BE']
}
]
}
}
Для страницы pages/blog
будут доступны следующие url
:
example.com/blog
example.fr/blog
example.nl/blog
example.nl/nl-BE/blog
Автоматическое определение локали
Когда пользователь запускает приложение, Next.js
пытается автоматически определить его локаль на основе заголовка Accept-Language
и текущего домена. При обнаружении локали, отличающейся от дефолтной, выполняется перенаправление.
Если при посещении example.com
запрос содержит заголовок Accept-Language: fr;q=0.9
, в случае роутинга на основе домена выполняется перенаправление на example.fr
, а в случае роутинга на основе субпутей — на /fr
.
Отключение автоматического определения локали
// next.config.js
module.exports = {
i18n: {
localeDetection: false
}
}
Доступ к информации о локали
Информация о локали содержится в роутере, доступ к которому можно получить с помощью хука useRouter
. Доступны следующие свойства:
-
locale
— текущая локаль -
locales
— доступные локали -
defaultLocale
— локаль по умолчанию
В случае предварительного рендеринга страниц с помощью getStaticProps
или getServerSideProps
информация о локали содержится в контексте, передаваемом функции.
При использовании getStaticPaths
локали также содержатся в параметре context
, передаваемом функции, в свойствах locales
и defaultLocale
.
Переключение между локалями
Для переключения между локалями можно использовать next/link
или next/router
.
Для next/link
может быть указан проп locale
для переключения на другую локаль. Если такой проп не указан, используется текущая локаль:
import Link from 'next/link'
export default function IndexPage(props) {
return (
<Link href="/another" locale="fr">
<a>Перейти к `/fr/another`</a>
</Link>
)
}
При использовании next/router
локаль указывается в настройках:
import { useRouter } from 'next/router'
export default function IndexPage(props) {
const router = useRouter()
return (
<div
onClick={() => {
router.push('/another', '/another', { locale: 'fr' })
}}
>
Перейти к `/fr/another`
</div>
)
}
Обратите внимание: для переключения локали с сохранением информации, хранящейся в роутере, такой так значения динамической строки запроса или значения скрытой строки запроса, в качестве значения пропа href
можно использовать объект:
import { useRouter } from 'next/router'
const router = useRouter()
const { pathname, asPath, query } = router
// Переключаем локаль с сохранением другой информации
router.push({ pathname, query }, asPath, { locale: nextLocale })
Если href
включает локаль, автоматическое добавления префикса можно отключить:
import Link from 'next/link'
export default function IndexPage(props) {
return (
<Link href="/fr/another" locale={false}>
<a>Перейти к `/fr/another`</a>
</Link>
)
}
Заметки
-
Next.js
позволяет перезаписывать значение заголовкаAccept-Language
с помощью кукиNEXT_LOCALE=локаль
. При установке такой куки,Accept-Language
будет игнорироваться. -
Next.js
автоматически добавляет атрибутlang
к тегуhtml
. Однако, он не знает о возможных вариантах страницы, поэтому добавление мета-тегаhreflang
— задача разработчика (это можно сделать с помощьюnext/head
).
Статическая генерация
Динамические роуты и getStaticProps
Для страниц, на которых используется динамическая маршрутизация с помощью getStaticProps
, все локали, которые должны быть предварительно отрендерены, должны возвращаться из getStaticPaths
. Вместе с объектом params
возвращается поле locale
, определяющее локаль для рендеринга:
// pages/blog/[slug].js
export const getStaticPaths = ({ locales }) => {
return {
paths: [
// При отсутствии `locale` генерируется только локаль по умолчанию
{ params: { slug: 'post-1' }, locale: 'en-US' },
{ params: { slug: 'post-1' }, locale: 'fr' },
],
fallback: true
}
}
Для автоматической статической оптимизации и нединамических страниц с getStaticProps
, для каждой локали генерируется отдельная версия страницы. Это может существенно повлиять на время сборки в зависимости от количества локалей, определенных в getStaticProps
.
Для решения этой проблемы следует использовать режим fallback
. Это позволяет возвращать из getStaticPaths
только самые популярные пути и локали для предварительного рендеринга во время сборки. Остальные страницы будут рендерится во время выполнения по запросу.
Если из getStaticProps
нединамической страницы вернуть notFound: true
, то соответствующий вариант страницы сгенерирован не будет:
export async function getStaticProps({ locale }) {
// Получаем посты из внешнего `API`
const res = await fetch(`https://example.com/posts?locale=${locale}`)
const posts = await res.json()
if (posts.length === 0) {
return {
notFound: true
}
}
return {
props: {
posts
}
}
}
Ограничения
-
locales
: максимум 100 локалей -
domains
: максимум 100 доменов
Заголовки безопасности
Шпаргалка и туториал по заголовкам безопасности.
Для улучшения безопасности приложения предназначена настройка headers
в next.config.js
. Данная настройка позволяет устанавливать HTTP-заголовки ответов для всех роутов в приложении:
// next.config.js
// Список заголовков
const securityHeaders = []
module.exports = {
async headers() {
return [
{
// Данные заголовки будут применяться ко всем роутам в приложении
source: '/(.*)',
headers: securityHeaders
}
]
}
}
Настройки
X-DNS-Prefetch-Control
Данный заголовок управляет предварительной загрузкой (prefetching) DNS
, позволяя браузерам заблаговременно разрешать названия доменов для внешних ссылок, изображений, CSS
, JS
и т.д. Предварительная загрузка выполняется в фоновом режиме и уменьшает время реакции на клик пользователя по ссылке:
{
key: 'X-DNS-Prefetch-Control',
value: 'on'
}
Strict-Transport-Security
Данный заголовок указывает браузеру использовать HTTPS
вместо HTTP
:
{
key: 'Strict-Transport-Security',
// 2 года
value: 'max-age=63072000; includeSubDomains; preload'
}
X-XSS-Protection
Данный заголовок предназначен для блокировки межсайтового скриптинга в старых браузерах, не поддерживающих заголовок Content-Security-Policy
:
{
key: 'X-XSS-Protection',
value: '1; mode=block'
}
X-Frame-Options
Данный заголовок определяет, может ли сайт открываться в iframe
. Он также предназначен для старых браузеров, не поддерживающих настройку frame-ancestors
заголовка CSP
:
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN'
}
Permissions-Policy
Данный заголовок позволяет определять, какие возможности и API
могут использоваться в браузере (раньше он назывался Feature-Policy
):
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()'
}
X-Content-Type-Options
Данный заголовок запрещает браузеру автоматически определять тип содержимого при отсутствии заголовка Content-Type
:
{
key: 'X-Content-Type-Options',
value: 'nosniff'
}
Referrer-Policy
Данный заголовок управляет тем, какая информация о предыдущем сайте включается в ответ:
{
key: 'Referrer-Policy',
value: 'origin-when-cross-origin'
}
Content-Security-Policy
Данный заголовок определяет разрешенные источники для загрузки скриптов, стилей, изображений, шрифтов, медиа и др.:
{
key: 'Content-Security-Policy',
value: // Политика `CSP`
}
API
Next CLI
Next CLI
позволяет запускать, собирать и экспортировать приложение.
Команда для получения списка доступных команд:
npx next -h
Пример передачи аргументов:
NODE_OPTIONS='--throw-deprecation' next
NODE_OPTIONS='-r esm' next
NODE_OPTIONS='--inspect' next
Сборка
Команда next build
создает оптимизированную производственную сборку приложения. Для каждого роута отображается:
- размер (size) — количество ресурсов, загружаемых при запросе страницы на стороне клиента
- первоначально загружаемый (first load)
JS
— количество ресурсов, загружаемых при получении страницы от сервера
Флаг --profile
позволяет включить производственный профайлинг:
next build --profile
Флаг --verbose
позволяет получить более подробный вывод:
next build --verbose
Разработка
Команда next dev
запускает приложение в режиме разработки с быстрой перезагрузкой кода, отчетами об ошибках и т.д.
По умолчанию приложение запускается по адресу http://localhost:3000
. Порт может быть изменен с помощью флага -p
:
npx next dev -p 4000
Для этого также можно использовать переменную PORT
:
PORT=4000 npx next dev
Дефолтный хост (0.0.0.0
) можно изменить с помощью флага -H
:
npx next dev -H 192.168.1.2
Продакшн
Команда next start
запускает приложение в производственном режиме. Перед этим приложение должно быть скомпилировано с помощью next build
.
Линтер
Команда next lint
запускает ESLint
для всех файлов в директориях pages
, components
и lib
. Дополнительные директории для проверки могут быть определены с помощью флага --dir
:
next lint --dir utils
Create Next App
CLI create-next-app
— это простейший способ создать болванку Next-проекта
.
npx create-next-app [app-name]
# or
yarn create next-app [app-name]
Или, если вам требуется поддержка TypeScript
:
npx create-next-app [app-name] --typescript
# or
yarn create next-app [app-name] --ts
Настройки
-
--ts | --typescript
— инициализацияTS-проекта
-
-e, --example [example-name][github-url]
— инициализация проекта на основе примера. ВURL
может быть указана любая ветка и/или поддиректория --example-path [path-to-example]
--use-npm
next/router
useRouter
Хук useRouter
позволяет получить доступ к объекту router
в любом функциональном компоненте:
import { useRouter } from 'next/router'
export const ActiveLink = ({ children, href }) => {
const router = useRouter()
const style = {
marginRight: 10,
color: router.asPath === href ? 'green' : 'blue',
}
const handleClick = (e) => {
e.preventDefault()
router.push(href)
}
return (
<a href={href} onClick={handleClick} style={style}>
{children}
</a>
)
}
Объект router
Объект router
возвращается useRouter
и withRouter
и содержит следующие свойства:
-
pathname: string
— текущий роут, путь страницы из/pages
безbasePath
илиlocale
-
query: object
— строка запроса, разобранная в объект. По умолчанию имеет значение{}
-
asPath: string
— путь (включая строку запроса), отображаемый в браузере, безbasePath
илиlocale
-
isFallback: boolean
— находится ли текущая страница в резервном режиме? -
basePath: string
— активный базовый путь -
locale: string
— активная локаль -
locales: string[]
— поддерживаемые локали -
defaultLocale: string
— дефолтная локаль -
domainsLocales: Array<{ domain, defaultLocalem locales }>
— локали для доменов -
isReady: boolean
— готовы ли поля роутера к использованию? Может использоваться только вuseEffect
-
isPreview: boolean
— находится ли приложение в режиме предварительного просмотра?
router.push
Метод router.push
предназначен для случаев, когда next/link
оказывается недостаточно.
router.push(url, as, options)
-
url: string | object
— путь для навигации -
as: string | object
— опциональный декоратор для адреса страницы при отображении в браузере -
options
— опциональный объект со следующими свойствами:-
scroll: boolean
— выполнять ли прокрутку в верхнюю часть области просмотра при переключении страницы? По умолчанию имеет значениеtrue
-
shallow: boolean
— позволяет обновлять путь текущей страницы без использованияgetStaticProps
,getServerSideProps
илиgetInitialProps
. По умолчанию имеет значениеfalse
-
locale: string
— локаль новой страницы
-
Использование
Переключение на страницу pages/about.js
:
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button className="link" onClick={() => router.push('/about')}>
О нас
</button>
)
}
Переключение на страницу pages/post/[pid].js
:
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button className="link" onClick={() => router.push('/post/abc')}>
Подробнее
</button>
)
}
Перенаправление пользователя на страницу pages/login.js
(может использоваться для реализации защищенных страниц):
import { useEffect } from 'react'
import { useRouter } from 'next/router'
// Получаем данные пользователя
const useUser = () => ({ user: null, loading: false })
export default function Page() {
const { user, loading } = useUser()
const router = useRouter()
useEffect(() => {
if (!loading && !user) {
router.push('/login')
}
}, [user, loading])
return user ? <p>Привет, {user.name}!</p> : <p>Загрузка...</p>
}
Пример использования объекта вместо строки:
import { useRouter } from 'next/router'
export default function ReadMore({ post }) {
const router = useRouter()
return (
<button
className='link'
onClick={() => {
router.push({
pathname: '/post/[pid]',
query: { pid: post.id }
})
}}
>
Подробнее
</button>
)
}
router.replace
Метод router.replace
обновляет путь текущей страницы без добавления URL
в стек history
.
router.replace(url, as, options)
Использование
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button className="link" onClick={() => router.replace('/home')}>
Главная
</button>
)
}
router.prefetch
Метод router.prefetch
позволяет выполнять предварительную загрузку страниц для ускорения навигации. Обратите внимание: next/link
выполняет предварительную загрузку страниц автоматически.
router.prefetch(url, as)
Использование
Предположим, что после авторизации мы перенаправляем пользователя на страницу профиля. Для ускорения навигации мы можем предварительно загрузить страницу профиля:
import { useCallback, useEffect } from 'react'
import { useRouter } from 'next/router'
export default function Login() {
const router = useRouter()
const handleSubmit = useCallback((e) => {
e.preventDefault()
fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
/* Данные пользователя */
}),
}).then((res) => {
// Выполняем перенаправление на предварительно загруженную страницу профиля
if (res.ok) router.push('/dashboard')
})
}, [])
useEffect(() => {
// Предварительно загружаем страницу профиля
router.prefetch('/dashboard')
}, [])
return (
<form onSubmit={handleSubmit}>
{/* Поля формы */}
<button type="submit">Войти</button>
</form>
)
}
router.beforePopState
Метод router.beforePopState
позволяет реагировать на событие popstate
перед обработкой пути роутером.
router.beforePopState(cb)
cb
— это функция, которая запускается при возникновении события popstate
. Данная функция получает объект события со следующими свойствами:
-
url: string
— путь для нового состояния (как правило, название страницы) -
as: string
—URL
, который будет отображен в браузере -
options: object
— дополнительные настройки изrouter.push
Использование
beforePopState
может использоваться для модификации запроса или принудительного обновления SSR
, как в следующем примере:
import { useEffect } from 'react'
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
useEffect(() => {
router.beforePopState(({ url, as, options }) => {
// Разрешены только указанные пути
if (as !== '/' && as !== '/other') {
// Заставляем `SSR` рендерить страницу 404
window.location.href = as
return false
}
return true
})
}, [])
return <p>Добро пожаловать!</p>
}
router.back
Данный метод позволяет вернуться к предыдущей странице. При его использовании выполняется window.history.back()
:
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button className="link" onClick={() => router.back()}>
Назад
</button>
)
}
router.reload
Данный метод перезагружает текущий URL
. При его использовании выполняется window.location.reload()
:
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button className="link" onClick={() => router.reload()}>
Обновить
</button>
)
}
router.events
Использование роутера приводит к возникновению следующих событий:
-
routeChangeStart(url, { shallow })
— возникает в начале обновления роута -
routeChangeComplete(url, { shallow })
— возникает в конце обновления роута -
routeChangeError(err, url, { shallow })
— возникает при провале или отмене обновления роута -
beforeHistoryChange(url, { shallow })
— возникает перед изменением истории браузера -
hashChangeStart(url, { shallow })
— возникает в начале изменения хеш-частиURL
(но не самогоURL
) -
hashChangeComplete(url, { shallow })
— возникает в конце изменения хеш-частиURL
(но не самогоURL
)
Обратите внимание: здесь url
— это адрес страницы, отображаемый в браузере, включая basePath
.
Использование
// pages/_app.js
import { useEffect } from 'react'
import { useRouter } from 'next/router'
export default function MyApp({ Component, pageProps }) {
const router = useRouter()
useEffect(() => {
const handleRouteChange = (url, { shallow }) => {
console.log(
`Выполняется переключение на страницу ${url} ${
shallow ? 'с' : 'без'
} поверхностной маршрутизации`
)
}
router.events.on('routeChangeStart', handleRouteChange)
// При размонтировании компонента
// отписываемся от события с помощью метода `off`
return () => {
router.events.off('routeChangeStart', handleRouteChange)
}
}, [])
return <Component {...pageProps} />
}
withRouter
Вместо useRouter
может использоваться withRouter
:
import { withRouter } from 'next/router'
function Page({ router }) {
return <p>{router.pathname}</p>
}
export default withRouter(Page)
TypeScript
import React from 'react'
import { withRouter, NextRouter } from 'next/router'
interface WithRouterProps {
router: NextRouter
}
interface MyComponentProps extends WithRouterProps {}
class MyComponent extends React.Component<MyComponentProps> {
render() {
return <p>{this.props.router.pathname}</p>
}
}
export default withRouter(MyComponent)
next/link
Компонент Link
, экспортируемый из next/link
, позволяет переключаться между страницами на стороне клиента.
Предположим, что в директории pages
содержатся следующие файлы:
pages/index.js
pages/about.js
pages/blog/[slug].js
Пример навигации по этим страницам:
import Link from 'next/link'
export default function Home() {
return (
<ul>
<li>
<Link href="/">
<a>Главная</a>
</Link>
</li>
<li>
<Link href="/about">
<a>О нас</a>
</Link>
</li>
<li>
<Link href="/blog/hello-world">
<a>Пост</a>
</Link>
</li>
</ul>
)
}
Link
принимает следующие пропы:
-
href
— путь илиURL
для навигации. Единственный обязательный проп -
as
— опциональный декоратор пути, отображаемый в адресной строке браузера -
passHref
— указываетLink
передать пропhref
дочернему компоненту. По умолчанию имеет значениеfalse
-
prefetch
— предварительная загрузка страницы в фоновом режиме. По умолчанию —true
. Предварительная загрузка страницы выполняется для любогоLink
, находящегося в области просмотра (изначально или при прокрутке). Предварительная загрузка может быть отключена с помощьюprefetch={false}
. Однако даже в этом случае предварительная загрузка выполняется при наведении курсора на ссылку. Для страниц со статической генерацией загружается JSON-файл для более быстрого переключения страницы. Предварительная загрузка выполняется только в производственном режиме -
replace
— заменяет текущее состояние истории вместо добавления новогоURL
в стек. По умолчанию —false
-
scroll
— выполнение прокрутки в верхнюю часть области просмотра. По умолчанию —true
-
shallow
— обновление пути текущей страницы без перезапускаgetStaticProps
,getServerSideProps
илиgetInitialProps
. По умолчанию —false
-
locale
— по умолчанию к пути добавляется активная локаль.locale
позволяет определить другую локаль. Когда имеет значениеfalse
, пропhref
должен включать локаль
Роут с динамическими сегментами
Пример динамического роута для pages/blog/[slug].js
:
import Link from 'next/link'
export default function Posts({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${encodeURIComponent(post.slug)}`}>
<a>{post.title}</a>
</Link>
</li>
))}
</ul>
)
}
Кастомный компонент — обертка для тега a
Если дочерним компонентом Link
является кастомный компонент, оборачивающий a
, Link
должен иметь проп passHref
. Это необходимо при использовании таких библиотек как styled-components
. Без этого тег a
не получит атрибут href
, что негативно скажется на доступности и SEO
.
import Link from 'next/link'
import styled from 'styled-components'
// Создаем кастомный компонент, оборачивающий `a`
const RedLink = styled.a`
color: red;
`
export default function NavLink({ href, name }) {
// Добавляем `passHref`
return (
<Link href={href} passHref>
<RedLink>{name}</RedLink>
</Link>
)
}
Функциональный компонент
Если дочерним компонентом Link
является функция, то кроме passHref
, необходимо обернуть ее в React.forwardRef
:
import Link from 'next/link'
import { forwardRef } from 'react'
// `onClick`, `href` и `ref` должны быть переданы DOM-элементу
const MyButton = forwardRef(({ onClick, href }, ref) => {
return (
<a href={href} onClick={onClick} ref={ref}>
Кликни
</a>
)
})
export default function Home() {
return (
<Link href="/about" passHref>
<MyButton />
</Link>
)
}
Объект URL
Link
также принимает объект URL
. Данный объект автоматически преобразуется в строку:
import Link from 'next/link'
export default function Home() {
return (
<ul>
<li>
<Link
href={{
pathname: '/about',
query: { name: 'test' },
}}
>
<a>О нас</a>
</Link>
</li>
<li>
<Link
href={{
pathname: '/blog/[slug]',
query: { slug: 'my-post' },
}}
>
<a>Пост</a>
</Link>
</li>
</ul>
)
}
В приведенном примере у нас имеются ссылки на:
- предопределенный роут:
/about?name=test
- динамический роут:
/blog/my-post
Замена URL
<Link href="/about" replace>
<a>О нас</a>
</Link>
Отключение прокрутки
<Link href="/?page=2" scroll={false}>
<a>Загрузить еще</a>
</Link>
next/image
Обязательные пропы
Компонент Image
принимает следующие обязательные пропы:
-
src
— статически импортированный файл или строка, которая может быть абсолютной ссылкой или относительным путем в зависимости от пропаloader
или настроек загрузки. При использовании ссылок на внешние ресурсы, эти ресурсы должны быть указаны в разделеdomains
файлаnext.config.js
-
width
— ширина изображения в пикселях: целое число без единицы измерения -
height
— высота изображения в пикселях: целое число без единицы измерения
Опциональные пропы
-
layout
—intrinsic | fixed | responsive | fill
. Значением по умолчанию являетсяintrinsic
-
loader
— кастомная функция для разрешенияURL
. Установка этого пропа перезаписывает настройки из разделаimages
вnext.config.js
.loader
принимает параметрыsrc
,width
иquality
import Image from 'next/image'
const myLoader = ({ src, width, quality }) => `https://example.com/${src}?w=${width}&q=${quality || 75}`
const MyImage = (props) => (
<Image
loader={myLoader}
src="me.png"
alt=""
role="presentation"
width={500}
height={500}
/>
)
-
sizes
— строка, содержащая информацию о ширине изображения на различных контрольных точках. По умолчанию имеет значение100vw
при использованииlayout="responsive"
илиlayout="fill"
-
quality
— качество оптимизированного изображения: целое число от1
до100
, где100
— лучшее качество. Значением по умолчанию является75
-
priority
— еслиtrue
, изображение будет считаться приоритетным и загружаться предварительно. Ленивая загрузка для такого изображения будет отключена -
placeholder
— заменитель изображения. Возможными значениями являютсяblur
иempty
. Значением по умолчанию являетсяempty
. Когда значением являетсяblur
, в качестве заменителя используется значение пропаblurDataURL
. Если значениемsrc
является объект из статического импорта и импортированное изображение имеет форматJPG
,PNG
,WebP
илиAVIF
,blurDataURL
заполняется автоматически
Пропы для продвинутого использования
-
objectFit
— определяет, как изображение заполняет родительский контейнер при использованииlayout="fill"
-
objectPosition
— определяет, как изображение позиционируется внутри родительского контейнера при использованииlayout="fill"
-
onLoadingComplete
— функция, которая вызывается после полной загрузки изображения и удаления заменителя -
lazyBoundary
— строка, определяющая ограничительную рамку для определения пересечения области просмотра с изображением для запуска его ленивой загрузки. По умолчанию имеет значение200px
-
unoptimized
— еслиtrue
, источник изображения будет использоваться как есть, без изменения качества, размера или формата. Значением по умолчанию являетсяfalse
Другие пропы
Любые другие пропы компонента Image
передаются дочернему элементу img
, кроме следующих:
-
style
— для стилизации изображения следует использоватьclassName
-
srcSet
— следует использовать размеры устройства -
ref
— следует использоватьonLoadingComplete
-
decoding
— всегдаasync
Настройки
Настройки для обработки изображений определяются в файле next.config.js
.
Домены
Настройка доменов для провайдеров изображений позволяет защитить приложение от атак, связанных с внедрением в изображение вредоносного кода.
module.exports = {
images: {
domains: ['assets.acme.com']
}
}
Размеры устройств
Настройка deviceSizes
позволяет определить список контрольных точек для ширины устройств потенциальных пользователей. Эти контрольные точки предназначены для предоставления подходящего изображения при использовании layout="responsive"
или layout="fill"
.
// настройки по умолчанию
module.exports = {
images: {
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840]
}
}
Размеры изображений
Настройка imageSizes
позволяет определить размеры изображений в виде списка. Этот список объединяется с массивом размеров устройств для формирования полного перечня размеров для генерации набора источников (srcset) изображения.
module.exports = {
images: {
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384]
}
}
next/head
Компонент Head
позволяет добавлять элементы в head
страницы:
import Head from 'next/head'
export default function IndexPage() {
return (
<div>
<Head>
<title>Заголовок страницы</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<p>Привет, народ!</p>
</div>
)
}
Проп key
позволяет дедуплицировать теги:
import Head from 'next/head'
export default function IndexPage() {
return (
<div>
<Head>
<title>Заголовок страницы</title>
<meta property="og:title" content="Заголовок страницы" key="title" />
</Head>
<Head>
<meta property="og:title" content="Новый заголовок страницы" key="title" />
</Head>
<p>Привет, народ!</p>
</div>
)
}
В данном случае будет отрендерен только <meta property="og:title" content="Новый заголовок страницы" key="title" />
.
Контент head
удаляется при размонтировании компонента.
title
, meta
и другие элементы должны быть прямыми потомками Head
. Они могут быть обернуты в один <React.Fragment>
или рендериться из массива.
next/server
Посредники создаются с помощью функции middleware
, находящейся в файле _middleware
. Интерфейс посредников основан на нативных объектах FetchEvent
, Response
и Request
.
Эти нативные объекты расширены для предоставления большего контроля над формированием ответов на основе входящих запросов.
Сигнатура функции:
import type { NextRequest, NextFetchEvent } from 'next/server'
export type Middleware = (
request: NextRequest,
event: NextFetchEvent
) => Promise<Response | undefined> | Response | undefined
Функция не обязательно должна называться middleware
. Это всего лишь соглашение. Функция также не обязательно должна быть асинхронной.
NextRequest
Объект NextRequest
расширяет нативный интерфейс Request
следующими методами и свойствами:
-
cookies
— куки запроса -
nextUrl
— расширенный, разобранный объектURL
, предоставляющий доступ к таким свойствам, какpathname
,basePath
,trailingSlash
иi18n
-
geo
— геоинформация о запросе-
country
— код страны -
region
— код региона -
city
— город -
latitude
— долгота -
longitude
— широта
-
-
ip
— IP-адрес запроса -
ua
— агент пользователя
NextRequest
может использоваться вместо Request
.
import type { NextRequest } from 'next/server'
NextFetchEvent
NextFetchEvent
расширяет объект FetchEvent
методом waitUntil
.
Данный метод позволяет продолжить выполнение функции после отправки ответа.
Например, waitUntil
может использоваться для интеграции с такими системами мониторинга ошибок, как Sentry
.
import type { NextFetchEvent } from 'next/server'
NextResponse
NextResponse
расширяет Response
следующими методами и свойствами:
-
cookies
— куки ответа -
redirect()
— возвращаетNextResponse
с набором перенаправлений (redirects) -
rewrite()
— возвращаетNextResponse
с набором перезаписей (rewrites) -
next()
— возвращаетNextResponse
, который является частью цепочки посредников
import { NextResponse } from 'next/server'
next.config.js
Файл next.config.js
или next.config.mjs
предназначен для кастомной продвинутой настройки Next.js
.
Пример next.config.js
:
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
/* настройки */
}
module.exports = nextConfig
Пример next.config.mjs
:
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
/* настройки */
}
export default nextConfig
Можно использовать функцию:
module.exports = (phase, { defaultConfig }) => {
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
/* настройки */
}
return nextConfig
}
phase
— это текущий контекст, для которого загружаются настройки. Доступными фазами являются:
PHASE_EXPORT
PHASE_PRODUCTION_BUILD
PHASE_PRODUCTION_SERVER
PHASE_DEVELOPMENT_SERVER
Фазы импортируются из next/constants
:
const { PHASE_DEVELOPMENT_SERVER } = require('next/constants')
module.exports = (phase, { defaultConfig }) => {
if (phase === PHASE_DEVELOPMENT_SERVER) {
return {
/* настройки для разработки */
}
}
return {
/* другие настройки */
}
}
Переменные среды окружения
Пример
module.exports = {
env: {
someKey: 'some-value'
}
}
Использование
export default function Page() {
return <h1>Значением `someKey` является `{process.env.someKey}`</h1>
}
Результат
export default function Page() {
return <h1>Значением `someKey` является `some-value`</h1>
}
Заголовки
Настройка headers
позволяет устанавливать кастомные HTTP-заголовки для входящих запросов:
module.exports = {
async headers() {
return [
{
source: '/about',
headers: [
{
key: 'x-custom-header',
value: 'кастомное значение заголовка',
},
{
key: 'x-another-custom-header',
value: 'еще одно кастомное значение заголовка'
}
]
}
]
}
}
headers
— асинхронная функция, возвращающая массив объектов со свойствами source
и headers
:
-
source
— адрес входящего запроса -
headers
— массив объектов со свойствамиkey
иvalue
Дополнительные параметры:
-
basePath: false | undefined
— еслиfalse
,basePath
не будет учитываться при совпадении, может использоваться только для внешних перезаписей -
locale: false | undefined
— определяет, должна ли при совпадении включаться локаль -
has
— массив объектов со свойствамиtype
,key
иvalue
Если в настройках определены два кастомных заголовка с одинаковыми ключами, будет учитываться только значение последнего заголовка.
Поиск совпадения путей
Разрешен поиск совпадения путей. Например, путь /blog/:slug
будет совпадать с /blog/hello-world
(без вложенных путей):
module.exports = {
async headers() {
return [
{
source: '/blog/:slug',
headers: [
{
key: 'x-slug',
value: ':slug', // Совпавшие параметры могут использоваться в качестве значений
},
{
key: 'x-slug-:slug', // Совпавшие параметры могут использоваться в качестве ключей
value: 'кастомное значение заголовка'
}
]
}
]
}
}
Поиск всех совпадений путей
Для поиска всех совпадений можно использовать *
после параметра, например, /blog/:slug*
будет совпадать с /blog/a/b/c/d/hello-world
:
module.exports = {
async headers() {
return [
{
source: '/blog/:slug*',
headers: [
{
key: 'x-slug',
value: ':slug*',
},
{
key: 'x-slug-:slug*',
value: 'кастомное значение заголовка'
}
]
}
]
}
}
Поиск совпадений путей с помощью регулярных выражений
Для поиска совпадений с помощью регулярок используются круглые скобки после параметра, например, /blog/:slug(\\d{1,})
будет совпадать с /blog/123
, но не с /blog/abc
:
module.exports = {
async headers() {
return [
{
source: '/blog/:post(\\d{1,})',
headers: [
{
key: 'x-post',
value: ':post'
}
]
}
]
}
}
Символы ( ) { } : * + ?
считаются частью регулярного выражения, поэтому при использовании в source
в качестве обычных символов они должны быть экранированы с помощью \\
:
module.exports = {
async headers() {
return [
{
// Это будет совпадать с `/english(default)/something`
source: '/english\\(default\\)/:slug',
headers: [
{
key: 'x-header',
value: 'some-value'
}
]
}
]
}
}
Поиск совпадения на основе заголовка, куки и строки запроса
Для этого используется поле has
. Заголовок будет установлен только при совпадении полей source
и has
.
Значением has
является массив объектов со следующими свойствами:
-
type: string
—header | cookie | host | query
-
key: string
— ключ для поиска совпадения -
value: string | undefined
— значение для проверки. Еслиundefined
, любое значение будет совпадать. Для захвата определенной части значения может использоваться регулярное выражение. Например, если дляhello-world
используется значениеhello-(?<param>.*)
,world
можно использовать в качестве значенияdestination
как:param
:
module.exports = {
async headers() {
return [
// Если имеется заголовок `x-add-header`,
// будет установлен заголовок `x-another-header`
{
source: '/:path*',
has: [
{
type: 'header',
key: 'x-add-header',
},
],
headers: [
{
key: 'x-another-header',
value: 'hello'
}
]
},
// Если совпадают `source`, `query` и `cookie`,
// будет установлен заголовок `x-authorized`
{
source: '/specific/:path*',
has: [
{
type: 'query',
key: 'page',
// Значение `page` будет недоступно в заголовке,
// поскольку значение указано и при этом
// не используются именованная группа захвата, например `(?<page>home)`
value: 'home',
},
{
type: 'cookie',
key: 'authorized',
value: 'true',
},
],
headers: [
{
key: 'x-authorized',
value: ':authorized'
}
]
},
// Если имеется заголовок `x-authorized` и он
// содержит искомое значение, будет установлен заголовок `x-another-header`
{
source: '/:path*',
has: [
{
type: 'header',
key: 'x-authorized',
value: '(?<authorized>yes|true)',
},
],
headers: [
{
key: 'x-another-header',
value: ':authorized'
}
]
},
// Если значением хоста является `example.com`,
// будет установлен данный заголовок
{
source: '/:path*',
has: [
{
type: 'host',
value: 'example.com',
},
],
headers: [
{
key: 'x-another-header',
value: ':authorized'
}
]
}
]
}
}
basePath
и i18n
При наличии basePath
, его значение автоматически добавляется к значению source
, если не определено basePath: false
:
module.exports = {
basePath: '/docs',
async headers() {
return [
{
source: '/with-basePath', // становится /docs/with-basePath
headers: [
{
key: 'x-hello',
value: 'world'
}
]
},
{
source: '/without-basePath', // не модифицируется
headers: [
{
key: 'x-hello',
value: 'world'
}
],
basePath: false
}
]
}
}
При наличии i18n
к значению source
автоматически добавляются значения locales
, если не определено locale: false
— в этом случае значение source
должно включать префикс локали:
module.exports = {
i18n: {
locales: ['en', 'fr', 'de'],
defaultLocale: 'en',
},
async headers() {
return [
{
source: '/with-locale', // автоматическая обработка всех локалей
headers: [
{
key: 'x-hello',
value: 'world'
}
]
},
{
// ручная установка локали
source: '/nl/with-locale-manual',
locale: false,
headers: [
{
key: 'x-hello',
value: 'world'
}
]
},
{
// это совпадает с `/`, поскольку `en` является `defaultLocale`
source: '/en',
locale: false,
headers: [
{
key: 'x-hello',
value: 'world'
}
]
},
{
// это преобразуется в /(en|fr|de)/(.*), поэтому не будет совпадать с роутами верхнего уровня, такими как
// `/` или `/fr`, а с `/:path*` будет
source: '/(.*)',
headers: [
{
key: 'x-hello',
value: 'worlld'
}
]
}
]
}
}
Дополнительная настройка Webpack
Перед кастомизацией Вебпака убедитесь, что Next.js
не имеет поддержки необходимого функционала.
Пример определения функции для настройки webpack
:
module.exports = {
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
// не забудьте вернуть модифицированный конфиг
return config
}
}
Данная функция выполняется дважды: один раз для сервера и еще один для клиента. Это позволяет разделять настройки с помощью свойства isServer
.
Вторым аргументом, передаваемым функции является объект со следующими свойствами:
-
buildId: string
— уникальный идентификатор сборки -
dev: boolean
— индикатор компиляции в режиме разработки -
isServer: boolean
— еслиtrue
, значит, выполняется компиляция для сервера -
defaultLocales: object
— дефолтные лоадеры:-
babel: object
— дефолтные настройкиbabel-loader
-
Пример использования defaultLoaders.babel
:
module.exports = {
webpack: (config, options) => {
config.module.rules.push({
test: /\.mdx/,
use: [
options.defaultLoaders.babel,
{
loader: '@mdx-js/loader',
options: pluginOptions.options
}
]
})
return config
}
}
Настройка distDir
позволяет определять директорию для сборки:
module.exports = {
distDir: 'build'
}
Настройка generateBuildId
позволяет определять идентификатор сборки:
module.exports = {
generateBuildId: async () => {
// Здесь можно использовать, например, хеш последнего гит-коммита
return 'my-build-id'
}
}
Пример отключения проверки кода с помощью eslint
при сборке проекта:
module.exports = {
eslint: {
ignoreDuringBuilds: true
}
}
Пример отключения typescript
при сборке проекта:
module.exports = {
typescript: {
ignoreBuildErrors: true
}
}
Пожалуй, на сегодня это все.
Благодарю за внимание и хорошего дня!
noodles
Это не проблемы SPA. Это проблемы тех, кто принял решение делать контентную seo-чувствительную чать продукта по технологии spa.
SPA - это как правило приложения, которые спрятаны за аутентификацией.
Или же ЗА кнопками "начать", "запустить", "войти", "заказать", "создать" и т.д.
ДО этих кнопок как правило находится seo-важный контент, который продаёт/рекламирует/рассказывает про продукт. Его не нужно делать как spa.