BackНазад к блогу

Next.js 9.3

Next.js 9.3 представляет улучшения для генерации статических сайтов, нативную поддержку SCSS, уменьшение размеров бандлов, статические 404 страницы и многое другое!

Сегодня мы рады представить Next.js 9.3 с новыми возможностями:

Все эти улучшения обратно совместимы и не требуют изменений. Для обновления достаточно выполнить:

Терминал
npm i next@latest react@latest react-dom@latest

Поддержка нового поколения статической генерации сайтов (SSG)

При создании веб-сайтов или приложений обычно приходится выбирать между двумя стратегиями: статической генерацией (SSG) или серверным рендерингом (SSR).

Next.js — это первый гибридный фреймворк, позволяющий выбирать подходящую технику для каждого конкретного случая на уровне отдельных страниц.

Next.js 9.0 представил концепцию Автоматической статической оптимизации. Если страница не требует блокирующего получения данных (например, через getInitialProps), она автоматически рендерится в HTML на этапе сборки.

Однако бывают случаи, когда нужно сгенерировать страницу в статический HTML на этапе сборки, даже если требуется блокирующее получение данных. Например, маркетинговые страницы на основе (headless) CMS или блоговая часть сайта.

Мы сотрудничали с активными пользователями SSG и next export, такими как HashiCorp, и подробно обсуждали оптимальные ограничения с сообществом в самом комментируемом RFC в истории Next.js, чтобы создать новый унифицированный способ получения данных и статической генерации.

Сегодня мы с гордостью представляем два новых метода получения данных: getStaticProps и getServerSideProps. Также мы добавили способ указания параметров для статической генерации страниц с динамическими маршрутами: getStaticPaths.

Эти новые методы имеют множество преимуществ по сравнению с моделью getInitialProps, так как чётко разделяют SSG и SSR:

  • getStaticProps (Статическая генерация): Получение данных на этапе сборки.
  • getStaticPaths (Статическая генерация): Указание динамических маршрутов для предварительного рендеринга на основе данных.
  • getServerSideProps (Серверный рендеринг): Получение данных при каждом запросе.
  • Эти улучшения являются дополнениями к API. Вся новая функциональность полностью обратно совместима и может внедряться постепенно. Никакие функции не объявлены устаревшими, и getInitialProps продолжит работать как прежде. Однако мы рекомендуем использовать новые методы для новых страниц и проектов.

getStaticProps

Если экспортировать async функцию getStaticProps из страницы, Next.js предварительно отрендерит эту страницу на этапе сборки. Это особенно полезно, когда нужно рендерить определённые статические страницы из CMS.

getStaticProps всегда выполняется в контексте Node.js, и код автоматически исключается из клиентских бандлов (tree-shaken), что уменьшает объём кода, отправляемого в браузер. Это избавляет от необходимости заботиться о выполнении кода получения данных как в Node.js, так и в браузере, где есть некоторые различия.

Это позволяет использовать любые асинхронные или даже синхронные методы получения данных, включая fetch, REST, GraphQL или прямой доступ к базе данных.

pages/posts/[id].js
export async function getStaticProps(context) {
  return {
    props: {}, // будет передано в компонент страницы как props
  };
}

Параметр context — это объект со следующими ключами:

  • params: Содержит параметры маршрута для страниц с динамическими маршрутами. Например, если страница называется [id].js, то params будет выглядеть как { id: ... }. Подробнее см. в документации по динамической маршрутизации. Это следует использовать вместе с getStaticPaths, о котором мы расскажем позже.

Вот пример использования getStaticProps для получения списка постов блога из CMS:

pages/blog.js
// Можно использовать любую библиотеку для получения данных
import fetch from 'node-fetch';
 
// posts будет заполнен на этапе сборки через getStaticProps()
function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li>{post.title}</li>
      ))}
    </ul>
  );
}
 
// Эта функция вызывается на этапе сборки в окружении Node.js.
// Она не вызывается на клиенте, поэтому можно даже делать
// прямые запросы к базе данных. См. раздел "Технические детали".
export async function getStaticProps() {
  // Вызов внешнего API для получения постов.
  const res = await fetch('https://.../posts');
  const posts = await res.json();
 
  // Возвращая { props: posts }, компонент Blog
  // получит `posts` как проп на этапе сборки
  return {
    props: {
      posts,
    },
  };
}
 
