Страницы и макеты

Pages Router использует файловую систему для маршрутизации, основанную на концепции страниц.

При добавлении файла в директорию pages он автоматически становится доступным как маршрут.

В Next.js страница — это React-компонент, экспортируемый из файла .js, .jsx, .ts или .tsx в директории pages. Каждая страница связана с маршрутом на основе имени файла.

Пример: Если вы создадите pages/about.js, экспортирующий React-компонент, как показано ниже, он будет доступен по адресу /about.

export default function About() {
  return <div>About</div>
}

Корневые маршруты

Роутер автоматически связывает файлы с именем index с корнем директории.

  • pages/index.js/
  • pages/blog/index.js/blog

Вложенные маршруты

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

  • pages/blog/first-post.js/blog/first-post
  • pages/dashboard/settings/username.js/dashboard/settings/username

Страницы с динамическими маршрутами

Next.js поддерживает страницы с динамическими маршрутами. Например, если создать файл pages/posts/[id].js, он будет доступен по адресам posts/1, posts/2 и т.д.

Подробнее о динамической маршрутизации можно узнать в документации по Dynamic Routing.

Шаблон макета

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

components/layout.js
import Navbar from './navbar'
import Footer from './footer'

export default function Layout({ children }) {
  return (
    <>
      <Navbar />
      <main>{children}</main>
      <Footer />
    </>
  )
}

Примеры

Общий макет с Custom App

Если для всего приложения требуется только один макет, можно создать Custom App и обернуть приложение в макет. Поскольку компонент <Layout /> повторно используется при смене страниц, его состояние (например, значения полей ввода) сохраняется.

pages/_app.js
import Layout from '../components/layout'

export default function MyApp({ Component, pageProps }) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  )
}

Индивидуальные макеты для страниц

Если требуется несколько макетов, можно добавить свойство getLayout к странице, позволяющее возвращать React-компонент для макета. Это позволяет определять макет для каждой страницы индивидуально. Поскольку мы возвращаем функцию, можно создавать сложные вложенные макеты.

pages/index.js

import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'

export default function Page() {
  return (
    /** Ваш контент */
  )
}

Page.getLayout = function getLayout(page) {
  return (
    <Layout>
      <NestedLayout>{page}</NestedLayout>
    </Layout>
  )
}
pages/_app.js
export default function MyApp({ Component, pageProps }) {
  // Используем макет, определённый на уровне страницы, если он доступен
  const getLayout = Component.getLayout ?? ((page) => page)

  return getLayout(<Component {...pageProps} />)
}

При переходе между страницами мы хотим сохранять состояние страницы (значения полей ввода, позицию прокрутки и т.д.) для одностраничного приложения (SPA).

Этот шаблон макета позволяет сохранять состояние, поскольку дерево React-компонентов поддерживается между переходами. React может определять, какие элементы изменились, чтобы сохранить состояние.

Полезно знать: Этот процесс называется согласованием (reconciliation), который позволяет React определять изменённые элементы.

Использование TypeScript

При использовании TypeScript сначала нужно создать новый тип для страниц, включающий функцию getLayout. Затем необходимо создать новый тип для AppProps, переопределяющий свойство Component с использованием ранее созданного типа.

import type { ReactElement } from 'react'
import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'
import type { NextPageWithLayout } from './_app'

const Page: NextPageWithLayout = () => {
  return <p>hello world</p>
}

Page.getLayout = function getLayout(page: ReactElement) {
  return (
    <Layout>
      <NestedLayout>{page}</NestedLayout>
    </Layout>
  )
}

export default Page
import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'

const Page = () => {
  return <p>hello world</p>
}

Page.getLayout = function getLayout(page) {
  return (
    <Layout>
      <NestedLayout>{page}</NestedLayout>
    </Layout>
  )
}

export default Page
import type { ReactElement, ReactNode } from 'react'
import type { NextPage } from 'next'
import type { AppProps } from 'next/app'

export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
  getLayout?: (page: ReactElement) => ReactNode
}

type AppPropsWithLayout = AppProps & {
  Component: NextPageWithLayout
}

export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
  // Используем макет, определённый на уровне страницы, если он доступен
  const getLayout = Component.getLayout ?? ((page) => page)

  return getLayout(<Component {...pageProps} />)
}
export default function MyApp({ Component, pageProps }) {
  // Используем макет, определённый на уровне страницы, если он доступен
  const getLayout = Component.getLayout ?? ((page) => page)

  return getLayout(<Component {...pageProps} />)
}

Получение данных

В макете можно получать данные на стороне клиента с помощью useEffect или библиотек вроде SWR. Поскольку этот файл не является страницей, нельзя использовать getStaticProps или getServerSideProps.

components/layout.js
import useSWR from 'swr'
import Navbar from './navbar'
import Footer from './footer'

export default function Layout({ children }) {
  const { data, error } = useSWR('/api/navigation', fetcher)

  if (error) return <div>Failed to load</div>
  if (!data) return <div>Loading...</div>

  return (
    <>
      <Navbar links={data.links} />
      <main>{children}</main>
      <Footer />
    </>
  )
}