Metadata,中文译为“元数据”,简单的来说就是描述数据的数据。
例如,一个 HTML 文件是一种数据,但 HTML 文件也能在 <head>
元素中包含描述该文档的数据,比如该文件的标题和字符编码。这些数据就被称为“元数据”。
<head>
<meta charset="utf-8" />
<title>我的测试页面</title>
</head>
不止标题和字符编码,元数据是一个非常丰富的概念,<head>
中的各种 meta 标签都是元数据,比如这是描述文档作者和文档信息的元数据:
<meta name="author" content="Chris Mills" />
<meta name="description" content="the description content" />
这是根据 Open Graph 协议编写的元数据,当分享到诸如 Facebook 这样的社交平台的时候,会展示特殊的格式和信息:
<meta property="og:image" content="..." />
<meta property="og:description" content="..." />
<meta property="og:title" content="..." />
网站的自定义图标、robots.txt
、sitemap.xml
等也是元数据:
<link rel="icon" href="favicon.ico" type="image/x-icon" />
元数据的丰富会改善 SEO 和 web 可共享性(比如定义一些特殊的字段用于内容分享到一些社交平台),为了方便定义和管理这些元数据,Next.js 提供了 Metadata API。在 Next.js 中,添加元数据的方法分为两类:
layout.js
或 page.js
中导出一个静态 metadata
对象或者一个动态的 generateMetadata
函数。通过这些选项,Next.js 会自动为页面生成相关的 <head>
元素。
要定义一个静态元数据,需要从 layout.js
或者 page.js
文件中导出一个 Metadata
对象:
// layout.js | page.js
export const metadata = {
title: '...',
description: '...',
}
export default function Page() {}
具体 Metadata 对象有哪些字段呢?我数了数源码中的字段,有 31 个,因为内容很多,所以放在本篇后面具体讲解。我们先了解下主要的定义元数据的方式。
动态元数据是指依赖动态信息如当前路由参数、外部数据、父级路由段metadata
等信息的元数据。要定义动态元数据,需要导出一个名为 generateMetadata
的函数,该函数返回一个 Metadata 对象。使用示例如下:
// app/products/[id]/page.js
export async function generateMetadata({ params, searchParams }, parent) {
// 读取路由参数
const id = params.id
// 获取数据
const product = await fetch(`https://.../${id}`).then((res) => res.json())
// 获取和拓展父路由段 metadata
const previousImages = (await parent).openGraph?.images || []
return {
title: product.title,
openGraph: {
images: ['/some-specific-page-image.jpg', ...previousImages],
},
}
}
export default function Page({ params, searchParams }) {}
让我们详细讲解下 generateMetadata 函数。该函数接收两个参数,props
和 parent
。
props
指的是包含当前路由参数的对象,该对象又有两个字段,一个是 params
,一个是 searchParams
。
Route | URL | params |
---|---|---|
app/shop/[slug]/page.js | /shop/1 | { slug: '1' } |
app/shop/[tag]/[item]/page.js | /shop/1/2 | { tag: '1', item: '2' } |
app/shop/[...slug]/page.js | /shop/1/2 | { slug: ['1', '2'] } |
那如果访问的不是动态路由呢?那 params 就是一个空对象 {}
。
searchParams
是一个包含当前 URL 搜索参数的对象,举个例子:URL 网址 | searchParams |
---|---|
/shop?a=1 | { a: '1' } |
/shop?a=1&b=2 | { a: '1', b: '2' } |
/shop?a=1&a=2 | { a: ['1', '2'] } |
searchParams
只在 page.js
中会有。
而 parent
是一个包含父路由段 metadata 对象的 promise 对象,所以示例代码中用的是 (await parent).openGraph
获取。
注意:无论是静态元数据还是动态元数据都只在服务端组件中支持。如果不依赖运行时的信息,则应该尽可能使用静态元数据方式。
缓存篇里也会讲,在 generateMetadata
、generateStaticParams
、布局、页面和服务端组件中的 fetch 请求会自动记忆化,所以不用担心多次请求有性能损失。
Next.js 会在等待 generateMetadata
中数据请求完毕再开始 UI 流式传输给客户端,这可以保证流式响应的第一部分包含 <head>
标签。
这些特殊的文件都可用于元数据:
基于文件是什么意思呢?我们以 icon 为例,icon 被用于设置浏览器网页标签的图标,虽然也可以使用静态元数据的方式设置(具体讲 metadata 对象的时候会讲到),但是 Next.js 提供了更便捷的方式,那就是约定一个文件名为 icon
的图片,格式支持 .ico
、.jpg
, .jpeg
, .png
, .svg
。也就是添加一个名为 icon.(ico|jpg|jpeg|png|svg)
的图片,Next.js 就会为该路由生成对应的元数据标签:
<link
rel="icon"
href="/icon?<generated>"
type="image/<generated>"
sizes="<generated>"
/>
甚至如果你想动态生成该图片也可以实现,约定一个名为 icon.(js|.ts|.tsx)
的文件,返回一张图片即可。
其他文件也是同理,使用这种方式快捷直观。不过具体这些文件该怎么设置,因为涉及的 API 太多,统一放在后面讲解。
要注意:基于文件的元数据具有更高的优先级,并会覆盖任何基于配置的元数据。
有两个默认的 meta
标签,即使路由未定义元数据也会添加,一个是设置网站字符编码的 charset
,一个是设置网站视口宽度和比例的 viewport
:
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
当然这两个元数据标签也都是可以覆盖的。
一个页面和它的布局以及它的父布局,都设置了一些元数据字段,有的字段重复,有的字段不同,那么 Next.js 该怎么处理呢?
这个逻辑其实我们也很容易想到,具体页面的设置应该优先级更高。Next.js 就是按照从根路由到 page.js
的顺序处理,比如当你访问 /blog/1
,处理顺序为:
app/layout.tsx
(根布局)app/blog/layout.tsx
(嵌套布局布局)app/blog/[slug]/page.tsx
(具体页面)Next.js 会按照这个顺序,将各层元数据对象浅合并,计算得到最终的元数据。重复的将会根据顺序进行替换。让我们举个例子,加入根布局的 metadata
设置为:
// app/layout.js
export const metadata = {
title: 'Acme',
openGraph: {
title: 'Acme',
description: 'Acme is a...',
},
}
page.js
的设置为:
// app/blog/page.js
export const metadata = {
title: 'Blog',
openGraph: {
title: 'Blog',
},
}
// 输出:
// <title>Blog</title>
// <meta property="og:title" content="Blog" />
在这个例子中:
app/layout.js
中的 title
被 app/blog/page.js
中的 title
替换app/layout.js
中的 openGraph
字段被 app/blog/page.js
中的 openGraph
替换,注意因为是浅合并,所以 description
字段丢失了。如果你想在多个路由之间共享一些字段,那你可以将他们提取到一个单独的变量中,比如在 app
下建立一个 shared-metadata.js
文件共享的字段:
// app/shared-metadata.js
export const openGraphImage = { images: ['http://...'] }
当需要使用时:
// app/page.js
import { openGraphImage } from './shared-metadata'
export const metadata = {
openGraph: {
...openGraphImage,
title: 'Home',
},
}
// app/about/page.js
import { openGraphImage } from '../shared-metadata'
export const metadata = {
openGraph: {
...openGraphImage,
title: 'About',
},
}
在这个例子中,OG 图片在 app/layout.js
和 app/about/page.js
中共享,然而 title
却是不同的。
比如根布局的设置为:
// app/layout.js
export const metadata = {
title: 'Acme',
openGraph: {
title: 'Acme',
description: 'Acme is a...',
},
}
页面的设置为:
// app/about/page.js
export const metadata = {
title: 'About',
}
// 输出:
// <title>About</title>
// <meta property="og:title" content="Acme" />
// <meta property="og:description" content="Acme is a..." />
在这个例子中:
app/layout.js
中的 title
被 app/about/page.js
中的 title
替代app/layout.js
中的所有 openGraph
字段被 app/about/page.js
继承,因为 app/about/page.js
没有设置 openGraph
字段JSON-LD,英文全称:JavaScript Object Notation for Linked Data
,当然这个太“全”了,稍微简写一点就是 JSON for Linking Data
,官方文档地址:json-ld.org/,是一种基于 JSON 的数据格式,用于向 Google 和其他搜索引擎描述网站上的内容。
比如如果我们在 Google 搜索 「Chocolate in a mug」,我们会看到这样的搜索结果:
这个搜索结果有评分(4.5)、评价(581条)等内容,我们打开页面,就可以看到搜索展示的这个内容其实对应了 application/ld+json
中的内容:
如果我们也要实现这样的效果,方便搜索引擎展现,该怎么做呢?
在页面加入这种结构化数据的方法很简单,只用在页面添加这样一段脚本就可以了:
<script type="application/ld+json">
// ...
</script>
具体里面的内容需要参考比如 Google 搜索中心提供的《结构化数据常规指南》,如果你写的是文章,参考 Article 章节后,可以写入以下这些属性:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "这里填写标题",
"image": [
"https://ts.yayujs.com/icon-144x144.png"
],
"datePublished": "2021-11-10T22:06:06.000Z",
"dateModified": "2022-03-04T16:00:00.000Z",
"author": [{
"@type": "Person",
"name": "冴羽",
"url": "https://github.com/mqyqingfeng/Blog"
}]
}
</script>
如何在 Next.js 中添加 JOSN-LD 呢?Next.js 推荐在 layout.js
或 page.js
组件中使用 <script>
标签,使用示例如下:
// app/products/[id]/page.js
export default async function Page({ params }) {
const product = await getProduct(params.id)
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
image: product.image,
description: product.description,
}
return (
<section>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
{/* ... */}
</section>
)
}
如果你使用 TypeScript,可以配合使用 schema-dts 这个包:
import { Product, WithContext } from 'schema-dts'
const jsonLd: WithContext<Product> = {
'@context': 'https://schema.org',
'@type': 'Product',
name: 'Next.js Sticker',
image: 'https://nextjs.org/imgs/sticker.png',
description: 'Dynamic at the speed of static.',
}
添加 JSON-LD 并部署后,你可以使用 https://search.google.com/test/rich-results 来测试网页是否支持富媒体搜索结果,使用 https://validator.schema.org/ 测试页面的结构化数据,输入 https://yayujs.com
试试。
接下来我们来介绍下 Metadata 对象的字段有哪些,由于字段很多,你可以先了解一下有哪些,需要使用的时候再查找使用。每个字段都配有使用的例子。
设置文档的标题,该值可以是一个简单的字符串,也可以是一个可选的模板对象。
先说字符串:
// layout.js | page.js
export const metadata = {
title: 'Next.js',
}
注意在 layout.js
和 page.js
中都可以定义,输出结果为:
<title>Next.js</title>
再说模板对象:
// app/layout.js
export const metadata = {
title: {
default: '...',
template: '...',
absolute: '...',
},
}
其中 default
为没有定义 title
的子路由提供一个备用 title:
// app/layout.tsx
export const metadata = {
title: {
default: 'Acme',
},
}
// app/about/page.tsx
export const metadata = {}
// Output: <title>Acme</title>
template
可用于为子路由的 title
添加一个前缀或者后缀,比如:
// app/layout.js
export const metadata = {
title: {
template: '%s | Acme',
default: 'Acme', // 设置 template 的时候,default 必传
},
}
// app/about/page.js
export const metadata = {
title: 'About',
}
// Output: <title>About | Acme</title>
使用 template
的时候要注意:
title.template
的时候,title.default
是必要的layout.js
中的 title.template
不会应用于同级的 page.js
中的 title
page.js
中的 title.template
不会有效果,因为页面没有子路由段。title
或者 title.default
,title.template
是不生效的absolute
用于设置标题,但会忽略父路由段中设置的 title.template
:
// app/layout.js
export const metadata = {
title: {
template: '%s | Acme',
},
}
// app/about/page.js
export const metadata = {
title: {
absolute: 'About',
},
}
// Output: <title>About</title>
这三个值在 layout.js
和 page.js
中的表现会有不同,让我们细致梳理一下:
在 layout.js
中:
title
(字符串)和 title.default
定义的是未设置标题的子路由段的默认标题。它会根据最近的父路由段中的 title.template
进行拓展。举个例子:// app/layout.js
export const metadata = {
title: {
default: 'home',
template: '%s | Home'
}
}
// app/about/layout.js
export const metadata = {
title: {
default: 'about'
}
}
如果 /about/company
没有定义自己标题,那它的标题为:about | Home
。而如果最近的父路由段没有定义 title.template
,那就不进行拓展。
PS:不过根据 demo 测试,如果 app/layout.js
如上相同设置,app/about/layout.js
不设置,app/about/company/layout.js
设置 default: 'company'
,访问 /about/company/members
时,标题为 company | Home
,也就是说实际的测试结果是如果完全不设置 metadata,默认标题会根据最近的存在的 title.template
进行扩展,并不一定是最近的父路由段中的 title.template
。
title.absolute
定义子路由段的默认标题,它会忽略来自父路由段的 title.template
,也就是说即使最近的父路由段有 title.template
,也不进行拓展(所以才叫 "absoulte" 绝对标题)title.template
为子路由段定义一个新标题模板,也就是说即使每层 layout 都设置了 title.template
,也不会出现 xxx | Company | About | Home
这种标题,设置标题的子路由段只会根据父路由段的 title.template
进行设置。在 page.js
中:
PS:根据 demo 测试,如果 page.js
未设置 title
,会优先使用同级 layout.js
中设置的 title
,包括字符串、title.default
和 title.abslolute
的方式。
如果祖先 layout.js
中只有一层设置了标题,而其他都未设置,页面的标题会使用该标题,比如 app/layout.js
中设置 title: 'home'
,app/about/company/members
中的各层 layout.js
都未设置,/about/company/members
的 title
为 home
。欢迎写 demo 验证。
page.js
定义了路由 title(字符串),它会根据最近的父路由段中的 title.template 进行拓展。PS:不过根据 demo 测试,如同 layout.js
,如果父级完全不设置 metadata,标题会根据最近的存在的 title.template
进行扩展,并不一定是最近的父路由段中的 title.template
。
换句话说,如果不设置 layout.js
的 metadata
,在标题的处理上,相当于这层不存在。
title.absolute
定义路由标题,它会忽略父路由段中的 title.template
.page.js
中设置 title.template
没有效果。总之,在使用的时候,尽可能保证每个页面都有设置 title,就不会遇到奇怪的逻辑了。
设置页面的描述:
// layout.js | page.js
export const metadata = {
description: 'The React Framework for the Web',
}
输出的 HTML 为:
<meta name="description" content="The React Framework for the Web" />
// layout.js | page.js
export const metadata = {
generator: 'Next.js',
applicationName: 'Next.js',
referrer: 'origin-when-cross-origin',
keywords: ['Next.js', 'React', 'JavaScript'],
authors: [{ name: 'Seb' }, { name: 'Josh', url: 'https://nextjs.org' }],
creator: 'Jiachi Liu',
publisher: 'Sebastian Markbåge',
formatDetection: {
email: false,
address: false,
telephone: false,
},
}
输出的 HTML 为:
<meta name="application-name" content="Next.js" />
<meta name="author" content="Seb" />
<link rel="author" href="https://nextjs.org" />
<meta name="author" content="Josh" />
<meta name="generator" content="Next.js" />
<meta name="keywords" content="Next.js,React,JavaScript" />
<meta name="referrer" content="origin-when-cross-origin" />
<meta name="color-scheme" content="dark" />
<meta name="creator" content="Jiachi Liu" />
<meta name="publisher" content="Sebastian Markbåge" />
<meta name="format-detection" content="telephone=no, address=no, email=no" />
设置 metadata 字段中地址的 URL 前缀,看个例子就明白了:
// layout.js | page.js
export const metadata = {
metadataBase: new URL('https://acme.com'),
alternates: {
canonical: '/',
languages: {
'en-US': '/en-US',
'de-DE': '/de-DE',
},
},
openGraph: {
images: '/og-image.png',
},
}
输出的 HTML 为:
<link rel="canonical" href="https://acme.com" />
<link rel="alternate" hreflang="en-US" href="https://acme.com/en-US" />
<link rel="alternate" hreflang="de-DE" href="https://acme.com/de-DE" />
<meta property="og:image" content="https://acme.com/og-image.png" />
关于 metadataBase
和 metadata
,Next.js 会自动处理,并不是简单的字符串拼接,比如当你设置 metadataBase
为:
// app/layout.js
export const metadata = {
metadataBase: new URL('https://acme.com'),
}
如果你传入 metadata
的值为:
metadata 字段 | 解析 URL |
---|---|
/ | https://acme.com |
./ | https://acme.com |
payments | https://acme.com/payments |
/payments | https://acme.com/payments |
./payments | https://acme.com/payments |
../payments | https://acme.com/payments |
https://beta.acme.com/payments | https://beta.acme.com/payments |
其他关于 metadataBase:
metadataBase
通常设置在app/layout.js
,应用于所有路由metadataBase
可以设置的更具体,比如 https://app.acme.com
、https://acme.com/start/from/here
等metadata
提供了绝对地址,metadataBase
就会被忽略metadataBase
的时候,metadata
使用相对地址会报错metadataBase
和相对地址间处理多余的斜线,比如 metadataBase
为 https://acme.com/
,metadata
为 /path
,Next.js 也能正确处理为 https://acme.com/path
。// layout.js | page.js
export const metadata = {
openGraph: {
title: 'Next.js',
description: 'The React Framework for the Web',
url: 'https://nextjs.org',
siteName: 'Next.js',
images: [
{
url: 'https://nextjs.org/og.png',
width: 800,
height: 600,
},
{
url: 'https://nextjs.org/og-alt.png',
width: 1800,
height: 1600,
alt: 'My custom alt',
},
],
locale: 'en_US',
type: 'website',
},
}
输出的 HTML 为:
<meta property="og:title" content="Next.js" />
<meta property="og:description" content="The React Framework for the Web" />
<meta property="og:url" content="https://nextjs.org/" />
<meta property="og:site_name" content="Next.js" />
<meta property="og:locale" content="en_US" />
<meta property="og:image:url" content="https://nextjs.org/og.png" />
<meta property="og:image:width" content="800" />
<meta property="og:image:height" content="600" />
<meta property="og:image:url" content="https://nextjs.org/og-alt.png" />
<meta property="og:image:width" content="1800" />
<meta property="og:image:height" content="1600" />
<meta property="og:image:alt" content="My custom alt" />
<meta property="og:type" content="website" />
export const metadata = {
openGraph: {
title: 'Next.js',
description: 'The React Framework for the Web',
type: 'article',
publishedTime: '2023-01-01T00:00:00.000Z',
authors: ['Seb', 'Josh'],
},
}
输出的 HTML 为:
<meta property="og:title" content="Next.js" />
<meta property="og:description" content="The React Framework for the Web" />
<meta property="og:type" content="article" />
<meta property="article:published_time" content="2023-01-01T00:00:00.000Z" />
<meta property="article:author" content="Seb" />
<meta property="article:author" content="Josh" />
// layout.js | page.js
export const metadata = {
robots: {
index: false,
follow: true,
nocache: true,
googleBot: {
index: true,
follow: false,
noimageindex: true,
'max-video-preview': -1,
'max-image-preview': 'large',
'max-snippet': -1,
},
},
}
输出的 HTML 为:
<meta name="robots" content="noindex, follow, nocache" />
<meta
name="googlebot"
content="index, nofollow, noimageindex, max-video-preview:-1, max-image-preview:large, max-snippet:-1"
/>
// layout.js | page.js
export const metadata = {
icons: {
icon: '/icon.png',
shortcut: '/shortcut-icon.png',
apple: '/apple-icon.png',
other: {
rel: 'apple-touch-icon-precomposed',
url: '/apple-touch-icon-precomposed.png',
},
},
}
输出的 HTML 为:
<link rel="shortcut icon" href="/shortcut-icon.png" />
<link rel="icon" href="/icon.png" />
<link rel="apple-touch-icon" href="/apple-icon.png" />
<link
rel="apple-touch-icon-precomposed"
href="/apple-touch-icon-precomposed.png"
/>
export const metadata = {
icons: {
icon: [{ url: '/icon.png' }, new URL('/icon.png', 'https://example.com')],
shortcut: ['/shortcut-icon.png'],
apple: [
{ url: '/apple-icon.png' },
{ url: '/apple-icon-x3.png', sizes: '180x180', type: 'image/png' },
],
other: [
{
rel: 'apple-touch-icon-precomposed',
url: '/apple-touch-icon-precomposed.png',
},
],
},
}
输出的 HTML 为:
<link rel="shortcut icon" href="/shortcut-icon.png" />
<link rel="icon" href="/icon.png" />
<link rel="apple-touch-icon" href="/apple-icon.png" />
<link
rel="apple-touch-icon-precomposed"
href="/apple-touch-icon-precomposed.png"
/>
<link rel="icon" href="https://example.com/icon.png" />
<link
rel="apple-touch-icon"
href="/apple-icon-x3.png"
sizes="180x180"
type="image/png"
/>
metadata 中的 themeColor 已废弃,使用 viewport 配置项
// layout.js | page.js
export const metadata = {
manifest: 'https://nextjs.org/manifest.json',
}
输出的 HTML 为:
<link rel="manifest" href="https://nextjs.org/manifest.json" />
// layout.js | page.js
export const metadata = {
twitter: {
card: 'summary_large_image',
title: 'Next.js',
description: 'The React Framework for the Web',
siteId: '1467726470533754880',
creator: '@nextjs',
creatorId: '1467726470533754880',
images: ['https://nextjs.org/og.png'],
},
}
输出的 HTML 为:
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site:id" content="1467726470533754880" />
<meta name="twitter:creator" content="@nextjs" />
<meta name="twitter:creator:id" content="1467726470533754880" />
<meta name="twitter:title" content="Next.js" />
<meta name="twitter:description" content="The React Framework for the Web" />
<meta name="twitter:image" content="https://nextjs.org/og.png" />
// layout.js | page.js
export const metadata = {
twitter: {
card: 'app',
title: 'Next.js',
description: 'The React Framework for the Web',
siteId: '1467726470533754880',
creator: '@nextjs',
creatorId: '1467726470533754880',
images: {
url: 'https://nextjs.org/og.png',
alt: 'Next.js Logo',
},
app: {
name: 'twitter_app',
id: {
iphone: 'twitter_app://iphone',
ipad: 'twitter_app://ipad',
googleplay: 'twitter_app://googleplay',
},
url: {
iphone: 'https://iphone_url',
ipad: 'https://ipad_url',
},
},
},
}
输出的 HTML 为:
<meta name="twitter:site:id" content="1467726470533754880" />
<meta name="twitter:creator" content="@nextjs" />
<meta name="twitter:creator:id" content="1467726470533754880" />
<meta name="twitter:title" content="Next.js" />
<meta name="twitter:description" content="The React Framework for the Web" />
<meta name="twitter:card" content="app" />
<meta name="twitter:image" content="https://nextjs.org/og.png" />
<meta name="twitter:image:alt" content="Next.js Logo" />
<meta name="twitter:app:name:iphone" content="twitter_app" />
<meta name="twitter:app:id:iphone" content="twitter_app://iphone" />
<meta name="twitter:app:id:ipad" content="twitter_app://ipad" />
<meta name="twitter:app:id:googleplay" content="twitter_app://googleplay" />
<meta name="twitter:app:url:iphone" content="https://iphone_url" />
<meta name="twitter:app:url:ipad" content="https://ipad_url" />
<meta name="twitter:app:name:ipad" content="twitter_app" />
<meta name="twitter:app:name:googleplay" content="twitter_app" />
Next.js 14 起已废弃,使用 viewport 配置项
// layout.js | page.js
export const metadata = {
verification: {
google: 'google',
yandex: 'yandex',
yahoo: 'yahoo',
other: {
me: ['my-email', 'my-link'],
},
},
}
输出的 HTML 为:
<meta name="google-site-verification" content="google" />
<meta name="y_key" content="yahoo" />
<meta name="yandex-verification" content="yandex" />
<meta name="me" content="my-email" />
<meta name="me" content="my-link" />
// layout.js | page.js
export const metadata = {
itunes: {
appId: 'myAppStoreID',
appArgument: 'myAppArgument',
},
appleWebApp: {
title: 'Apple Web App',
statusBarStyle: 'black-translucent',
startupImage: [
'/assets/startup/apple-touch-startup-image-768x1004.png',
{
url: '/assets/startup/apple-touch-startup-image-1536x2008.png',
media: '(device-width: 768px) and (device-height: 1024px)',
},
],
},
}
输出的 HTML 为:
<meta
name="apple-itunes-app"
content="app-id=myAppStoreID, app-argument=myAppArgument"
/>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="Apple Web App" />
<link
href="/assets/startup/apple-touch-startup-image-768x1004.png"
rel="apple-touch-startup-image"
/>
<link
href="/assets/startup/apple-touch-startup-image-1536x2008.png"
media="(device-width: 768px) and (device-height: 1024px)"
rel="apple-touch-startup-image"
/>
<meta
name="apple-mobile-web-app-status-bar-style"
content="black-translucent"
/>
// layout.js | page.js
export const metadata = {
alternates: {
canonical: 'https://nextjs.org',
languages: {
'en-US': 'https://nextjs.org/en-US',
'de-DE': 'https://nextjs.org/de-DE',
},
media: {
'only screen and (max-width: 600px)': 'https://nextjs.org/mobile',
},
types: {
'application/rss+xml': 'https://nextjs.org/rss',
},
},
}
输出的 HTML 为:
<link rel="canonical" href="https://nextjs.org" />
<link rel="alternate" hreflang="en-US" href="https://nextjs.org/en-US" />
<link rel="alternate" hreflang="de-DE" href="https://nextjs.org/de-DE" />
<link
rel="alternate"
media="only screen and (max-width: 600px)"
href="https://nextjs.org/mobile"
/>
<link
rel="alternate"
type="application/rss+xml"
href="https://nextjs.org/rss"
/>
// layout.js | page.js
export const metadata = {
appLinks: {
ios: {
url: 'https://nextjs.org/ios',
app_store_id: 'app_store_id',
},
android: {
package: 'com.example.android/package',
app_name: 'app_name_android',
},
web: {
url: 'https://nextjs.org/web',
should_fallback: true,
},
},
}
输出的 HTML 为:
<meta property="al:ios:url" content="https://nextjs.org/ios" />
<meta property="al:ios:app_store_id" content="app_store_id" />
<meta property="al:android:package" content="com.example.android/package" />
<meta property="al:android:app_name" content="app_name_android" />
<meta property="al:web:url" content="https://nextjs.org/web" />
<meta property="al:web:should_fallback" content="true" />
// layout.js | page.js
export const metadata = {
archives: ['https://nextjs.org/13'],
}
输出的 HTML 为:
<link rel="archives" href="https://nextjs.org/13" />
// layout.js | page.js
export const metadata = {
assets: ['https://nextjs.org/assets'],
}
输出的 HTML 为:
<link rel="assets" href="https://nextjs.org/assets" />
// layout.js | page.js
export const metadata = {
bookmarks: ['https://nextjs.org/13'],
}
输出的 HTML 为:
<link rel="bookmarks" href="https://nextjs.org/13" />
// layout.js | page.js
export const metadata = {
category: 'technology',
}
输出的 HTML 为:
<meta name="category" content="technology" />
所有的 metadata 选项都可以通过像本节介绍的这种方式直接覆盖,Next.js 内部已经定义了这些字段。然而你也可能会想自定义一些 metadata 标签,此时就可以使用 other 选项:
export const metadata = {
other: {
custom: 'meta',
},
}
输出的 HTML 为:
<meta name="custom" content="meta" />