export default Blog;

Когда использовать getStaticProps?

getStaticProps следует использовать, если:

  • Данные, необходимые для рендеринга страницы, доступны на этапе сборки до запроса пользователя.
  • Данные поступают из headless CMS.
  • Данные могут кэшироваться публично (не специфичны для пользователя).
  • Страница должна быть предварительно отрендерена (для SEO) и быть очень быстрой — getStaticProps генерирует HTML и JSON файлы, которые можно кэшировать через CDN для повышения производительности.

Подробнее о getStaticProps см. в документации по получению данных.

getStaticPaths

Если страница имеет динамические маршруты и использует getStaticProps, необходимо определить список путей, которые должны быть отрендерены в HTML на этапе сборки.

Если экспортировать async функцию getStaticPaths из страницы с динамическими маршрутами, Next.js предварительно отрендерит все пути, указанные в getStaticPaths.

pages/posts/[id].js
export async function getStaticPaths() {
  return {
    paths: [
      { params: { ... } } // См. раздел "paths" ниже
    ],
    fallback: true or false // См. раздел "fallback" ниже
  };
}

Ключ paths (обязательный)

Ключ paths определяет, какие пути будут предварительно отрендерены. Например, предположим, что у вас есть страница с динамическими маршрутами pages/posts/[id].js. Если экспортировать getStaticPaths из этой страницы и вернуть следующее для paths:

return {
  paths: [
    { params: { id: 1 } },
    { params: { id: 2 } }
  ],
  fallback: ...
}

Тогда Next.js статически сгенерирует posts/1 и posts/2 на этапе сборки, используя компонент страницы из pages/posts/[id].js.

Обратите внимание, что значения каждого params должны соответствовать параметрам, используемым в имени страницы:

  • Если имя страницы pages/posts/[postId]/[commentId], то params должен содержать postId и commentId.
  • Если имя страницы использует catch-all маршруты, например pages/[...slug], то params должен содержать slug в виде массива. Например, если массив ['foo', 'bar'], то Next.js статически сгенерирует страницу по адресу /foo/bar.

Ключ fallback (обязательный)

Объект, возвращаемый getStaticPaths, должен содержать булево значение fallback.

Fallback: false

Если fallback равен false, любые пути, не возвращённые getStaticPaths, приведут к 404 странице. Это полезно, если все пути известны на этапе сборки.

Вот пример, который предварительно рендерит один пост блога на странице pages/posts/[id].js. Список постов будет получен из CMS и возвращён getStaticPaths. Затем для каждой страницы данные поста получаются из CMS через getStaticProps.

pages/posts/[id].js
import fetch from 'node-fetch';
 
function Post({ post }) {
  // Рендеринг поста...
}
 
// Эта функция вызывается на этапе сборки
export async function getStaticPaths() {
  // Вызов внешнего API для получения постов
  const res = await fetch('https://.../posts');
  const posts = await res.json();
 
  // Получаем пути для предварительного рендеринга на основе постов
  const paths = posts.map((post) => `/posts/${post.id}`);
 
  // Предварительно рендерим только эти пути на этапе сборки.
  // { fallback: false } означает, что другие маршруты должны вернуть 404.
  return { paths, fallback: false };
}
 
// Эта функция также вызывается на этапе сборки
export async function getStaticProps({ params }) {
  // params содержит id поста.
  // Если маршрут /posts/1, то params.id будет 1
  const res = await fetch(`https://.../posts/${params.id}`);
  const post = await res.json();
 
  // Передаём данные поста в страницу через props
  return { props: { post } };
}
 
export default Post;

Fallback: true

Если fallback равен true, поведение getStaticProps меняется: Next.js отрендерит указанные пути в HTML на этапе сборки. Если путь не был сгенерирован на этапе сборки, он будет создан по требованию при запросе пользователя.

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

Пользователь, запросивший генерацию страницы, получит fallback HTML — обычно страницу с состоянием загрузки. Это связано с тем, что статический HTML можно отдавать через CDN, обеспечивая быструю загрузку страницы, даже если она ещё не сгенерирована.

Пример по требованию статической генерации дополнительных страниц:

pages/posts/[id].js
import { useRouter } from 'next/router';
import fetch from 'node-fetch';
 
