metadata 基础
每个 page / layout 可以 export metadata:
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'About · WadeLy',
description: 'About WadeLy, a developer who codes for fun.',
};
Next.js 自动生成:
<title>About · WadeLy</title>
<meta name="description" content="About WadeLy...">
根 layout 设全局默认
// src/app/layout.tsx
export const metadata: Metadata = {
title: {
default: 'WadeLy',
template: '%s · WadeLy', // 子页用 %s 占位
},
description: 'WadeLy 的个人主页和笔记。',
metadataBase: new URL('https://51testgame.com'),
icons: {
icon: '/favicon.ico',
apple: '/apple-icon.png',
},
};
之后每个页面只写自己的 title,会自动套 template:
// /about/page.tsx
export const metadata = { title: 'About' };
// 渲染成 <title>About · WadeLy</title>
OpenGraph + Twitter Card(社交分享)
export const metadata: Metadata = {
title: 'My Awesome Post',
description: 'A post about React.',
openGraph: {
title: 'My Awesome Post',
description: 'A post about React.',
url: 'https://51testgame.com/blog/post',
siteName: 'WadeLy',
images: [{ url: '/og-image.png', width: 1200, height: 630 }],
locale: 'zh_CN',
type: 'article',
},
twitter: {
card: 'summary_large_image',
title: 'My Awesome Post',
description: 'A post about React.',
images: ['/og-image.png'],
},
};
发到微信 / Twitter / 即刻自动显示卡片预览。
动态 metadata(基于 params)
import type { Metadata } from 'next';
// Next.js 15+:params 是 Promise
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;
const post = await getPost(slug);
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.coverImage],
type: 'article',
publishedTime: post.date,
authors: [post.author],
},
};
}
export default async function BlogPost({ params }) { ... }
自动生成 OG 图片(Next.js 内置)
src/app/blog/[slug]/opengraph-image.tsx:
import { ImageResponse } from 'next/og';
export const size = { width: 1200, height: 630 };
export default async function OGImage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await getPost(slug);
return new ImageResponse(
<div style={{
fontSize: 64,
background: 'white',
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}>
{post.title}
</div>
);
}
每篇文章自动有定制 OG 图——分享时显示标题。
sitemap.xml
// src/app/sitemap.ts
import type { MetadataRoute } from 'next';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await getAllPosts();
return [
{ url: 'https://51testgame.com', lastModified: new Date(), priority: 1 },
{ url: 'https://51testgame.com/about', lastModified: new Date() },
...posts.map(p => ({
url: `https://51testgame.com/blog/${p.slug}`,
lastModified: p.updatedAt,
priority: 0.7,
})),
];
}
Next.js 自动生成 /sitemap.xml。
robots.txt
// src/app/robots.ts
import type { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: '/admin/',
},
sitemap: 'https://51testgame.com/sitemap.xml',
};
}
自动生成 /robots.txt。
RSS feed
Next.js 没内置——用 Server Component + Response:
// src/app/rss.xml/route.ts
export async function GET() {
const posts = await getAllPosts();
const xml = `<?xml version="1.0"?>
<rss version="2.0">
<channel>
<title>WadeLy</title>
<link>https://51testgame.com</link>
${posts.map(p => `
<item>
<title>${p.title}</title>
<link>https://51testgame.com/blog/${p.slug}</link>
<pubDate>${new Date(p.date).toUTCString()}</pubDate>
<description>${p.excerpt}</description>
</item>
`).join('')}
</channel>
</rss>`;
return new Response(xml, {
headers: { 'Content-Type': 'application/xml' },
});
}
访问 /rss.xml 拿到 feed。
JSON-LD 结构化数据
export default function BlogPost({ post }) {
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.title,
author: { '@type': 'Person', name: 'WadeLy' },
datePublished: post.date,
image: post.coverImage,
};
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<article>...</article>
</>
);
}
帮助 Google 理解结构 → 在搜索结果里显示更丰富的卡片。
验证
部署后到这些工具检查:
坑
metadataBase必须设——OG 图相对 URL 没基址会变 localhost- 每页独立 metadata(不要全站一样的 title)—— SEO 损失大
- 中文站
locale: 'zh_CN',不是'zh-CN'(OpenGraph 标准) - 改了 metadata 要刷新 deploy / 重新生成 SSG 才生效
下一篇:部署到 Vercel。