当分享网站链接时,许多社交媒体平台可以显示丰富的预览信息(最显著的是封面图片、标题和描述)。Quartz通过合理的默认配置自动处理了大部分设置,但若需要更多控制权,您可以通过配置Frontmatter属性来自定义这些内容。
Quartz还能为每个页面动态生成专属封面图片,用于社交媒体链接预览。要启用此功能,请在quartz.config.ts
中设置generateSocialImages: true
。
效果展示
在quartz.config.ts
中启用generateSocialImages
后,内容创作页面的社交媒体链接预览效果如下:
亮色主题 | 暗色主题 |
---|---|
![]() | ![]() |
测试时推荐使用opengraph.xyz查看链接在不同平台上的显示效果(更多信息参见本地测试)。
自定义配置
您可以在quartz配置文件中自定义图片生成方式。
例如,当设置generateSocialImages: true
时,默认配置如下:
generateSocialImages: {
colorScheme: "lightMode", // what colors to use for generating image, same as theme colors from config, valid values are "darkMode" and "lightMode"
width: 1200, // width to generate with (in pixels)
height: 630, // height to generate with (in pixels)
excludeRoot: false, // wether to exclude "/" index path to be excluded from auto generated images (false = use auto, true = use default og image)
}
Frontmatter 属性
提示
即使禁用
generateSocialImages
,通过 frontmatter 覆盖社交媒体预览属性的功能仍然有效。
以下属性可用于自定义链接预览:
属性 | 别名 | 摘要 |
---|---|---|
socialDescription | description | 用于预览的描述文字。 |
socialImage | image , cover | 预览图片链接。 |
socialImage
属性应包含相对于 quartz/static
的图片链接。如果您在 quartz/static/my-images
中有图片文件夹,socialImage
的示例可以是 "my-images/cover.png"
。
信息
封面图片的优先级顺序如下:
frontmatter 属性 > 生成图片(如果启用)> 默认图片
。默认图片(
quartz/static/og-image.png
)仅在没有设置其他图片时作为后备使用。如果启用了generateSocialImages
,它将被视为每个页面的新默认图片,但可以通过为页面设置socialImage
frontmatter 属性来覆盖。
完全自定义图片生成
您可以通过将自己的组件传递给 generateSocialImages.imageStructure
来完全自定义生成图片的样式。该组件使用 html/css + 页面元数据/配置选项,并通过 satori 将其转换为图片。Vercel 提供了在线游乐场 用于预览您的 html/css 作为图片的效果。这是原型设计自定义样式的理想方式。
建议在 quartz/util/og.tsx
或其他 .tsx
文件中编写自己的图片组件,否则将其传递到配置中将无法正常工作。默认图片组件示例可在 og.tsx
的 defaultImage()
中找到。
提示
Satori 仅支持所有有效 CSS 属性的子集。所有支持的属性可在其文档 中查看。
您的自定义图片组件应具有 SocialImageOptions["imageStructure"]
类型,以便于开发。使用此类型的组件时,您将获得以下变量:
imageStructure: (
cfg: GlobalConfiguration, // global Quartz config (useful for getting theme colors and other info)
userOpts: UserOpts, // options passed to `generateSocialImage`
title: string, // title of current page
description: string, // description of current page
fonts: SatoriOptions["fonts"], // header + body font
) => JSXInternal.Element
现在,你可以尽情发挥创意,设计属于自己的图片组件了!如需参考或获取灵感,可以查看默认图片组件的标记结构。
示例
以下是一些常用标记的示例,助你快速上手:
获取主题色
cfg.theme.colors[colorScheme].<colorName>
,其中<colorName>
对应ColorScheme
中的键名(定义于quartz/util/theme.ts
文件顶部)使用页面标题/描述
<p>{title}</p>
/<p>{description}</p>
使用字体族
详见下文「字体」章节
字体
组件会接收一个包含标题字体和正文字体的数组(首项为标题字体,次项为正文字体)。这些字体对应 quartz.config.ts
中 theme.typography.header
和 theme.typography.body
的配置,并以 satori
要求的格式传递。在 CSS 中使用时,请通过 .name
属性调用(例如 fontFamily: fonts[1].name
表示使用”正文”字体族)。
使用标题字体的组件示例如下:
export const myImage: SocialImageOptions["imageStructure"] = (...) => {
return <p style={{ fontFamily: fonts[0].name }}>Cool Header!</p>
}
本地字体
若您在
static
文件夹下使用本地字体,请确保在custom.scss
中正确设置@font-face
custom.scss @font-face { font-family: "Newsreader"; font-style: normal; font-weight: normal; font-display: swap; src: url("/static/Newsreader.woff2") format("woff2"); }
然后在
quartz/util/og.tsx
中,可以按如下方式加载 satori 字体:quartz/util/og.tsx const headerFont = joinSegments("static", "Newsreader.woff2") const bodyFont = joinSegments("static", "Newsreader.woff2") export async function getSatoriFont(cfg: GlobalConfiguration): Promise<SatoriOptions["fonts"]> { const headerWeight: FontWeight = 700 const bodyWeight: FontWeight = 400 const url = new URL(`https://${cfg.baseUrl ?? "example.com"}`) const [header, body] = await Promise.all( [headerFont, bodyFont].map((font) => fetch(`${url.toString()}/${font}`).then((res) => res.arrayBuffer()), ), ) return [ { name: cfg.theme.typography.header, data: header, weight: headerWeight, style: "normal" }, { name: cfg.theme.typography.body, data: body, weight: bodyWeight, style: "normal" }, ] }
这样配置后,该字体即可与您的自定义结构配合使用
本地测试
若要在部署前预览页面完整效果,可以通过端口转发进行本地测试。在 VSCode 中,可按照此指南轻松实现(若需使用 opengraph.xyz 等外部工具测试,请确保将 Visibility
设置为 public
)。
若启用了 generateSocialImages
功能,可在 public/static/social-images
目录下查看所有生成的图片。
技术信息
生成的图片将保存为 .webp
格式,这种格式能有效控制文件体积(平均图片约 19kB
)。同时使用 sharp 进行进一步压缩。
使用图片时,系统会自动设置适当的 Open Graph 和 Twitter 元标签,确保其显示效果符合预期。
示例
除了默认的图片生成模板(位于 quartz/util/og.tsx
),您还可以添加自定义模板!您可以选择直接修改源文件(不推荐)或新建文件(例如下方展示的 customSocialImage.tsx
源码)。
添加自定义模板后,可按照以下方式更新 quartz.config.ts
以使用您的图片生成模板:
// Import component at start of file
import { customImage } from "./quartz/util/customSocialImage.tsx"
// In main config
const config: QuartzConfig = {
...
generateSocialImages: {
...
imageStructure: customImage, // tells quartz to use your component when generating images
},
}
以下示例将生成如下所示的图片:
浅色 | 深色 |
---|---|
![]() | ![]() |
此示例(及默认模板)使用您在 quartz 配置中指定的主题颜色和字体。字体通过 prop 传入,其中 fonts[0]
包含标题字体,fonts[1]
包含正文字体(更多信息请参阅 fonts 章节)。
import { SatoriOptions } from "satori/wasm"
import { GlobalConfiguration } from "../cfg"
import { SocialImageOptions, UserOpts } from "./imageHelper"
import { QuartzPluginData } from "../plugins/vfile"
export const customImage: SocialImageOptions["imageStructure"] = (
cfg: GlobalConfiguration,
userOpts: UserOpts,
title: string,
description: string,
fonts: SatoriOptions["fonts"],
fileData: QuartzPluginData,
) => {
// How many characters are allowed before switching to smaller font
const fontBreakPoint = 22
const useSmallerFont = title.length > fontBreakPoint
const { colorScheme } = userOpts
return (
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "flex-start",
alignItems: "center",
height: "100%",
width: "100%",
}}
>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "100%",
width: "100%",
backgroundColor: cfg.theme.colors[colorScheme].light,
flexDirection: "column",
gap: "2.5rem",
paddingTop: "2rem",
paddingBottom: "2rem",
}}
>
<p
style={{
color: cfg.theme.colors[colorScheme].dark,
fontSize: useSmallerFont ? 70 : 82,
marginLeft: "4rem",
textAlign: "center",
marginRight: "4rem",
fontFamily: fonts[0].name,
}}
>
{title}
</p>
<p
style={{
color: cfg.theme.colors[colorScheme].dark,
fontSize: 44,
marginLeft: "8rem",
marginRight: "8rem",
lineClamp: 3,
fontFamily: fonts[1].name,
}}
>
{description}
</p>
</div>
<div
style={{
height: "100%",
width: "2vw",
position: "absolute",
backgroundColor: cfg.theme.colors[colorScheme].tertiary,
opacity: 0.85,
}}
/>
</div>
)
}
高级示例
以下示例包含一个自定义背景和格式化日期的个性化社交图片。
custom-og.tsx export const og: SocialImageOptions["Component"] = ( cfg: GlobalConfiguration, fileData: QuartzPluginData, { colorScheme }: Options, title: string, description: string, fonts: SatoriOptions["fonts"], ) => { let created: string | undefined let reading: string | undefined if (fileData.dates) { created = formatDate(getDate(cfg, fileData)!, cfg.locale) } const { minutes, text: _timeTaken, words: _words } = readingTime(fileData.text!) reading = i18n(cfg.locale).components.contentMeta.readingTime({ minutes: Math.ceil(minutes), }) const Li = [created, reading] return ( <div style={{ position: "relative", display: "flex", flexDirection: "row", alignItems: "flex-start", height: "100%", width: "100%", backgroundImage: `url("https://${cfg.baseUrl}/static/og-image.jpeg")`, backgroundSize: "100% 100%", }} > <div style={{ position: "absolute", top: 0, left: 0, right: 0, bottom: 0, background: "radial-gradient(circle at center, transparent, rgba(0, 0, 0, 0.4) 70%)", }} /> <div style={{ display: "flex", height: "100%", width: "100%", flexDirection: "column", justifyContent: "flex-start", alignItems: "flex-start", gap: "1.5rem", paddingTop: "4rem", paddingBottom: "4rem", marginLeft: "4rem", }} > <img src={`"https://${cfg.baseUrl}/static/icon.jpeg"`} style={{ position: "relative", backgroundClip: "border-box", borderRadius: "6rem", }} width={80} /> <div style={{ display: "flex", flexDirection: "column", textAlign: "left", fontFamily: fonts[0].name, }} > <h2 style={{ color: cfg.theme.colors[colorScheme].light, fontSize: "3rem", fontWeight: 700, marginRight: "4rem", fontFamily: fonts[0].name, }} > {title} </h2> <ul style={{ color: cfg.theme.colors[colorScheme].gray, gap: "1rem", fontSize: "1.5rem", fontFamily: fonts[1].name, }} > {Li.map((item, index) => { if (item) { return <li key={index}>{item}</li> } })} </ul> </div> <p style={{ color: cfg.theme.colors[colorScheme].light, fontSize: "1.5rem", overflow: "hidden", marginRight: "8rem", textOverflow: "ellipsis", display: "-webkit-box", WebkitLineClamp: 7, WebkitBoxOrient: "vertical", lineClamp: 7, fontFamily: fonts[1].name, }} > {description} </p> </div> </div> ) }