function Post({ post }) {
  const router = useRouter();
 
  // Если страница ещё не сгенерирована, будет отображён
  // fallback до завершения работы getStaticProps()
  if (router.isFallback) {
    return <div>Загрузка...</div>;
  }
 
  // Рендеринг поста...
}
 
// Эта функция вызывается на этапе сборки
export async function getStaticPaths() {
  return {
    // Только `/posts/1` и `/posts/2` генерируются на этапе сборки
    paths: [{ params: { id: 1 } }, { params: { id: 2 } }],
    // Включение статической генерации дополнительных страниц
    // Например: `/posts/3`
    fallback: true,
  };
}
 
// Эта функция также вызывается на этапе сборки
export async function getStaticProps({ params }) {
  // params содержит id поста.
  // Если маршрут /posts/1, то params.id будет 1
  const res = await fetch(`https://.../posts/${params.id}`);
  const post = await res.json();
 
  // Передаём данные поста в страницу через props
  return { props: { post } };
}
 
export default Post;

Подробнее о getStaticPaths см. в документации по получению данных.

getServerSideProps

Если вы экспортируете асинхронную функцию getServerSideProps из страницы, Next.js будет рендерить эту страницу при каждом запросе (SSR, рендеринг на стороне сервера).

getServerSideProps всегда выполняется на сервере, и код автоматически исключается (tree-shaken) из клиентских сборок, что уменьшает объем кода, передаваемого в браузер. Это позволяет не беспокоиться о выполнении кода получения данных как на сервере, так и в браузере, где есть некоторые различия. Во многих случаях это повышает производительность, так как сервер обычно имеет более быстрое соединение с источником данных. Также это повышает безопасность, так как меньше логики получения данных становится доступной клиенту.

Это позволяет использовать любые асинхронные или даже синхронные методы получения данных, включая fetch, REST, GraphQL или даже прямой доступ к базе данных.

При переходе между страницами с помощью next/link вместо выполнения getServerSideProps в браузере Next.js выполнит запрос к серверу, который вернет результат вызова getServerSideProps.

pages/index.js
export async function getServerSideProps(context) {
  return {
    props: {}, // будет передано в компонент страницы как props
  };
}

Параметр context — это объект, содержащий следующие ключи:

Вот пример использования getServerSideProps для получения данных во время запроса и их отображения:

pages/index.js
function Page({ data }) {
  // Рендеринг данных...
}
 
// Вызывается при каждом запросе
export async function getServerSideProps() {
  // Получение данных из внешнего API
  const res = await fetch(`https://.../data`);
  const data = await res.json();
 
  // Передача данных в страницу через props
  return { props: { data } };
}
 
export default Page;

Подробнее о getServerSideProps см. в документации по получению данных.

Режим предпросмотра (Preview Mode)

Как обсуждалось ранее, статическая генерация (Static Generation) полезна, когда страницы получают данные из headless CMS. Однако это не идеально, когда вы пишете черновик в headless CMS и хотите сразу же просмотреть его на странице. Поскольку вывод статичен, предпросмотр изменений усложняется, так как вам придется перегенерировать статическую страницу.

Введение getStaticProps в Next.js открывает новые возможности, такие как использование возможностей рендеринга по требованию в определенных условиях.

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

Мы рады объявить о новой встроенной функции Next.js для решения этой задачи: режиме предпросмотра (Preview Mode).

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

Однако вы не ограничены определенными CMS. Режим предпросмотра интегрируется как с getStaticProps, так и с getServerSideProps, поэтому его можно использовать с любым решением для получения данных.

Режим предпросмотра уже доступен при использовании next start или при развертывании в Vercel Edge Network.

Попробуйте режим предпросмотра на https://next-preview.vercel.app/

Подробнее о режиме предпросмотра см. в документации.

Сотрудничество с провайдерами CMS

getStaticProps позволяет получать данные из любого источника, включая CMS.

Мы активно сотрудничаем со многими ключевыми игроками в экосистеме CMS, чтобы предоставить примеры и руководства по интеграции с Next.js.

Примеры, над которыми сейчас ведется работа:

Если ваша компания работает в экосистеме CMS, мы будем рады сотрудничеству! Свяжитесь с нашей командой по email или Twitter.

Встроенная поддержка глобальных стилей Sass

Next.js 9.2 представил встроенную поддержку глобальных CSS-стилей для замены плагина next-css с лучшими настройками для более оптимизированного результата.

