Оптимизация изображений в 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 месяца практики. Доступ к материалам — навсегда.
Комментарии