БлогУслугиКарьера
Обсудить проект
БлогУслугиКарьераОбсудить проект
React / Next.js

Оптимизация Next.js: от 50 до 100 баллов PageSpeed за 1 день

Пошаговый гайд по оптимизации Next.js проекта: шрифты, изображения, бандл, CSS, third-party скрипты. Реальный кейс с ростом с 50 до 98 баллов.

Редакция Feature
Редакция Feature·
22 мар
·
14 мин
·
Оптимизация Next.js: от 50 до 100 баллов PageSpeed за 1 день

Почему PageSpeed важен для бизнеса

Скорость загрузки напрямую влияет на конверсию и выручку. По данным Google, при увеличении времени загрузки с 1 до 3 секунд вероятность отказа растёт на 32%. При увеличении до 5 секунд — на 90%. Для e-commerce каждая секунда задержки снижает конверсию на 7%.

Google использует Core Web Vitals как фактор ранжирования. Сайты с плохими показателями получают меньше органического трафика. Подробнее о влиянии технических факторов на позиции в поиске читайте в нашей статье о SEO-продвижении.

Next.js — один из лучших фреймворков для производительных сайтов. Но из коробки он не гарантирует 100 баллов. Нужна осознанная оптимизация. В этой статье мы покажем пошаговый процесс, который мы применяли на реальном проекте, подняв PageSpeed с 50 до 98 баллов за один рабочий день.

Шаг 0: Аудит текущего состояния

Инструменты для аудита

Прежде чем что-то оптимизировать, нужно точно измерить текущее состояние. Мы используем три инструмента:

Google Lighthouse (встроен в Chrome DevTools) — даёт общую оценку и рекомендации. Запускайте в режиме Incognito, чтобы расширения браузера не влияли на результат.

WebPageTest (webpagetest.org) — показывает waterfall загрузки, размеры ресурсов, порядок их загрузки. Позволяет тестировать с разных локаций и устройств.

PageSpeed Insights (pagespeed.web.dev) — использует реальные данные пользователей (CrUX) вместе с лабораторными тестами. Показывает, как сайт работает у настоящих пользователей.

Core Web Vitals: что измеряем

Метрика Что измеряет Хорошо Нужно улучшить Плохо
LCP (Largest Contentful Paint) Загрузка главного контента < 2.5 сек 2.5–4 сек > 4 сек
INP (Interaction to Next Paint) Отзывчивость интерфейса < 200 мс 200–500 мс > 500 мс
CLS (Cumulative Layout Shift) Визуальная стабильность < 0.1 0.1–0.25 > 0.25

Наш исходный результат

На проекте, о котором пойдёт речь, начальные показатели были следующими:

  • PageSpeed Mobile: 48/100
  • LCP: 5.2 секунды
  • INP: 380 мс
  • CLS: 0.34
  • Общий размер JS: 412 KB (gzip)
  • Время до полной загрузки: 8.7 секунд (3G)

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

Изображения — обычно самый большой ресурс на странице и главная причина медленного LCP. Next.js предоставляет мощный компонент next/image, но его нужно использовать правильно.

Используйте next/image для всех изображений

// ПЛОХО: обычный img-тег
<img src="/hero.jpg" alt="Hero"/>;

// ХОРОШО: next/image с оптимизацией
import Image from "next/image";

<Image
    src="/hero.jpg"
    alt="Hero"
    width={1200}
    height={600}
    priority // для LCP-изображения
    sizes="(max-width: 768px) 100vw, 1200px"
/>;

Настройка форматов и качества

Next.js автоматически конвертирует изображения в WebP. Для ещё лучшего сжатия включите AVIF:

// next.config.js
const nextConfig = {
    images: {
        formats: ["image/avif", "image/webp"], // Настраиваем quality — баланс между размером и качеством
        quality: 80, // Определяем размеры для responsive
        deviceSizes: [640, 750, 828, 1080, 1200, 1920], imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
    },
};

Blur Placeholder для мгновенного отображения

Blur placeholder показывает размытую версию изображения, пока оригинал загружается. Это убирает «прыжки» контента (CLS) и создаёт ощущение мгновенной загрузки:

import Image from 'next/image';
import heroImage from '@/public/hero.jpg'; // статический импорт

// Для статических изображений blur генерируется автоматически
<Image
    src={heroImage}
    alt="Hero"
    placeholder="blur"
    priority
/>

// Для динамических — укажите blurDataURL вручную
<Image
    src={product.imageUrl}
    alt={product.name}
    width={400}
    height={300}
    placeholder="blur"
    blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD..."
/>

Lazy Loading

По умолчанию next/image использует lazy loading — изображения загружаются только при приближении к viewport. Для LCP-изображения (первое большое изображение на странице) отключите lazy loading через priority:

