⏭️ Как быстро разобраться в Next.js 14: 5 советов опытного фронтендера

Next.js – мощный фреймворк для создания веб-приложений на React: он поддерживает статическую генерацию сайтов, серверный и клиентский рендеринг. Это делает его идеальным выбором для проектов, которым необходима высокая производительность, SEO-дружественность и масштабируемость. Делимся пятью советами, которые помогут быстро разобраться в тонкостях работы с изображениями, переменными окружения, кэшированием и управлением компонентами в Next.js.

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

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

Локальные изображения

При использовании локальных изображений в Next.js не нужно указывать атрибуты width и height – Next.js автоматически вычисляет эти значения, предотвращая искажение страницы. Просто поместите изображения в директорию public, импортируйте их и выполните рендеринг с помощью компонента next/image. Можно использовать placeholder="blur" для показа размытых версий изображений, пока картинки не загрузятся полностью:

import Image from "next/image";
import example from "@/public/example.jpg";

export default function Home() {
  return (
    <main className="flex min-h-screen flex-col items-center justify-center">
      <h1>Название изображения</h1>
      <Image 
       src={example} 
       alt="Описание изображения" 
       placeholder="blur"/>
    </main>
  );
}

Удаленные изображения

При загрузке удаленных изображений необходимо указывать ширину и высоту изображений:

import Image from "next/image";
import example from "@/public/example.jpg";

export default function Home() {
  const remoteImg = (
    <Image
      src="https://cdnservice.com/my-media/example.jpg"
      alt="Описание изображения"
      width={500}
      height={300}
    />
  );

  return (
    <main className="flex min-h-screen flex-col items-center justify-center">
      <h1>Название изображения</h1>
      {remoteImg}
    </main>
  );
}

Если нужно показывать размытые плейсхолдеры, придется установить специальные пакеты – sharp и plaiceholder (именно так, это не опечатка):

npm install sharp plaiceholder @plaiceholder/next

И создать вспомогательную функцию:

import placeholder from 'placeholder';
import sharp from 'sharp';

export async function getBase64(url) {
  const res = await fetch(url);
  const buffer = await res.arrayBuffer();
  const base64 = await placeholder(buffer);
  return base64;
}

Функция getBase64 загружает изображение по указанному URL, преобразует его в формат ArrayBuffer, и использует библиотеку plaiceholder для создания размытой версии изображения:

import { getBase64 } from './utils/getBase64';

export default async function Home() {
  const blurData = await getBase64('https://my-cdn.com/my-account/example.jpg');
  return (
    <Image
      src="https://my-cdn.com/my-account/example.jpg"
      width={800}
      height={600}
      alt="Описание изображения"
      placeholder="blur"
      blurDataURL={blurData}
    />
  );
}
👨‍💻🎨 Библиотека фронтендера
Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека фронтендера»

Использование переменных окружения

Будьте осторожны с префиксом NEXT_PUBLIC – он раскрывает всю информацию в браузере:

NEXT_PUBLIC_API_URL=https://api.example.com

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

API_SECRET_KEY=your-secret-key

Кэширование в среде разработке и в продакшене

Next.js использует разные подходы к кэшированию в зависимости от среды (разработка или продакшн):

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

Управлять кэшированием можно с помощью опции revalidate для инкрементальной статической регенерации или force-dynamic для постоянного получения свежих данных. В первом примере страница будет обновляться каждые 5 секунд, во втором случае статическая оптимизация отключается, обновление будет происходить динамически:

// Повторная валидация каждые 5 секунд
export const revalidate = 5;

// Принудительное динамическое получение данных
export const dynamic = 'force-dynamic';

Получение данных в серверных компонентах

Вместо того, чтобы создавать отдельный API-маршрут для получения данных, а затем вызывать этот маршрут из серверного компонента, рекомендуется получать данные напрямую. Этот подход позволяет Next.js оптимизировать кэширование и повторное использование данных в нескольких серверных компонентах:

export default async function Home() {
  const res = await fetch('https://api.example.com/jokes/random');
  const data = await res.json();
  return <div>{data.joke}</div>;
}

Если нужно использовать одну и ту же логику получения данных в нескольких местах, нужно создать серверное действие:

// server/getJoke.js
import { server } from 'next/server';

export async function getJoke() {
  const res = await fetch('https://api.example.com/jokes/random');
  const data = await res.json();
  return data;
}

// Home.js
import { getJoke } from '../server/getJoke';

export default async function Home() {
  const joke = await getJoke();
  return <div>{joke.joke}</div>;
}

Серверные и клиентские компоненты

Next.js использует два типа компонентов – серверные и клиентские.

Серверные компоненты

  • По умолчанию все страницы в Next.js являются серверными компонентами.
  • Они выполняются на сервере и отправляют готовый HTML клиенту.
  • Могут иметь прямой доступ к серверным ресурсам (базе данных, файловой системе), но не имеют доступа к клиентскому API (useState, useEffect и т. д.)
  • Отличаются быстрой начальной загрузкой и SEO-дружественностью.
  • Серверные компоненты могут включать в себя клиентские (для интерактивности).

Клиентские компоненты

  • Выполняются на стороне пользователя, могут использовать браузерные API (например, localStorage).
  • Они могут быть интерактивными (формы, кнопки, анимации) и использовать все возможности React в браузере.
  • Подходят для создания компонентов, которые должны обновляться без перезагрузки страницы.
  • Чтобы сделать компонент клиентским, нужно добавить директиву "use client":
"use client";
import { useState } from "react";

export default function ClientComponent() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Счет: {count}</p>
      <button onClick={() => setCount(count + 1)}>Увеличить счет</button>
    </div>
  );
}

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

// Клиентский компонент
'use client';
import { useState } from 'react';

export default function ClientComponent() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

// Серверный компонент
import ClientComponent from '../components/ClientComponent';

export default function Home() {
  return (
    <div>
      <ClientComponent />
    </div>
  );
}

Особенности взаимодействия компонентов

Вложенность

  • Когда вы рендерите клиентский компонент внутри серверного, он остается клиентским.
  • Все дочерние компоненты клиентского компонента автоматически становятся клиентскими, даже без директивы "use client".

Работа с провайдерами

При работе с провайдерами (например, для тем), важно правильно обрабатывать дочерние компоненты, чтобы они сохраняли свой статус серверных или клиентских компонентов. В этом примере Theme остается серверным компонентом:

// theme.js
import { ThemeProvider } from 'styled-components';

export default function Theme({ children }) {
  return <ThemeProvider theme={{}}>{children}</ThemeProvider>;
}

// layout.js
import Theme from '../components/theme';

export default function Layout({ children }) {
  return (
    <Theme>
      {children}
    </Theme>
  );
}

В заключение

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

***

При подготовке статьи использовалась информация из видео:

***

💻 Для тех, кто хочет войти во frontend-разработку

Запустили базовый курс Frontend Basic с фокусом на реальные задачи:

  • Создадите интернет-магазин с нуля
  • Освоите связку HTML + CSS + JavaScript
  • Научитесь работать с React и Git
  • Получите код-ревью от разработчиков из Газпромбанка

26 уроков, 28 заданий, 2 месяца практики. Доступ к материалам — навсегда.

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