The Complete Guide to Next.js 15 Metatags — From og:image to hreflang
A practical guide to The Complete Guide to Next.js 15 Metatags — From og:image to hreflang, with a clear checklist, key risks to watch, and next steps for readers who want to compare options before acting.
Key Summary
- Next.js 15 can declaratively manage static and dynamic meta tags through the Metadata API based on the App Router.
- The
generateMetadata()function allows automatic generation of per-page dynamic og:image, title, and description.- Twitter Card, hreflang, canonical URL, and structured data (JSON-LD) can all be controlled from a single file.
- Incorrectly configured meta tags directly lead to social preview errors and drops in search rankings, so always check with the Meta Tag Checker before deploying.
Quick Answer: You can efficiently manage meta tags using the Metadata API in Next.js 15.
What Is the Next.js 15 Metadata API?
| Item | Value |
|---|---|
| Next.js Version | 15 |
| Metadata Management Method | Metadata API |
| Dynamic Metadata Generation Function | generateMetadata() |
| Supported Meta Tag Types | og:image, title, description, Twitter Card, hreflang, canonical URL, JSON-LD |
| Meta Tag Checker Link | Meta Tag Checker |
The App Router introduced in Next.js 13 provides a Metadata API that replaces the legacy component approach. In Next.js 15, this API has become even more refined, allowing you to control meta tags in two ways from layout.tsx and page.tsx files: by exporting a metadata object or by using the generateMetadata() function.
Static Metadata
The simplest form is to directly export a metadata object.
// app/page.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Next.js 15 Meta Tag Guide',
description: 'Everything about Next.js meta tags, from og:image to hreflang',
}This approach is suitable for static pages where content is finalized at build time — for example, a blog home page or an about page where the content doesn't change frequently.
Dynamic Metadata: generateMetadata()
For pages where content varies based on URL parameters — such as blog posts or product detail pages — use the generateMetadata() function.
// app/blog/[slug]/page.tsx
import type { Metadata, ResolvingMetadata } from 'next'
type Props = {
params: { slug: string }
}
export async function generateMetadata(
{ params }: Props,
parent: ResolvingMetadata
): Promise<Metadata> {
const post = await fetchPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [{ url: post.ogImage, width: 1200, height: 630 }],
},
}Since generateMetadata() runs on the server, it can perform database queries and external API calls. It returns a value of type Metadata, and Next.js automatically injects it into the tag.
How Do You Properly Configure og:image?
Open Graph images are the preview images that appear when a link is shared on social platforms like KakaoTalk, Slack, and Twitter. If configured incorrectly, blank images or broken layouts will be displayed, which significantly reduces click-through rates.
Basic og:image Configuration
export const metadata: Metadata = {
openGraph: {
title: 'Page Title',
description: 'Page description',
url: 'https://example.com/blog/my-post',
siteName: 'Site Name',
images: [
{
url: 'https://example.com/og/my-post.png',
width: 1200,
height: 630,
alt: 'Post representative image',
},
],
locale: 'ko_KR',
type: 'article',
},
}Dynamic og:image Generation in Next.js — ImageResponse
Next.js 15 supports server-side dynamic OG image rendering using ImageResponse from next/og. You can control the design entirely in code without any separate image editing.
// app/og/route.tsx
import { ImageResponse } from 'next/og'
export const runtime = 'edge'
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const title = searchParams.get('title') ?? 'Default Title'
return new ImageResponse(
(
<div
style={{
display: 'flex',
fontSize: 60,
background: '#0f172a',
color: 'white',
width: '100%',
height: '100%',
alignItems: 'center',
justifyContent: 'center',
padding: '40px',
}}
>
{title}
</div>
),
{ width: 1200, height: 630 }
)
}You can then reference this endpoint inside generateMetadata().
images: [{ url: `https://example.com/og?title=${encodeURIComponent(post.title)}` }]Twitter Card Configuration
Twitter (now X) uses its own meta tag spec, but the Next.js Metadata API consolidates this under the twitter key.
export const metadata: Metadata = {
twitter: {
card: 'summary_large_image',
title: 'Page Title',
description: 'Page description',
creator: '@handle',
images: ['https://example.com/og/my-post.png'],
},
}The card value can be one of summary, summary_large_image, app, or player. For blog posts, summary_large_image is the most appropriate choice.
How to Complete Multilingual SEO with hreflang
hreflang tags signal to search engines which language and region each version of the same content is intended for. If you run both Korean and English versions of a site, this must be configured. Without it, Google may treat language-specific pages as duplicate content and penalize their search visibility.
Configuring hreflang with alternates
export const metadata: Metadata = {
alternates: {
canonical: 'https://example.com/ko/blog/my-post',
languages: {
'ko-KR': 'https://example.com/ko/blog/my-post',
'en-US': 'https://example.com/en/blog/my-post',
'ja-JP': 'https://example.com/ja/blog/my-post',
},
}Based on this configuration, Next.js automatically generates the following tags.
<link rel="canonical" href="https://example.com/ko/blog/my-post" />
<link rel="alternate" hreflang="ko-KR" href="https://example.com/ko/blog/my-post" />
<link rel="alternate" hreflang="en-US" href="https://example.com/en/blog/my-post" />
<link rel="alternate" hreflang="ja-JP" href="https://example.com/ja/blog/my-post" />Why Canonical URL Configuration Matters
A canonical URL tag tells search engines "this URL is the original" when identical or similar content exists at multiple URLs. When duplicate URLs arise from pagination, UTM parameters, www vs. non-www differences, and similar cases, omitting a canonical tag causes search ranking signals to be split across URLs.
alternates: {
canonical: 'https://example.com/blog/my-post',
}Use absolute paths and maintain HTTPS and www consistency (with or without www) across the entire site.
Obtaining Rich Results with Structured Data (JSON-LD)
Structured data enables rich results in search, displaying additional information such as star ratings, authors, and publication dates. In Next.js, the recommended approach is to insert a `
🔧 Related Free Tools
Next useful step
Continue from this guide
Related
A practical guide to 7 Practical Ways to Reach INP 200ms in 2026, with a clear c...
ITRTX 5070 vs RTX 5080: AI Training GPU Buying GuideA practical buying guide comparing the RTX 5070 and RTX 5080 for AI training, co...
IT6 Ways to Make Side Income with ChatGPT — A Practical, Tested Monetization Guide for 2026A practical guide to 6 Ways to Make Side Income with ChatGPT — A Practical, Test...
IT2026 ChatGPT vs Claude vs Gemini — AI Chatbot Performance, Pricing, and Use Cases ComparedA practical guide to 2026 ChatGPT vs Claude vs Gemini — AI Chatbot Performance, ...