Пошук уроків, статей та іншого контенту
Перевірите застосунок перед релізом за чеклістом зображень, шрифтів, бандла, метрик і production-налаштувань.
Перед релізом потрібно перевірити не лише час завантаження сторінки, а весь ланцюжок:
браузер отримує HTML;
завантажує критичні ресурси;
відображає найбільший елемент першого екрана;
реагує на дії користувача;
не змінює розмітку під час завантаження;
не завантажує зайвий JavaScript, CSS, зображення та шрифти.
Перевірку виконуйте на production-збірці:
npm run build
npm run startНе оцінюйте продуктивність через next dev: режим розробки додає накладні витрати й не відображає поведінку production-збірки.
[ ] Застосунок зібраний командою next build.
[ ] Тестування виконується через next start.
[ ] Використовується production-профіль бекенда та бази даних.
[ ] Браузер відкриває сторінку без розширень, які можуть змінювати її поведінку.
[ ] Перевірені холодне завантаження та повторне завантаження з кешем.
[ ] Тест проведений на реалістичній швидкості мережі та мобільному CPU.
[ ] Перевірені основні маршрути: головна сторінка, список, сторінка сутності, пошук і checkout або інший критичний сценарій.
[ ] Зафіксовано базові значення Core Web Vitals до внесення змін.
Порівнюйте результати не за одним запуском, а за кількома вимірюваннями. На результат можуть впливати кеш, мережа, навантаження сервера та сторонні скрипти.
[ ] Для фотографій використовуються WebP або AVIF, якщо це підтримує ваш pipeline.
[ ] Для ілюстрацій із малою кількістю кольорів використовується SVG або оптимізований PNG.
[ ] Оригінальний файл не завантажується, якщо на екрані потрібна менша версія.
[ ] У кожного зображення є відомі width і height або співвідношення сторін.
[ ] Зображення не масштабуються браузером із маленької версії в дуже велику.
[ ] Для адаптивних зображень задано sizes.
[ ] Перевірено якість стиснення на реальних пристроях.
next/image може вибирати відповідний розмір зображення та оптимізувати його під час запиту. Однак компонент не знає, яку ширину фактично займає зображення на сторінці, якщо не вказати sizes.
Найбільший видимий елемент першого екрана часто є зображенням hero-блоку. Для нього:
[ ] Зображення не має безумовного loading="lazy".
[ ] Воно має коректні розміри.
[ ] Воно завантажується з високим пріоритетом.
[ ] Зображення не замінюється після гідрації без потреби.
[ ] Його URL не залежить від повільного клієнтського запиту, якщо цього можна уникнути.
У версіях Next.js, де підтримується властивість preload, її можна використовувати для єдиного критичного зображення першого екрана. У старіших версіях для цієї мети використовується priority. Не позначайте так усі зображення: це створить конкуренцію за мережу.
// app/layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({
subsets: ["latin", "cyrillic"],
display: "swap",
preload: true,
variable: "--font-inter",
});
export const metadata: Metadata = {
title: "Каталог",
description: "Каталог товарів",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="uk">
<body className={inter.variable}>{children}</body>
</html>
);
}// app/page.tsx
import Image from "next/image";
export default function HomePage() {
return (
<main>
<section className="hero">
<div>
<p className="eyebrow">Нова колекція</p>
<h1>Оптимізований каталог</h1>
<p>
Критичне зображення має відомі розміри та завантажується без
зайвої затримки.
</p>
</div>
<Image
src="/images/hero.webp"
alt="Предмети з нової колекції"
width={1280}
height={800}
sizes="(max-width: 768px) 100vw, 50vw"
preload
/>
</section>
<section className="gallery" aria-label="Популярні товари">
<Image
src="/images/product-1.webp"
alt="Чорний рюкзак"
width={640}
height={480}
sizes="(max-width: 768px) 100vw, 33vw"
/>
<Image
src="/images/product-2.webp"
alt="Шкіряний гаманець"
width={640}
height={480}
sizes="(max-width: 768px) 100vw, 33vw"
/>
</section>
</main>
);
}У цьому прикладі файли hero.webp, product-1.webp і product-2.webp мають знаходитися в public/images.
Для всіх зображень, крім критичних, next/image зазвичай використовує відкладене завантаження. Перевірте, чи не приховуєте ви зображення через CSS або умовний рендеринг до моменту, коли браузер уже мав би його завантажити.
fill використовується без sizes.
Контейнер із fill не має position: relative і заданої висоти.
Hero-зображення позначене як lazy.
Кожна картка завантажує зображення в максимальній якості.
Розміри зображення змінюються після завантаження.
Для декоративного зображення вказано текстовий alt, що створює зайвий шум для скринрідерів.
Віддалені зображення дозволені через надто загальне правило конфігурації.
Якщо використовуються віддалені джерела, обмежте їх у next.config.js через remotePatterns, а не дозволяйте будь-який hostname.
[ ] Шрифти підключаються через next/font, а не через зовнішній CSS-запит під час завантаження сторінки.
[ ] Завантажуються лише потрібні сімейства та накреслення.
[ ] Вказані необхідні підмножини, наприклад latin і cyrillic.
[ ] Не завантажуються 300, 400, 500, 600, 700, якщо реально використовуються лише два накреслення.
[ ] Перевірено fallback-шрифт.
[ ] Текст не стає невидимим під час очікування шрифту.
[ ] Після завантаження шрифту макет не змінюється помітно.
[ ] Локальні шрифти оптимізовані та містять лише необхідні glyphs, якщо це можливо.
next/font самостійно оптимізує шрифти та завантажує їх із вашого застосунку. Це зменшує залежність від окремого запиту до сервісу шрифтів.
Якщо застосунок використовує кілька шрифтів, перевірте, чи всі вони справді потрібні. Великий variable font також може бути дорожчим за кілька вузьких накреслень, якщо використовується лише обмежений набір стилів.
Після next build перегляньте:
[ ] Розмір JavaScript для кожного маршруту.
[ ] Чи не став маршрут динамічним без необхідності.
[ ] Чи не потрапила велика бібліотека в загальний client bundle.
[ ] Чи не імпортується серверний код у Client Component.
[ ] Чи немає дубльованих версій однієї бібліотеки.
[ ] Чи не завантажується код модального вікна, редактора або графіків до моменту його використання.
[ ] Чи не містить початковий bundle всіх локалізацій, іконок або форматерів.
Розділяйте компоненти за середовищем виконання:
серверні компоненти залишайте серверними, якщо їм не потрібні state або browser APIs;
додавайте "use client" на найменшому можливому рівні;
не передавайте в Client Component великі об’єкти, які можна обробити на сервері;
завантажуйте важкі інтерактивні компоненти динамічно.
Наприклад, редактор або графік, які потрібні лише після відкриття вкладки, не повинні потрапляти в початковий JavaScript сторінки.
Для детального аналізу можна використати @next/bundle-analyzer.
Встановлення:
npm install --save-dev @next/bundle-analyzerКонфігурація:
// next.config.js
const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true",
});
/** @type {import("next").NextConfig} */
const nextConfig = {
reactStrictMode: true,
poweredByHeader: false,
};
module.exports = withBundleAnalyzer(nextConfig);Запуск аналізу:
ANALYZE=true npm run buildУ PowerShell:
$env:ANALYZE="true"; npm run buildПід час аналізу шукайте не лише найбільший файл, а й причину його присутності:
бібліотека імпортована цілком замість окремої функції;
пакет використовується лише в одному рідкісному сценарії;
серверний пакет опинився в браузерному бандлі;
один пакет дублюється через різні версії;
великі дані імпортуються як JavaScript замість отримання з API або сервера.
Динамічний імпорт доречний для важкого компонента, який не потрібен для першого відображення:
"use client";
import dynamic from "next/dynamic";
import { useState } from "react";
const Chart = dynamic(() => import("./chart"), {
loading: () => <p>Завантаження графіка…</p>,
});
export default function StatisticsPanel() {
const [isOpen, setIsOpen] = useState(false);
return (
<section>
<button type="button" onClick={() => setIsOpen((value) => !value)}>
{isOpen ? "Сховати статистику" : "Показати статистику"}
</button>
{isOpen && <Chart />}
</section>
);
}Не використовуйте динамічний імпорт автоматично для всіх компонентів. Якщо компонент потрібен для LCP або першої взаємодії, додатковий запит може погіршити результат.
Перевіряйте метрики на двох рівнях.
Lighthouse і подібні інструменти запускають контрольований сценарій. Вони корисні для:
пошуку великих ресурсів;
перевірки блокувальних запитів;
аналізу LCP;
виявлення layout shift;
перевірки невикористаного JavaScript.
Лабораторний результат не є гарантією для реальних користувачів.
Польові дані збираються з реальних браузерів, пристроїв і мереж. Для основних метрик орієнтуйтеся на 75-й перцентиль:
LCP — до 2,5 секунди;
INP — до 200 мілісекунд;
CLS — до 0,1.
Додатково контролюйте TTFB, оскільки повільна відповідь сервера затримує всі наступні етапи завантаження.
Який саме елемент став LCP?
Чи є це зображенням, текстом або блоком, що залежить від JavaScript?
Чи не завантажується критичний ресурс після гідрації?
Чи не витрачається час на зайві шрифти або редиректи?
Чи достатньо швидко відповідає сервер?
Чи не виконується довга JavaScript-операція після кліку?
Чи не перераховується весь великий список після кожної зміни?
Чи немає важкого синхронного парсингу JSON?
Чи не запускаються сторонні скрипти під час першої взаємодії?
Чи задані розміри зображень і відео?
Чи не вставляються банери над уже видимим контентом?
Чи не змінюється висота тексту після завантаження шрифту?
Чи не з'являється повідомлення про помилку без зарезервованого місця?
Не оптимізуйте лише середнє значення. Користувачів із повільними пристроями часто приховує середнє, тому рішення приймайте за перцентилями та розподілом значень.
У package.json мають бути окремі команди для збірки й запуску production:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
}
}Перевірте:
[ ] Реліз не запускається через next dev.
[ ] next build завершується без помилок і несподіваних попереджень.
[ ] Застосунок запускається в середовищі з NODE_ENV=production.
[ ] Секрети не мають префікса NEXT_PUBLIC_.
[ ] Публічні змінні середовища справді можуть бути доступні браузеру.
[ ] Значення змінних середовища перевіряються під час запуску або збірки.
[ ] У production немає debug-логування великих об’єктів.
Для контейнерного розгортання можна використовувати standalone output:
// next.config.js
/** @type {import("next").NextConfig} */
const nextConfig = {
output: "standalone",
poweredByHeader: false,
};
module.exports = nextConfig;Після збірки Next.js створює мінімальний набір файлів для запуску застосунку в .next/standalone. Під час копіювання контейнера не забудьте також додати потрібні директорії public і .next/static, якщо цього вимагає ваша схема розгортання.
Перевірте, де саме виконується стиснення відповідей:
Next.js може стискати відповіді за замовчуванням;
reverse proxy або CDN також може виконувати стиснення;
подвійне стиснення не повинно створювати некоректні заголовки;
HTML, JavaScript, CSS і JSON мають передаватися зі стисненням;
уже стиснені зображення не потрібно стискати повторно на проксі.
Якщо застосунок працює за reverse proxy, перевірте реальний Content-Encoding, кешування та час до першого байта з боку клієнта.
[ ] Статичні ресурси мають довгий кеш і хешовані імена.
[ ] HTML із персональними даними не кешується як загальнодоступний.
[ ] API-відповіді кешуються лише за чітко визначеною стратегією.
[ ] CDN не кешує відповідь із cookie або приватними заголовками для всіх користувачів.
[ ] Після релізу старі та нові версії статичних ресурсів не конфліктують.
Не встановлюйте довгий публічний кеш для всіх відповідей без перевірки автентифікації та персоналізації.
[ ] Складено список усіх аналітичних, рекламних і віджет-скриптів.
[ ] Скрипти, які не потрібні для першого екрана, не блокують його.
[ ] Скрипти завантажуються лише на маршрутах, де вони використовуються.
[ ] Виміряно вплив кожного стороннього скрипту на INP та TBT.
[ ] Дубльовані системи аналітики видалені.
[ ] Сторонній код не виконується під час кожного рендеру React-компонента.
Сторонній код може змінювати результати вимірювань сильніше, ніж оптимізація локального компонента. Додавайте його по одному й порівнюйте профіль до та після.
Виконайте перевірку в такому порядку:
Зберіть production-версію.
Перевірте розмір маршрутів після збірки.
Запустіть Bundle Analyzer для підозріло великих пакетів.
Перевірте HTML і мережеві запити на основних сторінках.
Знайдіть LCP-елемент і перевірте його джерело.
Перевірте, що зображення не спричиняють CLS.
Перевірте завантаження шрифтів і перемикання fallback-шрифту.
Перевірте INP під час реальних сценаріїв: пошук, фільтрація, відкриття меню, модальні вікна.
Запустіть Lighthouse у мобільному профілі.
Перевірте польові метрики після розгортання на staging або для малої частки користувачів.
Порівняйте результати з попереднім релізом.
Зафіксуйте бюджети продуктивності в CI, щоб регресія була помітною до релізу.
Вимірювання виконуються в next dev.
Усі зображення позначаються як критичні.
Для Image fill не вказано sizes.
У зображень немає зарезервованих розмірів, тому виникає CLS.
Весь інтерфейс оголошений Client Component через "use client" у кореневому компоненті.
Велика бібліотека імпортується в початковий bundle заради однієї функції.
Динамічний імпорт застосовано до LCP-компонента, через що він з'являється пізніше.
Завантажуються всі накреслення шрифту, хоча використовуються лише два.
Зовнішній шрифт підключений через блокувальний <link>.
У production залишилися console-логи з великими відповідями API.
Секрет передано через NEXT_PUBLIC_.
Після додавання аналітики перевірено лише Lighthouse, але не INP у реальних користувачів.
Для всіх відповідей встановлено довгий публічний кеш без урахування cookie та персоналізації.
Середнє значення метрики використовується замість 75-го перцентиля.
Перед production-релізом Next.js-застосунку потрібно:
оптимізувати формат, розмір і пріоритет зображень;
задавати sizes, width, height і правильний alt;
підключати лише необхідні шрифти через next/font;
зменшувати Client Components і початковий JavaScript;
аналізувати production-бандл, а не лише вихідний код;
вимірювати LCP, INP, CLS і TTFB на лабораторних та реальних даних;
перевіряти змінні середовища, кешування, стиснення та спосіб запуску;
порівнювати метрики до й після релізу та контролювати регресії в CI.