元数据与 OG 图片
Metadata API 帮你为每个页面定义 <title>、<meta>、<link>,进而优化 SEO 和社交分享。你有三条路径:
- 静态
metadata对象 - 动态
generateMetadata函数 - 基于文件的约定(
favicon.ico、opengraph-image.tsx、robots.txt、sitemap.xml等)
metadata与generateMetadata只在 服务端组件 中生效。
默认标签
即便你不写任何元数据,Next.js 也会自动注入:
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
静态元数据
在 layout.tsx 或 page.tsx 里导出一个 Metadata 对象:
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: '我的博客',
description: '关于前端与产品的思考',
};
export default function Layout() {
/* … */
}
动态元数据
根据 params 拉取数据再生成标题描述:
import type { Metadata } from 'next';
type Props = {
params: Promise<{ slug: string }>;
};
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
const post = await fetch(`https://api.vercel.app/blog/${slug}`).then(r => r.json());
return {
title: post.title,
description: post.description,
};
}
export default function Page() {
/* … */
}
避免重复请求
页面本身和 generateMetadata 常常要同一份数据。用 React 的 cache 包一层,保证只请求一次:
import { cache } from 'react';
import { db } from '@/app/lib/db';
export const getPost = cache(async (slug: string) => {
return db.query.posts.findFirst({ where: eq(posts.slug, slug) });
});
流式元数据
对动态渲染的页面,Next.js 会把元数据 单独流式 注入 HTML,不阻塞视觉内容。Twitterbot、Slackbot 这类必须在 <head> 里拿到 meta 的爬虫会自动降级为「等全部生成完再返回」。
文件约定
这些特殊文件可以静态放置,也可以用代码动态生成:
favicon.ico、icon.jpg、apple-icon.jpgopengraph-image.jpg、twitter-image.jpgrobots.txt、sitemap.xml
静态 favicon / OG
把 favicon.ico 放在 app/ 根部,把 opengraph-image.jpg 放在需要定制的路由里。Next.js 会自动注入对应 <meta> 标签,且就近路由的 OG 优先级更高。
动态 OG 图片
用 ImageResponse 按 JSX + CSS 生成 1200×630 的 PNG,非常适合博客每篇文章一张独有封面:
// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og';
import { getPost } from '@/app/lib/data';
export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';
export default async function Image({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug);
return new ImageResponse(
(
<div
style={{
fontSize: 128,
background: 'white',
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
{post.title}
</div>
)
);
}
ImageResponse 底层使用 satori + resvg,只支持 flexbox 等子集,grid 不可用。把它当做「可程序化的截图器」而非完整浏览器。