В статье мы рассмотрим:
- Отрисовку отдельных компонентов по разным маршрутам.
- Разницу между статической генерацией и генерацией на стороне сервера.
- Получение данных с помощью
getStaticProps
. - Получение данных и динамическую генерацию маршрутов с помощью
getStaticPaths
. - Получение данных API на стороне сервера через
getServerSideProps
.
Введение
Представьте, что вас нанимают для создания сайта с помощью React. Вот требования заказчика:
- Это должен быть не стандартный одностраничник, а полноценный сайт с отдельными страницами, отображаемыми по соответствующим маршрутам.
- Сайт будет статичен, поэтому его быстродействие должно играть первостепенную роль.
Вы уверенно соглашаетесь на реализацию этого проекта, ведь опыт работы с React у вас имеется, и сложностей возникнуть не должно. Тем не менее, поскольку это статический сайт, вам нужно максимально повысить его скорость. Здесь у вас возникает вопрос: “А как создать быстродействующий сайт?”
После недолгого изучения вопроса вы понимаете, что есть такой фреймворк — Next.js.
Что такое Next.js, и когда он нужен?
С появлением Next.js многие разработчики переметнулись с чистого React именно на него. Очевидно, что он имеет свои выгодные особенности.
Согласно посту на StackOverFlow эта библиотека задействует удобную систему маршрутизации, которая позволяет программисту отображать конкретные компоненты по конкретным маршрутам. Более того, она также дает возможность делать отрисовку на стороне сервера. Этот метод позволяет распределить процесс загрузки во времени, повысив тем самым быстродействие. И это еще не все. Как говорит один из участников разработки Next.js, Ли Робинсон, эта технология дает гибкую возможность использовать отрисовку каждой страницы либо на стороне клиента, либо на сервере.
У вас может возникнуть вопрос: “Так почему бы просто не использовать React и React Routers? Зачем изучать еще один фреймворк?” Дело в том, что в чистом React по мере увеличения размера приложения и его сложности производительность уходит на второй план. Если ваше приложение будет медлить, то и пользоваться им никто не захочет. Next.js устраняет эту проблему за счет минимизации JS-кода, кэширования сборок и других техник, обеспечивающих повышенную скорость отклика.
Если говорить в общем, то он упрощает маршрутизацию и обеспечивает для статического сайта максимально возможную производительность.
А теперь к делу. Начнем с изучения простой маршрутизации на основе имен файлов.
Маршрутизация на основе имен файлов
Настройка
Для инициализации проекта выполните в терминале:
Перейдите в каталог /pages
проекта и найдите в нем index.js
. В этом файле отыщите компонент Home
:
Замените его на:
Выполните этот код командой:
Для просмотра вывода перейдите на localhost:3000
. Там вы увидите:
Теперь перейдем к созданию простых страниц для нашего приложения.
Создание страниц
Каждая страница в Next.js управляется компонентом React. При создании компонентов страниц Next.js опирается на каталог /pages
. При этом страницы также ассоциируются с их именем файла. Например:
- При переходе по маршруту
/about
будет отрисовываться компонент вpages/about.js
. - Аналогичным образом при переходе по маршруту
/contact
Next.js будет отрисовывать компонент, расположенный вpages/contact.js
. - Если же перейти в
/todo/helloworld.js
, то в браузере отобразиться компонент изpages/todo/helloworld.js
.
Но:
- При переходе в корневой каталог
/
отрисовываетсяpages/index.js
. - Если направиться в
/todo
, будет отображен компонент React изtodo/index.js
.
Примечание: файл index.js
является исключением из названного правила.
Разобравшись с принципом действия, можно переходить к реализации в коде.
Создайте в каталоге /pages
файл about.js
и пропишите в нем следующий код:
- строка 1: создание компонента
About
и его экспорт для использования в проекте.
Выполните код. На этот раз вывод будет таким:
Все работает! При переходе в каталог /about
на странице отображается компонент pages/about.js
.
Как уже говорилось, можно даже писать код для реализации в приложении суб-маршрутизации. Для этого создайте в директории /pages
каталог contact
. В contact
создайте файл index.js
, и в нем пропишите:
Теперь создайте в каталоге /pages/contact
другой компонент под названием helloworld.js
. В этом компоненте пропишите:
Выполните код. Вывод будет таким:
Все также работает! Когда пользователь переходит по маршруту /contact
, отрисовывается файл contact/index.js
. Если же клиент направляется по пути /contact/helloworld
отображается компонент ContactHello
.
Судя по тому, насколько меньше кода нам пришлось написать, очевидно, что Next.js существенно упрощает для разработчиков создание маршрутов.
Далее мы изучим основы динамической маршрутизации.
Динамическая маршрутизация
Предположим, что у вас есть каталог с контактной информацией о различных людях на сайте. В этом случае вам нужно прописать маршруты в формате /contact/{id}
, где {id}
будет параметром, содержащим ID конкретного человека в списке. Например:
contact/1
отображает информацию о первом контакте списка;- точно также
contact/2
предоставит данные по второму контакту и т.д.
Как же это реализовать? Один из вариантов — структурировать каталог /pages
следующим образом:
Безусловно, это должно сработать. Но что, если у нас будут сотни контактов? Это сделает дальнейшее написание кода очень утомительным и времязатратным. К счастью, в Next.js для этого уже есть специальная система.
Создайте в каталоге pages/contact
файл [id].js
. Он будет отображаться в случае перехода пользователя по маршруту /contact/{id}
, где {id}
является параметром.
Напишите в /pages/contact/[id].js
:
Снова запустите код. Результат будет таким:
Как видите, при переходе на страницу /contact/{id}
отображается соответствующий компонент. Значит, наш код работает!
Тем не менее остается одна проблема: как извлечь параметр {id}
? Здесь нам поможет хук useRouter
.
useRouter
Хук useRouter
, по сути, сообщает нам информацию об URL (например, текущий маршрут, на котором находится пользователь, или параметры). В этой статье нас будут интересовать только параметры.
Найдите в /pages/contact/[id].js
следующий фрагмент кода:
Замените его на:
- строка 1: импортируем хук
useRouter
; - строка 3: создаем экземпляр
useRouter
, который будет предоставлять данные о параметрах текущего маршрута; - строка 4: используем деструктуризацию объекта для извлечения параметра
id
из объектаquery
, расположенного в экземпляреuseRouter
.
Теперь осталось только отобразить полученный ID. Найдите блок return
в /pages/contact/[id].js
:
Замените его на:
- строка 3: отображаем текущий параметр
id
.
Выполните код. Вот ожидаемый результат:
Отлично! Код работает! Как видите, страница показывает текущий параметр id
.
На этом текущий раздел заканчивается. Далее мы изучим получение данных с API через Next.js
В итоге /pages/contact/[id].js
должен выглядеть так:
import { useRouter } from "next/router";
export default function ContactId() {
const router = useRouter();
const { id } = router.query;
return (
<div>
<p>At ID number: {id}</p>
</div>
);
}
Репозиторий GitHub для этого раздела.
Получение данных из внешних API
Настройка
Этот процесс аналогичен рассмотренному в предыдущем разделе. Вот только название репозитория мы установим как next-api-fetch
.
Далее мы сначала разберем разницу между статической генерацией сайта и его генерацией на сервере.
Статическая генерация сайта
Предположим, что вы запрашиваете данные из To-Do API и отображаете все элементы:
Когда мы даем Next.js команду собрать эту страницу, JS-код компилируется и преобразуется в HTML:
Теперь вместо отрисовки JS для каждого запроса пользователю передается этот HTML-код. В итоге процесс отображения существенно ускоряется.
Более того, поскольку элементы в To-Do API статичны (то есть в ближайшем времени не изменятся), статическая генерация оказывается как раз кстати.
Идем далее.
Генерация на стороне сервера
В качестве примера мы используем Sports API для получения результатов футбольных матчей. Здесь данные изменяются в реальном времени. Следовательно, для Next.js нет смысла кэшировать результаты этого кода, поскольку они будут меняться при каждом обновлении пользователем страницы.
В этом случае отлично подойдет генерация на стороне сервера.
Если кратко, то статическую генерацию следует применять для редко изменяющихся данных, а серверную для тех, которые меняются постоянно.
Рассмотрев виды генерации, можно переходить к знакомству с методом getStaticProps
.
Получение данных через getStaticProps
Как и предполагает его имя, метод getStaticProps
использует статическую генерацию. Работать мы будем с To-Do API.
Пропишите в конце pages/index.js
следующий фрагмент кода:
export const getStaticProps = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/todos");
const todos = await res.json();
return {
props: {
todos,
},
};
};
- строка 1: экспортируем функцию
getStaticProps
; - строка 2: выполняем функцию
fetch
для To-Dos API и сохраняем сырые данные в переменнойres
; - строка 3: преобразуем сырые данные в JSON и сохраняем в переменной
todos
; - строка 5: отправляем преобразованные данные как пропсы (входные данные).
Так мы экспортировали полученные данные в виде пропсов. Последним шагом будет их отображение.
Найдите в /pages/index.js
следующий фрагмент кода:
Замените его на:
- строка 1: вносим объект
todos
, который мы получили в качестве пропсов из методаgetStaticProps
; - строка 4: используем метод
map
для массива и отображаем полеtitle
каждого элемента.
Выполняем код. Результат будет таким:
Код работает! Мы смогли отобразить на странице все элементы.
В итоге pages/index.js
должен выглядеть так:
import Head from "next/head";
import styles from "../styles/Home.module.css";
export default function Home({ todos }) {
return (
<div>
{todos.map((item) => (
<p key={item.id}>{item.title}</p>
))}
</div>
);
}
export const getStaticProps = async (context) => {
const res = await fetch("https://jsonplaceholder.typicode.com/todos");
const todos = await res.json();
return {
props: {
todos,
},
};
};
В идеале нам бы хотелось, чтобы каждый элемент вел на отдельную страницу, на которой бы отображались остальные его свойства. Для этого можно сделать следующее:
- использовать компонент
Link
, сделав каждый элемент кликабельным. - при клике по элементу должен происходить переход на другую страницу по маршруту
/todo/{id}
, гдеid
представляет ID элемента из списка to-do. - на странице
/todo/{id}
выполнить запросfetch
для элемента to-do с полем ID, содержащим{id}
. - отобразить все поля этого элемента на странице
/todo/{id}
.
А теперь реализуем все это в коде. Сначала пропишите импорт Link
в /pages/index.js
:
Далее найдите в том же файле этот фрагмент:
Замените его на:
- строка 4: используем компонент
Link
для перенаправления пользователя по маршруту/todo
с полемid
в качестве параметра.Link
в Next.js аналогичен тегуa
. - строка 5: отображаем поле
title
элемента.
Выполните код. Результат будет таким:
Все работает! Элементы перенаправляют нас в соответствующее расположение. Тем не менее при клике на любой из них возникает ошибка. Причина в том, что эти маршруты еще не обработаны.
Осталось только реализовать динамическую маршрутизацию, что с помощью Next.js делается очень просто. В следующем разделе мы применим вложенную и динамическую маршрутизацию через функцию getServerSideProps()
.
Запрос данных с помощью getServerSideProps
Функция getServerSideProps
использует для получения данных генерацию на стороне сервера. Дело в том, что нам нужно получать выбранный элемент to-do в момент запроса.
Создайте в директории /pages
каталог /todo
. В нем создайте файл [id].js
и заполните его следующим кодом:
export default function TodoInfo({ todo }) {
return (
<div>
<h1>{todo.title}</h1>
<input type="checkbox" readOnly checked={todo.completed}></input>
<label for="completed">Completed</label>
</div>
);
}
export const getServerSideProps = async (context) => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${context.params.id}`
);
const todo = await res.json();
return {
props: {
todo,
},
};
};
- строка 1: получаем объект
todo
, который приняли в качестве пропсов из функцииgetServerSideProps
; - строки 4–6: отображаем свойства объекта
todo
. Если полеcompleted
будетtrue
, значит флажок отмечен; - строка 11: экспортируем метод
getServerSideProps
. Параметрcontext
в этой функции содержит информацию об URL и других свойствах, подробнее о которых можно почитать в документации; - строка 12: получаем данные о конкретном элементе to-do. Это можно сделать с помощью объекта
context.params
, который содержит значения параметров URL. Здесь мы обращаемся к значению параметраid
; - строка 19: экспортируем полученные данные в качестве пропсов.
Выполняем код. Вот его вывод:
Отлично! Теперь можно кликать по элементу, получая о нем более подробную информацию.
В следующем разделе мы познакомимся с функцией getStaticPaths
.
Генерация путей с помощью getStaticPaths
Хоть сайт у нас и работает, как планировалось, есть еще одна проблема: что, если пользователь попытается перейти к несуществующему элементу to-do, например к localhost:3000/todo/4300
? Естественно, в нашем примере элемента с таким id
нет. В этом случае нужно выбросить ошибку 404: Page not found
.
Для решения этой проблемы нужно указать Next.js, какие пути должны отображаться. Здесь и используется getStaticPaths
. Эта функция задействует статическую генерацию и применяется совместно с методом getStaticProps
.
Найдите в /pages/todo/[id].js
этот фрагмент:
export const getServerSideProps = async (context) => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${context.params.id}`
);
const todo = await res.json();
return {
props: {
todo,
},
};
}
Переименуйте функцию getServerSideProps
в getStaticProps
:
export const getStaticProps = async (context) => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${context.params.id}`
);
const todo = await res.json();
return {
props: {
todo,
},
};
};
Пропишите в конце pages/todo/[id].js
следующий фрагмент:
export const getStaticPaths = async () => {
const res = await fetch(`https://jsonplaceholder.typicode.com/todos/`);
const todos = await res.json();
const paths = todos.map((item) => ({
params: { id: item.id.toString() },
}));
return {
paths,
fallback: false,
};
};
- строка 1: экспортируем функцию
getStaticPaths
; - строка 5: создаем массив
paths
, содержащий поляid
всех элементов to-do в API; - строка 9: возвращаем объект
paths
. Он указывает Next.js, какие пути будут отображаться; - строка 10: флаг
fallback
сообщает Next.js о необходимости выбрасывать ошибку в случае перехода пользователя по неподдерживаемому пути.
Выполняем код. Вот его результат:
Как видите, вывод кода остался прежним, но попытайтесь перейти по адресу localhost:3000/todo/250
. Так как элемента с id
равным 250
не существует, будет выброшена ошибка.
Все отлично работает! Можно даже уменьшить количество генерируемых функцией getStaticPaths
путей. Перейдите в /pages/todo/[id].js
и найдите следующий фрагмент:
export const getStaticPaths = async () => {
const res = await fetch(`https://jsonplaceholder.typicode.com/todos/`);
const todos = await res.json();
const paths = todos.map((item) => ({
params: { id: item.id.toString() },
}));
return {
paths,
fallback: false,
};
};
Измените его на:
export const getStaticPaths = async () => {
const res = await fetch(`https://jsonplaceholder.typicode.com/todos/`);
const todos = await res.json();
return {
paths: [{ params: { id: "1" } }, { params: { id: "2" } }],
fallback: false,
};
};
- строка 7: теперь генерируется только два статических пути. В остальных случаях будет возникать ошибка
404: Page not found
.
Выполните код. Вот результат:
Как видите, все сработало, и теперь сгенерировалось только два динамических пути.
В итоге файл /pages/todo/[id].js
должен выглядеть так:
export default function TodoInfo({ todo }) {
console.log(todo);
return (
<>
<h1>{todo.title}</h1>
<input type="checkbox" readOnly checked={todo.completed}></input>
<label for="completed">Completed</label>
</>
);
}
export const getStaticProps = async (context) => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${context.params.id}`
);
const todo = await res.json();
return {
props: {
todo,
},
};
};
export const getStaticPaths = async () => {
const res = await fetch(`https://jsonplaceholder.typicode.com/todos/`);
const todos = await res.json();
const paths = todos.map((item) => ({
params: { id: item.id.toString() },
}));
return {
paths,
fallback: false,
};
//to only show two paths:
/* return {
paths: [{ params: { id: "1" } }, { params: { id: "2" } }],
fallback: false,
}; */
};
Репозиторий GitHub для этого раздела.
Дополнительные ресурсы и репозитории GitHub
Репозитории GitHub
Дополнительные ресурсы
- Next.js — Pages and Routes By Net Ninja
- Next.js — getStaticProps by Net Ninja
- Data Fetching in Next.js — Official documentation
- Next.js Data Fetching by Traversy Media
Заключение
Next.js по праву занял лидирующее место на рынке фреймворков для разработки блогов. Он не только позволяет создавать быстродействующие и масштабируемые приложения React, но также дает разработчику возможность реализовывать маршрутизацию без лишних заморочек. Не удивительно, что в видео Traversy Media он упоминается как замечательный фреймворк для освоения в 2021 году.
Если в процессе изучения этого руководства у вас возникнут сложности, можете разобрать образцы кода и поэкспериментировать с ними, чтобы полностью уловить всю логику.
Благодарю за ваше внимание!
Читайте также:
- Создаем собственный блог с помощью Next.js и Strapi
- 10 видов шаблонного кода на NextJS
- Установка Next.js с использованием клиентского сервера Express и TypeScript
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Hussain Arif: Routing and Data Fetching in Next.js