next.js app router 中 metadata 必须服务端生成,静态对象或 async generatemetadata 函数在 html 渲染前注入;客户端 js 修改无效,且需匹配路由参数、防 xss、同步 lang 属性。

服务端预构建(如 Next.js、Nuxt、Astro 或自建 SSR)中,title 和 meta 标签不能靠客户端 JS 注入——搜索引擎爬虫不执行 JS,只解析初始 HTML 响应体。所以元数据必须在 HTML 字符串生成阶段就写进去,否则页面会以空 <title></title> 或默认值上线。
Next.js App Router 中的 metadata 对象如何生效
Next.js 13+ App Router 不再用 getServerSideProps 拼接 ,而是通过导出 metadata 对象声明元数据。它不是运行时渲染逻辑,而是在构建/请求时由框架读取并注入到 HTML 的 中。
-
metadata必须是静态对象或同步函数(如generateMetadata()),不能含await异步调用(除非你明确使用async generateMetadata()并确保其在 SSR 阶段可 resolve) - 动态值(如文章标题)需从路由参数或
fetch()获取,但generateMetadata()内部的fetch是被 Next.js 特殊处理的“服务端 fetch”,会提前于 HTML 渲染完成 - 若你在
generateMetadata()里写了console.log,它只在服务端打印;若漏掉return,整个<title></title>就为空
静态生成(SSG)下 meta 描述缺失的典型原因
当你用 generateStaticParams 预生成页面,但 generateMetadata 没做对应参数匹配,就会导致部分页面的 description 为 undefined 或 fallback 值。
- 比如
app/blog/[slug]/page.tsx中,generateStaticParams返回了[{ slug: 'seo-guide' }],但generateMetadata没解构params.slug去查数据库,而是直接返回固定文案,那所有页面的meta name="description"都一样 - 更隐蔽的问题:你用了
revalidate: 3600开启 ISR,但首次构建时数据源不可用,generateMetadata抛错或返回空,缓存就存了坏的 HTML —— 后续请求都返回空<title></title> - 检查方式:直接
curl https://yoursite.com/blog/seo-guide,看响应 HTML 里<title></title>是否真实存在且非空
自定义 SSR 框架中手动注入 meta 的安全边界
如果你没用 Next/Nuxt 这类封装好的方案,而是用 Express + React Server Components 或纯模板引擎(如 EJS、Nunjucks),元数据补全必须发生在字符串拼接前,且要防 XSS。
- 所有用户输入(如 CMS 中的
post.title)必须经过 HTML 实体转义,否则<title>{title}</title>可能变成<title><script>alert(1)</script></title>
- 不要在模板里拼接
<meta name="description" content="{rawDescription}">,而要用框架提供的 escape 函数,例如 Nunjucks 的 <code>{{ description | e }} -
lang属性和charset必须硬编码在模板最开始:<meta charset="UTF-8">,不能依赖 JS 设置
真正容易被忽略的是:元数据是否随语言/地区路由变化而更新。比如 /en/blog/ 页面用了 en-US 的 title,但 没跟着切——这会让 Google 认为内容与声明语言不符,间接影响区域排名。这类问题不会报错,但会在 Search Console 的「覆盖范围」报告里静默降权。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











