Маршрутизация и получение данных в Next.js

В статье мы рассмотрим:

  • Отрисовку отдельных компонентов по разным маршрутам.
  • Разницу между статической генерацией и генерацией на стороне сервера.
  • Получение данных с помощью 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:

Код, который нужно найти в /pages/index.js

Замените его на:

Код для замены в /pages/index.js

Выполните этот код командой:

Команда в терминале для запуска кода

Для просмотра вывода перейдите на 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 и пропишите в нем следующий код:

Код для файла /pages/about.js
  • строка 1: создание компонента About и его экспорт для использования в проекте.

Выполните код. На этот раз вывод будет таким:

Вывод кода

Все работает! При переходе в каталог /about на странице отображается компонент pages/about.js.

Как уже говорилось, можно даже писать код для реализации в приложении суб-маршрутизации. Для этого создайте в директории /pages каталог contact. В contact создайте файл index.js, и в нем пропишите:

Код для файла /pages/contact/index.js

Теперь создайте в каталоге /pages/contact другой компонент под названием helloworld.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:

Код для /pages/contact/[id].js

Снова запустите код. Результат будет таким:

Вывод кода

Как видите, при переходе на страницу /contact/{id} отображается соответствующий компонент. Значит, наш код работает!

Тем не менее остается одна проблема: как извлечь параметр {id}? Здесь нам поможет хук useRouter.

useRouter

Хук useRouter, по сути, сообщает нам информацию об URL (например, текущий маршрут, на котором находится пользователь, или параметры). В этой статье нас будут интересовать только параметры.

Найдите в /pages/contact/[id].js следующий фрагмент кода:

Код, который нужно найти в /pages/contact/[id].js

Замените его на:

Код для замены в /pages/contact/[id].js
  • строка 1: импортируем хук useRouter;
  • строка 3: создаем экземпляр useRouter , который будет предоставлять данные о параметрах текущего маршрута;
  • строка 4: используем деструктуризацию объекта для извлечения параметра id из объекта query, расположенного в экземпляре useRouter

Теперь осталось только отобразить полученный ID. Найдите блок return в /pages/contact/[id].js:

Код, который нужно найти в /pages/contact/[id].js

Замените его на:

Код для замены в /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 и отображаете все элементы:

Список элементов в To-Do API

Когда мы даем Next.js команду собрать эту страницу, JS-код компилируется и преобразуется в HTML:

Преобразованный 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 следующий фрагмент кода:

Код, который нужно найти в /pages/index.js

Замените его на:

Код для замены в /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:

Код для /pages/index.js

Далее найдите в том же файле этот фрагмент:

Код, который нужно найти в /pages/index.js

Замените его на:

Код для замены в /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 по праву занял лидирующее место на рынке фреймворков для разработки блогов. Он не только позволяет создавать быстродействующие и масштабируемые приложения React, но также дает разработчику возможность реализовывать маршрутизацию без лишних заморочек. Не удивительно, что в видео Traversy Media он упоминается как замечательный фреймворк для освоения в 2021 году.

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

Благодарю за ваше внимание!

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Hussain Arif: Routing and Data Fetching in Next.js

Предыдущая статьяВ погоне за продуктивностью, или 9 полезных расширений для браузера
Следующая статьяКомпилятор VS интерпретатор: ключевые отличия