// LCP-изображение — загружается сразу
<Image src="/hero.jpg" alt="Hero" priority/>

// Остальные изображения — загружаются лениво (по умолчанию)
<Image src="/product.jpg" alt="Product"/>

Результат шага

  • Размер изображений: уменьшился с 2.4 MB до 380 KB
  • LCP: улучшился с 5.2 до 3.1 секунды
  • CLS: снизился с 0.34 до 0.08

Шаг 2: Оптимизация шрифтов

Шрифты — вторая по значимости причина плохого LCP и CLS. Неоптимизированные шрифты вызывают FOUT (Flash of Unstyled Text) или FOIT (Flash of Invisible Text), а их загрузка блокирует рендеринг.

Используйте next/font

next/font загружает шрифты на этапе сборки и self-hosting'ит их. Никаких запросов к Google Fonts в рантайме:

// app/layout.tsx
import {Inter, Roboto_Mono} from "next/font/google";

const inter = Inter({
    subsets: ["latin", "cyrillic"],
    display: "swap",
    variable: "--font-inter",
});

const robotoMono = Roboto_Mono({
    subsets: ["latin"],
    display: "swap",
    variable: "--font-roboto-mono",
});

export default function RootLayout({children}) {
    return (
        <html lang="ru" className={`${inter.variable} ${robotoMono.variable}`}>
        <body className={inter.className}>{children}</body>
        </html>
    );
}

Локальные шрифты

Если используете кастомные шрифты, загрузите их через next/font/local:

import localFont from "next/font/local";

const myFont = localFont({
    src: [
        {path: "./fonts/MyFont-Regular.woff2", weight: "400", style: "normal"},
        {path: "./fonts/MyFont-Bold.woff2", weight: "700", style: "normal"},
    ],
    display: "swap",
    variable: "--font-my",
});

Подключение только нужных подмножеств

Кириллический подмножество (cyrillic) значительно меньше полного набора символов. Всегда указывайте только нужные subsets.

Результат шага

  • Размер шрифтов: с 180 KB до 45 KB
  • CLS от шрифтов: 0 (font-display: swap + size-adjust)

Сайт тормозит?

Проведём аудит и оптимизируем до 90+ баллов PageSpeed

Заказать оптимизацию

Шаг 3: Оптимизация JavaScript-бандла

Анализ бандла

Первый шаг — понять, что занимает больше всего места. Установите bundle-analyzer:

npm install -D @next/bundle-analyzer
// next.config.js
const withBundleAnalyzer = require("@next/bundle-analyzer")({
    enabled: process.env.ANALYZE === "true",
});

module.exports = withBundleAnalyzer({
    // остальная конфигурация
});
ANALYZE=true npm run build

Это откроет интерактивную карту бандла в браузере. Обычно самые большие «виновники» — это UI-библиотеки, библиотеки дат (moment.js, date-fns), иконки и аналитика.

Dynamic Imports (Code Splitting)

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

import dynamic from "next/dynamic";

// Модальное окно — загружается только при открытии
const Modal = dynamic(() => import("@/components/modal"), {
    loading: () => <div className="animate-pulse h-64 bg-gray-200 rounded"/>,
});

// Редактор — загружается только на странице редактирования
const RichEditor = dynamic(() => import("@/components/rich-editor"), {
    ssr: false, // клиентский компонент, не нужен SSR
    loading: () => <EditorSkeleton/>,
});

// Компонент графиков — тяжёлая библиотека
const Chart = dynamic(() => import("@/components/chart"), {
    ssr: false,
});

Tree Shaking

Убедитесь, что используете именованные импорты вместо импорта всего модуля:

// ПЛОХО: импортирует всю библиотеку (200+ KB)
import _ from "lodash";

const sorted = _.sortBy(items, "name");

// ХОРОШО: импортирует только одну функцию (2 KB)
import sortBy from "lodash/sortBy";

const sorted = sortBy(items, "name");

// ЕЩЁ ЛУЧШЕ: нативный JS
const sorted = [...items].sort((a, b) => a.name.localeCompare(b.name));

Перенос вычислений на сервер

Если компонент использует тяжёлую библиотеку только для рендера, сделайте его Server Component. Например, подсветка синтаксиса, рендеринг Markdown, форматирование дат. Код библиотеки не попадёт в клиентский бандл. Подробнее об этом подходе читайте в нашей статье Server Components vs Client Components.

Результат шага

  • Размер JS (gzip): с 412 KB до 127 KB
  • INP: улучшился с 380 мс до 160 мс

Шаг 4: Оптимизация CSS

Tailwind CSS: минимизация

