Привет, это Эмиль, тимлид группы frontend‑разработчиков.

Хочу поделиться с вами несколькими рекомендациями, как увеличить производительность приложений на Next.js. Статья будет полезна junior‑ и middle‑ разработчикам. Поехали!

Применение Next.js и почему возникает необходимость в увеличении скорости

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

Скорость приложения влияет на время загрузки страницы (предоставление клиенту кода приложения, данных и стилей) и как следствие на UX. В случае необходимости отправки дополнительных ресурсов сервером, таких как изображения, производительность снижается. Для избежания данных проблем рекомендуем несколько способов увеличения скорости приложений Next.js.

Применение рендеринга на стороне сервера

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

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

Пример:

export async function getServerSideProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  };
}

Рендеринг выполняется только на стороне сервера и никогда не запускается в браузере. Если страница использует getServerSideProps, то при запросе этой страницы на стороне клиента Next.js отправляет запрос API на сервер, который запускает его. Происходит извлечение данных из API и возврат полученных данных компоненту страницы в качестве атрибута.

Пример:

// This function will be called by the server

export async function getServerSideProps({ context }) {
  // Fetch data from external API

  const data = await fetch(YOUR_API);

  // Returning the fetched data

  return { props: { data } };
}



function SSRPage({ data }) {
  // Displaying the data to the client

  return <div>{data}</div>;
}

export default SSRPage;

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

Динамический импорт

При начальной загрузке приложением загружаются все необходимые компоненты и CSS. Использование динамического импорта позволяет разбить код на фрагменты и загружать по необходимости. По мере использования клиентом приложения, компоненты, с которыми не взаимодействует клиент, не будут загружены. Значительно повышается производительность, в особенности на мобильных устройствах. Также это сокращает время начальной загрузки и объем подключаемого пакета данных. В случае, когда пользователь не вошел в систему, доступно отложить загрузку компонента входа.

Для использования динамического импорта необходимо импортировать код с помощью динамического импорта ES2020. Динамический импорт позволяет условно импортировать файлы когда они нам нужны, для этого необходимо вызвать ключевое слово import и передать ему путь к нужному файлу. Также данная функция возвращает обещание, и с ней возможно использовать .then и .catch.

Пример:

import dynamic from 'next/dynamic';
import SimpleButton from '../components/Buttons';

const DynamicComponent = dynamic(() => import('../components/LoginButton'));

function Program() {
  return (
    <div>
      <SimpleButton />

      <DynamicComponent />
    </div>
  );
}

export default Program;

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

Кэширование часто используемого содержимого

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

Пример:

export default function handler(req, res) {
  res.setHeader('Cache-Control', 's-maxage=10');
}

Для рендеринга на стороне сервера:

export async function getServerSideProps({ req, res }) {
  res.setHeader(
    'Cache-Control',
    'public, s-maxage=10, stale-while-revalidate=59'
  );
  return {
    props: {},
  };
}

Next.js  автоматически добавляет статические файлы и ресурсы, нет необходимости добавлять кэширование вручную.

Удаление неиспользуемых зависимостей

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

Если проект небольшой, можно легко найти неиспользуемые зависимости и удалить их из package.json файла. В случае большого проекта с большим количеством различных зависимостей, возникают трудности в определении неиспользуемых. В данном случае используется depcheck‑npm.

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

Оптимизация изображений

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

Процесс проходит в 2 этапа:

  1. изменение изображения до меньшего размера;

  2. сохранение в формате jpeg — для фотографий, png — для графики.

Next.js имеет встроенный компонент next/image, который может использоваться вместо собственного <img> компонента.

Пример: import Image from 'next/image'

function OptimizedImage() {
  return (
    <>
      <h1>Next.js Image</h1>

      <Image
        src={image_url}
        alt="Any Text"
        width={500}
        height={500}
        blurDataURL="URL"
        placeholder="blur"
      />
    </>
  );
}

Преимущества next/image компонента:

  • Отложенная загрузка - процесс загрузки изображения происходит только тогда, когда оно видно в окне клиента. По умолчанию компонент избирательно загружает изображения, что сокращает время загрузки. Если нет необходимости откладывать загрузку изображения, необходимо отключить priority={true}.

  • Изображения заполнители - используя next/image компонент, существует возможность добавить размытый заполнитель для любого изображения, используя placeholder атрибут.

  • Предварительная загрузка изображений - при наличии нескольких изображений на странице возможно установить приоритет загрузки с помощью next/image. 

Оптимизация скриптов

Дополнительно многие приложения используют сторонние скрипты, например Google Analytics, Google AdSense и др., они могут еще больше замедлять Next.js приложения. Вместо использования <script> тега по умолчанию, лучше использовать next/script компонент Next.js. Это позволит установить приоритет загрузки для сторонних скриптов.

Пример:

import Script from 'next/script';

export default function OptimizedScript() {
  return (
    <>
      <Script
        id="YOUR_ID"
        src="URL"
        onError={(err) => {
          console.error('Error', err);
        }}
        onLoad={() => {
          // Function to perform after loading the script
        }}
      />
    </>
  );
}

При использовании strategyprop в next/script компоненте, можно использовать три подхода к загрузке скрипта:

  • afterInteractive — скрипт будет загружен на стороне клиента после того, как страница станет интерактивной;

  • beforeInteractive — скрипт будет загружен на стороне сервера до того, как будет выполнен JavaScript;

  • lazyOnload — скрипт будет загружен после загрузки всех других ресурсов.

Выводы

Использование рекомендаций по увеличению скорости позволит создавать оптимизированные JavaScript приложения, используя преимущества Next.js.

Желаю успехов в изучении Next.js и быстрых вам приложений!