В клиентской производительности веб-приложений типографика и сторонний JavaScript представляют главную угрозу стабильности интерфейса и времени отклика.
При традиционном подключении внешних шрифтов (например, через <link href="https://fonts.googleapis.com/css2...">) браузер вынужден выполнить многоступенчатый сетевой каскад: запросить HTML, обнаружить CSS, установить внешнее TLS-соединение с сервером Google Fonts, распарсить таблицу @font-face и лишь затем скачать файлы .woff2. В этот период возникают деструктивные визуальные артефакты:
Сторонние скрипты (аналитика, рекламные трекеры, чат-виджеты) создают другую проблему: они захватывают основной поток (Main Thread). Синхронный парсинг и компиляция сотен килобайт чужого кода замораживают пользовательский ввод, разрушая показатель INP (Interaction to Next Paint).
Next.js решает обе проблемы на архитектурном уровне через модули next/font (Zero-RTT локализация ассетов на этапе сборки с синтезом fallback-метрик) и next/script (гранулярные стратегии приоритизации загрузки).
next/font#Модуль next/font переносит загрузку внешних шрифтов с клиентского рантайма на этап статической сборки проекта (next build):
.next/static. Браузер пользователя больше не совершает ни одного запроса к внешним серверам.next/script#Компонент <Script> инкапсулирует расширенное управление жизненным циклом тега <script>, координируя его выполнение с процессом гидратации React-дерева.
На этапе сборки Next.js скачивает бинарники WOFF2 локально в директорию проекта. В рантайме полностью отсутствуют сетевые запросы к серверам Google Fonts.
Генерируются правила @font-face с дескрипторами size-adjust и ascent-override. Текст резервирует идеальные геометрические пропорции в DOM.
В head корневого layout внедряются теги <script>, необходимые до старта гидратации (безопасность, полифиллы ядра).
React активирует виртуальный DOM. Сразу после этого запускаются скрипты аналитики и мониторинга ошибок (Sentry, GTM).
Браузер завершает первичный рендеринг. Во время паузы в работе процессора (requestIdleCallback) подгружаются чаты и тяжелые SDK соцсетей.
Next.js поддерживает как Variable Fonts, так и статические начертания.
import { Inter, JetBrains_Mono } from 'next/font/google';
import localFont from 'next/font/local';
// 1. Вариативный шрифт Google Fonts (содержит все веса по умолчанию)
export const inter = Inter({
subsets: ['latin', 'cyrillic'],
display: 'swap',
variable: '--font-inter',
});
// 2. Моноширинный шрифт с явным указанием весов (для невариативных шрифтов)
export const jetbrainsMono = JetBrains_Mono({
subsets: ['latin'],
display: 'swap',
variable: '--font-jetbrains-mono',
});
// 3. Локальный кастомный шрифт из файловой системы
export const customBrandFont = localFont({
src: [
{
path: '../public/fonts/BrandFont-Regular.woff2',
weight: '400',
style: 'normal',
},
{
path: '../public/fonts/BrandFont-Bold.woff2',
weight: '700',
style: 'normal',
},
],
variable: '--font-brand',
display: 'swap',
});Шрифты инициализируются строго на уровне корневого слоя и внедряются в <html> или <body> через CSS-переменные.
import type { Metadata } from 'next';
import { inter, jetbrainsMono } from '@/app/fonts';
import Script from 'next/script';
import '@/app/globals.css';
export const metadata: Metadata = {
title: 'Fullstack Agentic Lab - Оптимизированная платформа',
description: 'Пример высокопроизводительного Next.js приложения',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html
lang="ru"
className={`${inter.variable} ${jetbrainsMono.variable} antialiased`}
>
<head>
{/* beforeInteractive разрешен СТРОГО внутри Root Layout */}
<Script
id="security-headers-init"
strategy="beforeInteractive"
src="https://cdn.example.com/security-shield.js"
/>
</head>
<body className="min-h-screen bg-bg-base text-text-primary font-sans">
{children}
</body>
</html>
);
}В Tailwind CSS v4 конфигурационный файл tailwind.config.ts не используется. Привязка переменных next/font осуществляется декларативно в globals.css через директиву @theme inline.
@import "tailwindcss";
@theme inline {
/* Привязываем CSS-переменные шрифтов к стандартным утилитам font-sans и font-mono */
--font-sans: var(--font-inter), ui-sans-serif, system-ui, -apple-system, sans-serif;
--font-mono: var(--font-jetbrains-mono), ui-monospace, SFMono-Regular, monospace;
--font-heading: var(--font-inter);
}next/script и инлайн-скрипты#Для внедрения аналитики, трекеров и виджетов используется компонент <Script>.
'use client';
import Script from 'next/script';
export function AnalyticsScripts() {
return (
<>
{/* 1. afterInteractive: Внешний скрипт аналитики сразу после гидратации */}
<Script
src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"
strategy="afterInteractive"
/>
{/* 2. Инлайн-скрипт: Атрибут 'id' ОБЯЗАТЕЛЕН для дедупликации */}
<Script id="google-analytics-setup" strategy="afterInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX', { page_path: window.location.pathname });
`}
</Script>
{/* 3. lazyOnload: Некритичный виджет чата поддержки в период простоя */}
<Script
src="https://cdn.support-chat.com/widget.js"
strategy="lazyOnload"
onLoad={() => console.log('Support chat widget loaded during idle.')}
/>
</>
);
}Разработчик добавил тег <Script src='/payment-sdk.js' strategy='beforeInteractive' /> внутри страницы app/checkout/page.tsx. Почему сборщик Next.js завершится с фатальной ошибкой?
@next/third-parties#В современных версиях Next.js ручное подключение Google Tag Manager, Google Analytics и YouTube через <Script> заменяется официальным пакетом @next/third-parties. Он оптимизирован инженерами Chrome Aurora и предотвращает деградацию TBT и LCP.
import { GoogleTagManager, GoogleAnalytics } from '@next/third-parties/google';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ru">
<body>
{children}
{/* Автоматически оптимизированный запуск GTM */}
<GoogleTagManager gtmId="GTM-XYZ1234" />
<GoogleAnalytics gaId="G-XXXXXXXXXX" />
</body>
</html>
);
}display: 'optional'#Свойство font-display со значением 'optional' — мощнейший инструмент для достижения идеального мобильного LCP:
export const inter = Inter({
subsets: ['latin', 'cyrillic'],
display: 'optional', // Идеально для максимального балла Core Web Vitals на мобильных сетях
variable: '--font-inter',
});Каким образом next/font обеспечивает нулевой Cumulative Layout Shift (CLS = 0) при подмене системного шрифта на кастомный веб-шрифт?
Попытка использовать стратегию strategy="beforeInteractive" внутри вложенных страниц (page.tsx), клиентских компонентов или дочерних layout приводит к фатальному сбою сборки или рантайм-крашу Next.js:
Error: next/script's beforeInteractive strategy is only allowed in the root layout (app/layout.js).
Решение: Стратегия beforeInteractive разрешена строго в корневом app/layout.tsx. Для любых вложенных страниц используйте strategy="afterInteractive" или lazyOnload.
При передаче встроенного кода внутрь компонента <Script> без обязательного свойства id Next.js выбрасывает ошибку: next/script: id is required for inline scripts. Это свойство необходимо фреймворку для дедупликации скриптов при клиентской навигации между маршрутами.
Решение: Всегда задавайте уникальный строковый id: <Script id="analytics-init">{...}</Script>.
В Tailwind CSS v4 файл tailwind.config.ts больше не генерируется и игнорируется движком стилей. Попытка настроить theme.extend.fontFamily в TypeScript-файле не приведет ни к какому эффекту.
Решение: Объявляйте соответствие переменных шрифтов через директиву @theme inline в файле app/globals.css.
Подключение невариативного шрифта (например, Roboto) без указания конкретных весов приводит к ошибке сборки, а перечисление всех весов (['100', '300', '400', '500', '700', '900']) заставляет браузер скачивать сотни килобайт файлов.
Решение: Всегда отдавайте предпочтение вариативным шрифтам (Variable Fonts: Inter, Geist, Roboto Flex), которые содержат все веса в одном легковесном файле WOFF2. Для невариативных шрифтов подключайте строго используемые веса (например, только weight: ['400', '700']).
Подключение Google Tag Manager или Facebook Pixel через стратегию beforeInteractive или afterInteractive с десятками внутренних тегов приводит к тому, что во время начальной загрузки главный поток браузера зависает на 500–1500 мс, вызывая сбой метрики INP и зависание анимаций.
Решение: Используйте официальный оптимизированный пакет @next/third-parties/google (компоненты <GoogleTagManager> и <GoogleAnalytics>) либо переносите скрипты в веб-воркер через strategy="worker" с библиотекой Partytown.
Инициализация шрифта (const inter = Inter({...})) внутри изолированного компонента карточки или кнопки приводит к тому, что стили шрифта подгружаются в DOM только в момент первого рендера этого компонента.
Решение: Всегда выносите инициализацию шрифтов в централизованный файл (например, app/fonts.ts) и подключайте CSS-переменные в корневом app/layout.tsx.