Сразу после выпуска нам все чаще стали поступать запросы о добавлении поддержки Sass, так как многие компании, переходящие на Next.js, имеют существующую дизайн-систему на основе Sass.

Изучив использование плагинов Next.js, мы обнаружили, что примерно 30% приложений Next.js используют next-sass. По сравнению с 44%, использующими чистый CSS, и 6%, использующими Less.

Кроме того, next-sass имел те же недостатки, что и next-css. Это означало, что вы могли импортировать файл Sass в любой файл проекта, однако этот импортированный файл Sass был глобальным для всего приложения.

Учитывая эту статистику и отзывы, мы рады объявить, что Next.js теперь имеет встроенную поддержку импорта таблиц стилей Sass.

Чтобы начать использовать глобальные импорты Sass в вашем приложении, установите sass:

Terminal
npm install sass

Затем импортируйте файл Sass в pages/_app.js.

Например, рассмотрим следующую таблицу стилей styles.scss в корне вашего проекта:

$primary-color: #333;
 
body {
  padding: 20px 20px 60px;
  margin: 0;
  color: $primary-color;
}

Создайте файл pages/_app.js, если его еще нет. Затем импортируйте файл styles.scss:

pages/_app.js
import '../styles.scss';
 
// Этот экспорт по умолчанию обязателен в новом файле `pages/_app.js`.
export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

Поскольку таблицы стилей по своей природе глобальны, они должны быть импортированы в пользовательский компонент <App>. Это необходимо, чтобы избежать конфликтов имен классов и порядка для глобальных стилей.

В режиме разработки такой способ определения стилей позволяет автоматически обновлять стили на странице по мере их редактирования.

В продакшене все файлы Sass и CSS автоматически объединяются в один минифицированный файл .css. Этот файл CSS загружается через тег <link> и автоматически встраивается в HTML-разметку, генерируемую Next.js по умолчанию.

Эта новая функция полностью обратно совместима. Если вы используете @zeit/next-sass или другие плагины, связанные с CSS, функция отключается, чтобы избежать конфликтов.

Если вы сейчас используете @zeit/next-sass, мы рекомендуем удалить плагин из next.config.js и package.json, перейдя на встроенную поддержку Sass после обновления.

Встроенная поддержка CSS Modules для Sass на уровне компонентов

Next.js теперь поддерживает CSS Modules с файлами Sass, используя соглашение об именовании файлов [name].module.scss.

В отличие от поддержки, ранее доступной в Next.js 5+ с next-sass, глобальные Sass и CSS Modules теперь могут сосуществоватьnext-sass требовал, чтобы все файлы .scss в приложении обрабатывались как глобальные или локальные, но не оба сразу.

CSS Modules локально ограничивают область видимости Sass, автоматически создавая уникальные имена классов. Это позволяет использовать одно и то же имя класса Sass в разных файлах без риска конфликтов.

Такое поведение делает CSS Modules идеальным способом включения Sass на уровне компонентов. Файлы CSS Modules могут быть импортированы в любом месте вашего приложения.

Чтобы начать использовать Sass CSS Modules в вашем приложении, убедитесь, что у вас установлен sass:

Terminal
npm install sass

Теперь рассмотрим повторно используемый компонент Button в папке components/:

Сначала создайте components/Button.module.scss со следующим содержимым:

/*
Вам не нужно беспокоиться о конфликте .error {} с другими файлами
`.css` или `.module.css`!
*/
$color: white;
 
.error {
  color: $color;
  background-color: red;
}

Затем создайте components/Button.js, импортируя и используя приведенный выше CSS-файл:

components/Button.js
import styles from './Button.module.scss';
 
export function Button() {
  return (
    <button
      type="button"
      // Обратите внимание, как класс "error" доступен как свойство
      // импортированного объекта `styles`.
      className={styles.error}
    >
      Destroy
    </button>
  );
}

CSS Modules для файлов Sass — это опциональная функция и работает только для файлов с расширением .module.scss. Обычные таблицы стилей <link> и глобальные стили Sass по-прежнему поддерживаются.

В продакшене все файлы CSS Modules автоматически объединяются в множество минифицированных и разделенных на части файлов .css. Эти файлы .css представляют горячие пути выполнения в вашем приложении, гарантируя загрузку минимального количества CSS для отрисовки каждой страницы.

