Metadata และ SEO
ไอเดียหลักในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียหลักในหนึ่งประโยค”คุณอธิบาย <head> ของหน้าเป็น data — object metadata แบบ static หรือฟังก์ชัน generateMetadata แบบ dynamic — แล้ว Next.js render tag ที่ถูกต้องบน server จึงไม่ต้องมาจัดการ <head> ด้วยมืออีก
metadata แบบ static
หัวข้อที่มีชื่อว่า “metadata แบบ static”คุณ export object metadata จาก layout หรือ page ไหนก็ได้ แล้ว Next.js เปลี่ยนค่านั้นเป็น <title>, <meta> และ link tag ขั้นตอนนี้รันบน server ดังนั้น tag จึงอยู่ใน HTML ชุดแรกที่ crawler อ่าน
import type { Metadata } from 'next'
export const metadata: Metadata = { title: 'About', description: 'Who we are and what we build.', openGraph: { title: 'About Acme', images: ['/og.png'] },}
export default function AboutPage() { return <h1>About</h1>}metadata แบบ dynamic และ title template
หัวข้อที่มีชื่อว่า “metadata แบบ dynamic และ title template”เมื่อ tag ขึ้นกับ data — blog post หรือ product — ให้ export generateMetadata แทน function นี้รับ params ของ route รันบน server และ return รูปแบบ Metadata เดียวกัน การ fetch ตรงนี้ถูก dedupe กับที่อยู่ในหน้าของคุณ คุณจึงไม่จ่ายซ้ำสองครั้ง
import type { Metadata } from 'next'
export async function generateMetadata({ params,}: { params: Promise<{ slug: string }>}): Promise<Metadata> { const { slug } = await params const post = await fetch(`https://api.example.com/posts/${slug}`).then((r) => r.json()) return { title: post.title, description: post.excerpt }}layout กำหนด title.template ได้เพื่อให้ทุกหน้าลูกได้ suffix ที่สม่ำเสมอ และต้องมี default คู่กับ template เสมอ
// app/layout.tsx — every child title becomes "<page> | Acme"import type { Metadata } from 'next'
export const metadata: Metadata = { title: { template: '%s | Acme', default: 'Acme', },}file-based metadata
หัวข้อที่มีชื่อว่า “file-based metadata”metadata บางอย่างเป็นไฟล์ ไม่ใช่ field วางไฟล์ที่ตั้งชื่อแบบพิเศษลงใน route segment แล้ว Next.js ต่อให้เอง opengraph-image.tsx สร้าง social preview, icon.png กลายเป็น favicon ส่วน sitemap.ts และ robots.ts กลายเป็น route ของตัวเอง route พิเศษเหล่านี้ถูก cache โดย default
// app/opengraph-image.tsx — generated, cached social imageimport { ImageResponse } from 'next/og'
export const size = { width: 1200, height: 630 }export const contentType = 'image/png'
export default function Image() { return new ImageResponse(<div style={{ fontSize: 96 }}>Acme</div>, size)}// app/sitemap.ts — becomes /sitemap.xmlimport type { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap { return [{ url: 'https://acme.com', lastModified: new Date() }]}เพราะทุกอย่างเป็น data หรือเป็นไฟล์ จึงไม่มี <head> ให้คอย sync ด้วยมือ — template merge ทั้ง parent และ child และ tag ที่ซ้ำกันก็ไม่มีทางเพี้ยนออกจากกัน
flowchart TD A["metadata object"] --> M[Merge parent + child] B["generateMetadata(params)"] --> M C["opengraph-image / sitemap / robots files"] --> M M --> Head["Server-rendered head tags"]