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

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 и т.д.

Чтобы узнать больше о динамической маршрутизации, ознакомьтесь с документацией по динамическим маршрутам.

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

Модель 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} />)
}

При переходе между страницами мы хотим сохранять состояние страницы (значения полей ввода, позицию прокрутки и т.д.) для работы в стиле Single-Page Application (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 />
    </>
  )
}