Если используете Tailwind CSS, убедитесь, что в production включён purge неиспользуемых классов. В Tailwind v3+ это работает автоматически через content конфигурацию:

// tailwind.config.js
module.exports = {
    content: ["./app/**/*.{js,ts,jsx,tsx,mdx}", "./components/**/*.{js,ts,jsx,tsx,mdx}", // Не добавляйте node_modules!
    ], theme: {
        extend: {},
    },
};

Critical CSS

Next.js автоматически инлайнит критический CSS при использовании CSS Modules или Tailwind. Но если вы подключаете глобальные стили, они могут блокировать рендеринг.

// ПЛОХО: тяжёлый глобальный CSS-файл
import "@/styles/everything.css"; // 150 KB

// ХОРОШО: разделите стили по маршрутам
// app/layout.tsx
import "@/styles/base.css"; // 5 KB — только базовые стили

// app/dashboard/layout.tsx
import "@/styles/dashboard.css"; // 20 KB — только для дашборда

Удаление неиспользуемых CSS-библиотек

Проверьте, не подключены ли CSS-фреймворки, которые вы уже не используете. Часто в проектах остаётся подключённый Bootstrap или Material UI CSS при переходе на Tailwind.

Результат шага

  • Размер CSS: с 89 KB до 18 KB
  • Время блокировки рендера CSS: с 800 мс до 120 мс

Шаг 5: Оптимизация сторонних скриптов

Проблема

Аналитика, чат-виджеты, рекламные скрипты и пиксели ретаргетинга — всё это загружается синхронно и блокирует рендеринг. Один Google Tag Manager может добавить 200+ мс к LCP.

Используйте next/script

Next.js предоставляет компонент Script с разными стратегиями загрузки:

import Script from "next/script";

export default function RootLayout({children}) {
    return (
        <html lang="ru">
        <body>
        {children}

        {/* afterInteractive — загружается после гидрации (по умолчанию) */}
        <Script
            src="https://www.googletagmanager.com/gtag/js?id=G-XXXXX"
            strategy="afterInteractive"
        />

        {/* lazyOnload — загружается в фоне после полной загрузки */}
        <Script
            src="https://widget.intercom.io/widget/xxxxx"
            strategy="lazyOnload"
        />

        {/* worker — выполняется в Web Worker (через Partytown) */}
        <Script
            src="https://www.google-analytics.com/analytics.js"
            strategy="worker"
        />
        </body>
        </html>
    );
}

Рекомендации по стратегиям

Скрипт Стратегия Почему
Google Analytics / GTM afterInteractive Нужен для трекинга навигации
Яндекс.Метрика afterInteractive Аналогично GA
Чат-виджеты lazyOnload Не критичны для первой загрузки
Рекламные пиксели lazyOnload Не влияют на UX
A/B-тестирование beforeInteractive Должен загрузиться до рендера

Результат шага

  • LCP: улучшился с 2.4 до 1.6 секунды
  • Total Blocking Time: с 450 мс до 90 мс

Шаг 6: Настройка кэширования

Заголовки кэширования

Настройте правильные cache-control заголовки для статических ресурсов:

// next.config.js
const nextConfig = {
    async headers() {
        return [{
            // Статические ассеты (JS, CSS, шрифты) — кэшируем надолго
            source: "/_next/static/:path*", headers: [{
                key: "Cache-Control", value: "public, max-age=31536000, immutable",
            },],
        }, {
            // Изображения — кэшируем на месяц
            source: "/images/:path*", headers: [{
                key: "Cache-Control", value: "public, max-age=2592000, stale-while-revalidate=86400",
            },],
        },];
    },
};

ISR (Incremental Static Regeneration)

Для страниц с контентом, который обновляется нечасто, используйте ISR:

// app/blog/[slug]/page.tsx
export const revalidate = 3600; // ревалидация каждый час

export default async function BlogPost({params}) {
    const {slug} = await params;
    const post = await getPost(slug);
    return <article>{/* ... */}</article>;
}

Обсудим ваш проект?

Оставьте контакты — перезвоним и обсудим задачу

Шаг 7: Дополнительные оптимизации

Prefetch ссылок

Next.js автоматически prefetch'ит ссылки, которые видны во viewport. Убедитесь, что используете next/link:

import Link from 'next/link';

// Prefetch включён по умолчанию для статических маршрутов
<Link href="/about">О компании</Link>

// Отключите для маршрутов, куда пользователь вряд ли перейдёт
<Link href="/terms" prefetch={false}>Условия использования</Link>

Оптимизация metadata

Используйте Metadata API Next.js для управления тегами head без клиентского JavaScript:

// app/layout.tsx
import type {Metadata} from "next";

