В Next.js изображения, metadata и production build находятся в разных частях проекта. next/image занимается загрузкой и геометрией изображений. Metadata API формирует <title>, description, Open Graph и Twitter metadata. next build собирает production‑версию приложения. Ошибки в этих местах часто не мешают работе next dev. Страница открывается, локальная картинка показывается, title есть. На другом домене внешний Image получает запрещённый URL, Open Graph содержит localhost, production build падает на конфигурации или импорте. Разберём эти три части отдельно на примере проекта Goods Finder.
next/image
Image из next/image принимает локальный путь, внешний URL или статический import. Для локального изображения из public путь начинается от корня сайта.
import Image from "next/image"; export default function Page() { return ( <Image src="/og-goods-finder.png" alt="Обложка" width={1200} height={630} style={{ width: "100%", height: "auto" }} /> ); }
width и height задают intrinsic‑размер и пропорцию, CSS задаёт размер картинки на странице.
При fill размеры переходят к родительскому контейнеру.
<div className="relative aspect-[4/3]"> <Image src="/product.png" alt="Карточка товара" fill sizes="(min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw" className="object-contain" /> </div>
Родитель задаёт геометрию, Image занимает его площадь.
sizes сообщает браузеру предполагаемую ширину изображения на разных viewport. Без него браузер при fill исходит из ширины 100vw и может выбрать из srcset файл крупнее нужного.
Внешние изображения
Внешний URL нужно разрешить в next.config. Для этого используется images.remotePatterns.
/** @type {import("next").NextConfig} */ const nextConfig = { images: { remotePatterns: [ { protocol: "https", hostname: "cdn.example.com", port: "", pathname: "/product-images/**", search: "", }, ], }, }; export default nextConfig;
Next сравнивает protocol, hostname, port, pathname и search с URL изображения. Несовпавший URL Image Optimizer не принимает.
покрывает один сегмент. * покрывает несколько сегментов в конце pathname или несколько subdomain в начале hostname.
Широкий вариант вроде hostname: "**" открывает оптимизатор для любых внешних адресов. Узкий pattern оставляет только тот host и path, откуда приложение получает картинки.
В проекте разрешён cdn.dummyjson.com и путь /product-images/**. Карточки получают thumbnail из API, затем передают URL в Image.
Изображение первого экрана
Обычные изображения загружаются с loading="lazy" по умолчанию. Картинка первого экрана может загружаться иначе. В Next.js 16 старый prop priority помечен deprecated. В API появился preload.
<Image src={item.thumbnail} alt={item.title} fill sizes="(min-width: 640px) 50vw, 100vw" preload />
preload добавляет загрузку изображения через <link> в <head>. Документация также предлагает loading="eager" или fetchPriority="high" для сценариев, где отдельный preload не нужен. Все карточки списка помечать preload не требуется. Кандидат выбирается среди изображений первого экрана.
Metadata в layout
Статические metadata можно экспортировать из layout.js или page.js.
export const metadata = { title: { default: "Store", template: "%s - Store", }, description: "Каталог товаров", };
Дочерняя страница может задать только свой title.
export const metadata = { title: "Список товаров", };
С шаблоном из layout итоговый <title> будет собран из title страницы и общего имени сайта.
Open Graph тоже можно задать на уровне layout.
export const metadata = { title: "Store", description: "Каталог товаров", openGraph: { title: "Store", description: "Каталог товаров", type: "website", images: [ { url: "/og.png", width: 1200, height: 630, alt: "Store", }, ], }, };
Относительный /og.png требует базовый адрес.
metadataBase
metadataBase задаёт origin для URL внутри metadata.
const metadataBase = new URL( process.env.NEXT_PUBLIC_SITE_URL ?? "http://localhost:3000" ); export const metadata = { metadataBase, openGraph: { images: [ { url: "/og.png", width: 1200, height: 630, }, ], }, };
При metadataBase = https://example.com путь /og.png превращается в https://example.com/og.png.
Относительные URL в metadata без metadataBase могут остановить build. Абсолютный URL от metadataBase не зависит.
Fallback на localhost подходит локальной среде. В production такой fallback не сообщает об ошибке конфигурации. Сборка может пройти с http://localhost:3000 внутри metadata. Production env в таком случае проверяется отдельно.
Для production можно убрать fallback.
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL; if (!siteUrl) { throw new Error("NEXT_PUBLIC_SITE_URL is required"); } export const metadata = { metadataBase: new URL(siteUrl), };
Отсутствующая переменная остановит выполнение с явной ошибкой.
generateMetadata
Статический metadata подходит страницам с заранее известными title и description. Dynamic route может получить metadata по params.
export async function generateMetadata({ params }) { const { id } = await params; const item = await getProductById(id); return { title: item.title, description: item.description, openGraph: { title: item.title, description: item.description, url: `/goods/${id}`, images: [ { url: item.thumbnail, alt: item.title, }, ], }, twitter: { card: "summary_large_image", title: item.title, description: item.description, images: [item.thumbnail], }, }; }
generateMetadata выполняется на сервере и получает данные dynamic segment через params.
Description обычно ограничивают по длине до передачи в metadata.
function toShortText(value, maxLen = 160) { const text = String(value || "") .trim() .replace(/\s+/g, " "); if (!text) return ""; if (text.length <= maxLen) return text; return `${text.slice(0, maxLen - 1)}…`; }
Функция возвращает одну строку и обрезает её до заданной длины.
Ошибку загрузки данных для metadata тоже обрабатывает серверный код.
export async function generateMetadata({ params }) { const { id } = await params; try { const item = await getProductById(id); return { title: item.title, description: toShortText(item.description), }; } catch { return { title: "Товар не найден", description: "Запрошенный товар отсутствует.", }; } }
При ошибке страница получает fallback metadata.
Open Graph и remotePatterns
remotePatterns относится к next/image. Поле openGraph.images записывает URL изображения в metadata.
Один и тот же внешний thumbnail может использоваться в двух местах.
<Image src={item.thumbnail} alt={item.title} width={600} height={400} />
Для этого URL действует remotePatterns.
return { openGraph: { images: [item.thumbnail], }, };
Здесь Next формирует <meta property="og:image"> с URL.
Dev и build
next dev запускает development server. next build собирает production‑приложение. В package.json обычно есть отдельные команды.
{ "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "eslint" } }
В Next.js 16 next build не запускает ESLint. Команда next lint тоже удалена.
Перед production сборкой lint запускается отдельно.
npm run lint npm run build
Первая команда проверяет правила ESLint. Вторая собирает production‑версию Next.js.
Build проверяет production‑компиляцию и код, который Next должен обработать во время сборки. Часть ошибок проявляется только во время runtime, поэтому после успешного build запускается production server.
npm run start
next start работает с результатом предыдущего next build.
Проверка production‑режима
После next start открывается главная страница, список и dynamic route. Проверяется загрузка локальных и внешних изображений. В консоли браузера не должно быть ошибок Image Optimizer.
У dynamic page проверяется <head>. Title и description должны соответствовать данным страницы. og:url и og:image содержат production origin, если для них используется относительный URL через metadataBase.
Проверяется неизвестный id и обычный 404. Отдельно открывается страница после reload, а не только через клиентский переход.
Production env запускается с теми же именами переменных, которые использует код. NEXT_PUBLIC_SITE_URL, API base URL и другие значения проверяются до выкладки.
Внешние картинки проверяются по реальным URL. Новый hostname или новый pathname требует изменения remotePatterns.
Перед деплоем
npm run lint npm run build npm run start
После запуска проверяются основные маршруты, dynamic route, 404, изображения и metadata. Dev server в эту проверку не входит.