Как и выше, эта новая функция полностью обратно совместима. Если вы используете @zeit/next-sass или другие плагины, связанные с CSS, функция отключается, чтобы избежать конфликтов.

Если вы сейчас используете @zeit/next-sass, мы рекомендуем удалить плагин из next.config.js и package.json, перейдя на встроенную поддержку Sass.

Автоматическая статическая оптимизация для 404

Выпуск Next.js 9 представил концепцию автоматической статической оптимизации: если страница не требует блокирующих данных, Next.js автоматически генерирует страницу как статический HTML во время сборки. Однако была одна страница, которая не рендерилась автоматически как статическая: страница 404. Основная причина, по которой страница 404 не делалась статической автоматически, заключалась в том, что страница /_error, отвечающая за 404, обрабатывала не только 404, но и другие ошибки.

Учитывая, что страницы 404 рендерятся для несуществующих маршрутов, рендеринг страницы по требованию может привести к увеличению затрат и нагрузки на сервер.

Мы поставили перед собой цель сделать это удобным двумя способами:

  • Опыт Next.js по умолчанию генерирует статическую страницу 404
  • При настройке страницы 404 по-прежнему гарантируется, что вы получите статическую страницу

Эта функция полностью обратно совместима, поэтому если у вас есть пользовательский pages/_error.js, он будет использоваться для страницы 404 до тех пор, пока вы не добавите pages/404.js.

Статическая страница 404 по умолчанию

Если в вашем приложении нет пользовательской страницы pages/_error.js, Next.js автоматически сгенерирует статическую страницу 404 и будет использовать ее при необходимости отображения 404. Это происходит автоматически, и никаких изменений не требуется.

Пользовательская страница 404 с использованием pages/404.js

Чтобы переопределить страницу 404 по умолчанию, вы можете создать pages/404.js, который по-прежнему будет автоматически статически оптимизирован во время сборки. Эта страница используется вместо pages/_error.js для рендеринга 404, если ваше приложение имеет такую страницу.

pages/404.js
export default () => <h1>Это страница 404</h1>;

На 32+ КБ меньше рантайма (15+ КБ в Gzip)

Next.js поддерживает те же браузеры, что и React, без необходимости дополнительной настройки. Это включает Internet Explorer 11 (IE11) и все популярные браузеры (Edge, Firefox, Chrome, Safari, Opera и др.).

В рамках этой совместимости мы также компилируем ваше приложение для работы с IE11: это позволяет безопасно использовать синтаксические возможности ES6+, Async/Await, Object Rest/Spread Properties и многое другое — все без необходимости настройки.

Часть этого процесса компиляции также включает прозрачную вставку необходимых полифиллов (например, Array.from или Symbol). Однако эти полифиллы нужны только для менее чем 10% веб-трафика, в основном для поддержки IE11.

Начиная с Next.js 9.3, Next.js будет автоматически загружать полифиллы, необходимые для поддержки старых браузеров, и только в этих старых браузерах.

На практике это означает, что 32 КБ или более будут исключены из размера Первой загрузки для 90%+ ваших пользователей.

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

Эта оптимизация полностью автоматическая, и для ее использования не требуется изменять приложение!

Сообщество

Мы очень рады видеть продолжающийся рост популярности Next.js:

  • У нас было более 927 независимых участников.
  • На GitHub проект получил более 46,600 звезд.
  • В директории примеров представлено более 226 примеров.

Сообщество Next.js теперь насчитывает более 15,250 участников. Теперь сообщество можно найти в обсуждениях GitHub — новом месте для обсуждений и вопросов! Присоединяйтесь!

Мы благодарны нашему сообществу и всем внешним отзывам и вкладам, которые помогли сформировать этот релиз.

Особая благодарность Jeff Escalante за ценные отзывы о новых методах получения данных.

Огромное спасибо всем, кто внес свой вклад в этот релиз: @arcanis, @lgordey, @ijjk, @martpie, @jaywink, @fabianishere, @dijs, @TheRusskiy, @quinnturner, @timneutkens, @lfades, @vvo, @adithwip, @rafaelalmeidatk, @bmathews, @Spy-Seth, @EvgeniyKumachev, @chibicode, @piglovesyou, @HaNdTriX, @Timer, @janicklas-ralph, @devknoll, @prateekbh, @ethanryan, @MoOx, @rifaidev, @msweeneydev, @motiko, и @balazsorban44 за помощь!