如何在Next.js 13 App Router中添加Schema脚本?
摘要
Next.js 13的/app路由中的layout和page组件改变了我们向<head>添加内容的方式。如何为每个页面添加Schema脚本?Next.js会自动将任何layout或page中的<head>标签合并为一个单独的<head>。
需求目标
和所有注重SEO的网站一样,我希望在每个页面的head中加入Schema脚本。
过往实现方式
通常,直接在<head>中编写即可,示例如下:
<!-- index.html --> <head> <script type="application/ld+json"> { "@context": "https://schema.org", // ... 其余内容 } </script> </head>
而在Next.js的/pages目录中,实现方式略有不同。必须使用dangerouslySetInnerHTML属性,示例如下:
// index.tsx import Head from 'next/head' export default function Page() { return ( <Head> <script id="schema" type="application/ld+json" dangerouslySetInnerHTML={{__html: ` { "@context": "https://schema.org", // ... 其余内容 } `}} /> </Head> ) }
当前问题
现在引入/app路由后,我们有了全新的简化方式来设置元数据,无需通过next/head直接导入<head>组件。实际上,/app路由中不应再使用next/head组件。
那么问题来了:如何在每个页面中单独访问<head>添加Schema脚本?
我原本期望Next.js团队已经考虑到这一点,将Schema添加到新的metadata变量中,甚至单独设置一个变量,但目前看来他们似乎没有这样的计划。
我尝试在page中添加<head>,但遗憾的是它无法正确合并到全局<head>中。目前我能想到的唯一方法是为每个页面单独创建layout并添加Schema,但这样做非常麻烦。
解决方案
方法1:使用generateMetadata的scripts字段(推荐)
在Next.js 13.4+的/app路由中,可通过generateMetadata函数返回的scripts数组添加内联Schema脚本,会自动合并到页面<head>,支持服务器组件:
// app/[page]/page.tsx import type { Metadata } from 'next' export async function generateMetadata(): Promise<Metadata> { const schema = { "@context": "https://schema.org", "@type": "WebPage", "name": "当前页面名称", "description": "当前页面描述" // 其他Schema字段 } return { scripts: [ { id: 'page-schema', type: 'application/ld+json', dangerouslySetInnerHTML: JSON.stringify(schema) } ] } } export default function Page() { return <div>页面内容</div> }
方法2:在页面组件中直接使用<head>标签
若需在客户端组件中动态生成Schema,可直接在页面里添加<head>标签,Next.js会自动合并所有layout和page中的<head>内容:
// app/[page]/page.tsx 'use client' export default function Page() { const schema = { "@context": "https://schema.org", "@type": "WebPage", // 动态生成的Schema内容 } return ( <> <head> <script id="page-schema" type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }} /> </head> <div>页面内容</div> </> ) }
注意:服务器组件中直接在<head>写内联脚本需用dangerouslySetInnerHTML,不能直接在<script>标签内写JSON内容。
方法3:创建可复用的Schema组件
若多个页面需添加Schema,可封装通用组件,在每个页面导入并传入对应Schema数据:
// app/components/Schema.tsx interface SchemaProps { data: Record<string, any> id?: string } export default function Schema({ data, id = 'page-schema' }: SchemaProps) { return ( <head> <script id={id} type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }} /> </head> ) }
页面中使用:
// app/[page]/page.tsx import Schema from '@/app/components/Schema' export default function Page() { const pageSchema = { "@context": "https://schema.org", "@type": "WebPage", // 当前页面的Schema数据 } return ( <> <Schema data={pageSchema} /> <div>页面内容</div> </> ) }
内容的提问来源于stack exchange,提问作者andrilla