export const metadata: Metadata = {
    title: {
        default: "My Site",
        template: "%s | My Site",
    },
    description: "Описание сайта",
    openGraph: {
        type: "website",
        locale: "ru_RU",
    },
};

Compression

Убедитесь, что на уровне хостинга включена Gzip или Brotli-компрессия. Vercel и большинство CDN делают это автоматически. Если используете собственный сервер:

// next.config.js
const nextConfig = {
    compress: true, // включает gzip-сжатие (по умолчанию true)
};

Итоговый кейс: до и после

Вот сводная таблица результатов оптимизации нашего проекта — интернет-магазина на Next.js 14 (App Router):

Метрика До оптимизации После оптимизации Изменение
PageSpeed Mobile 48 98 +50
PageSpeed Desktop 72 100 +28
LCP 5.2 сек 1.1 сек -79%
INP 380 мс 95 мс -75%
CLS 0.34 0.02 -94%
JS размер (gzip) 412 KB 127 KB -69%
CSS размер 89 KB 18 KB -80%
Изображения 2.4 MB 380 KB -84%
Время полной загрузки (3G) 8.7 сек 2.8 сек -68%

Распределение времени по шагам

Шаг Время Вклад в PageSpeed
Изображения 2 часа +18 баллов
Шрифты 30 мин +6 баллов
JS-бандл 3 часа +14 баллов
CSS 1 час +4 балла
Скрипты 1 час +5 баллов
Кэширование 30 мин +3 балла

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

Чек-лист для быстрой проверки

Перед каждым релизом проходите по этому чек-листу:

  • Все изображения используют next/image
  • LCP-изображение имеет атрибут priority
  • Шрифты загружены через next/font
  • font-display: swap установлен для всех шрифтов
  • Bundle analyzer показывает отсутствие лишних зависимостей
  • Тяжёлые компоненты загружаются через dynamic import
  • Tailwind content правильно настроен
  • Сторонние скрипты используют next/script с правильной стратегией
  • Cache-Control заголовки настроены для статических ресурсов
  • ISR или revalidate настроен для контентных страниц
  • Gzip/Brotli компрессия включена

Заключение

Оптимизация Next.js — это системный процесс, а не магия. Каждый шаг даёт измеримый результат, и при правильном подходе можно добиться 90+ баллов PageSpeed за один рабочий день. Ключевые направления: изображения, шрифты, бандл JavaScript и сторонние скрипты.

Не забывайте, что PageSpeed — это не разовая работа. С каждым новым функционалом нужно следить за тем, чтобы показатели не деградировали. Включите Lighthouse CI в ваш пайплайн деплоя и отслеживайте метрики в Google Search Console. О том, как организовать SEO-мониторинг для вашего проекта, мы рассказываем в отдельной статье.

Если вам нужна помощь с оптимизацией Next.js-проекта или полноценная разработка на Next.js с учётом производительности с первого дня, наша команда готова помочь.

Обсудим ваш проект?

Оставьте контакты — перезвоним и обсудим задачу

Содержание
  • Почему PageSpeed важен для бизнеса
  • Шаг 0: Аудит текущего состояния
  • Шаг 1: Оптимизация изображений
  • Шаг 2: Оптимизация шрифтов
  • Шаг 3: Оптимизация JavaScript-бандла
  • Шаг 4: Оптимизация CSS
  • Шаг 5: Оптимизация сторонних скриптов
  • Шаг 6: Настройка кэширования
  • Шаг 7: Дополнительные оптимизации
  • Итоговый кейс: до и после
  • Чек-лист для быстрой проверки
  • Заключение
Поделиться:

Похожие статьи

React Server Actions: практический гайд для продакшена
React / Next.js

React Server Actions: практический гайд для продакшена

13 мин
Server Components vs Client Components: когда что использовать
React / Next.js

Server Components vs Client Components: когда что использовать

12 мин
Next.js 16: что нового и как использовать в production
React / Next.js

Next.js 16: что нового и как использовать в production

14 мин
Feature IT

Feature IT — платформа по обучению программированию и разработке цифровых продуктов. Мы создаём современные веб-решения для бизнеса и обучаем этому других!

Политика конфиденциальностиПользовательское соглашение

О компании

  • Блог
  • Карьера

Услуги разработки

  • Разработка сайтов под ключ
  • Веб-приложения на React/Next.js
  • Telegram-боты для бизнеса
  • Mini Apps (Telegram, VK)
  • SEO-оптимизированные сайты
  • Автоматизация бизнес-процессов
  • Поддержка и развитие IT-продуктов

Обучение

  • Курс Python с нуля
  • Алгоритмы и структуры данных
  • Паттерны проектирования
  • Подготовка к собеседованиям в IT
  • Практика на реальных проектах

Инструменты

  • Генератор UTM-меток
  • Счётчик символов