- 后端
- 前端
- Web框架
- 开发工具
【免费下载链接】redwood
RedwoodGraphQL
本指南以 RedwoodJS 官方文档 seo-head.md 为骨架,系统讲解在 Redwood 应用中完成 HTML 级 SEO 的完整路径:从redwood.toml设置应用标题与标题模板,到用内置<Head>组件与<Metadata>组件精确控制每个页面的<title>、描述、OpenGraph 与 Twitter 卡片等<meta>标签,再到结合 Cell 与预渲染实现动态标签。读完本文,你将掌握 Redwood 中"静态配置 + 组件化注入"两套 SEO 手段,并能直接落地到自己的页面、布局与 Cell 中。
一、为什么 Redwood 把 SEO 做成"组件化"
Search Engine Optimization(SEO)常被视为一门"玄学",但对大多数应用而言,最基础、最可靠的优化手段就是输出正确的 HTML 标签:<title>、<meta name="description">、OpenGraph 的og:image等。Redwood 将这类 HTML 级 SEO 能力内建到框架中,让开发者不必手写繁琐的 DOM 操作,而是以声明式组件的方式声明"页面想要什么标签",由框架负责渲染并注入<head>。
Redwood 的这套能力由 packages/web 包提供,核心组件最终统一从@redwoodjs/web导出(见 packages/web/src/index.ts):
Head:基于react-helmet-async的Helmet重导出的通用头部组件;Metadata:面向 SEO/OpenGraph 的便捷组件,能以极简的 props 语法批量生成<meta>标签;MetaTags:Redwood 6.6.0 之前的旧组件,现已弃用,仅做向后兼容。
整套机制分两层:全局静态层(应用标题与标题模板)和页面动态层(<Head>/<Metadata>按页注入)。
二、设置应用标题:redwood.toml中的title
默认情况下,新创建的 Redwood 应用页面标题是 "Redwood App"。这个默认值来自框架的项目配置,见 packages/project-config/src/config.ts 中DEFAULT_CONFIG.web.title = 'Redwood App'。要改成你自己的品牌名,编辑项目根目录的redwood.toml:
[web] - title = "Redwood App" + title = "My Cool App" port = 8910 apiUrl = "/.redwood/functions"几点关键说明:
- 这个
title是应用级默认标题:如果你在某个页面没有显式定义自己的标题,该应用标题就会作为回退值使用; - 它同时会被标题模板(title template)引用,作为模板中的
%AppTitle占位符; - 从源码看,config.ts 中
[web]段的完整默认配置还包括port = 8910、path = './web'、apiUrl = '/.redwood/functions'、fastRefresh = true等,title只是其中之一;并且title支持环境变量插值,例如title = "App running on ${APP_ENV}"(见 fixtures/redwood.withEnv.toml 与对应测试 config.test.ts),可用于按环境区分站点名。
标题模板(Title Template)
光有应用标题还不够——你希望每个页面的标题在浏览器标签页里呈现为统一的格式,比如Home Page | My Cool App。这就是titleTemplate的用途:它作为RedwoodProvider的 prop 传入,作用于所有页面。
- <RedwoodProvider> + <RedwoodProvider titleTemplate="%PageTitle | %AppTitle"> /* ... */ <RedwoodProvider />模板格式完全由你决定,框架只识别两个占位符:
"%PageTitle | %AppTitle" => "Home Page | Redwood App" "%AppTitle · %PageTitle" => "Redwood App · Home Page" "%PageTitle : %AppTitle" => "Home Page : Redwood App"源码层面,packages/web/src/components/RedwoodProvider.tsx 的实现揭示了其工作原理:
- 从
globalThis.__REDWOOD__APP_TITLE读取应用标题(该全局值由构建/运行时注入); - 用
titleTemplate.replace(/%AppTitle/g, appTitle)将%AppTitle替换为真实应用标题; - 用
titleTemplate.replace(/%PageTitle/g, '%s')将%PageTitle替换为%s——这是react-helmet-async的模板占位符,最终在渲染页面时由各页面实际的<title>内容填充; - 最终以
Helmet titleTemplate={template()} defaultTitle={appTitle}交给react-helmet-async管理。
注意:当启用流式 SSR(RWJS_ENV.RWJS_EXP_STREAMING_SSR)时,RedwoodProvider直接透传 children,不再使用 Helmet,头部标签改由 PortalHead 机制处理(详见下文第五节)。
三、用<Head>向页面<head>添加内容
redwood.toml的标题是全局兜底,而每个页面通常需要自己的标题。Redwood 提供内置的<Head>组件(实际上就是react-helmet-async的Helmet的再导出),在任意页面组件中这样使用:
+import { Head } from '@redwoodjs/web' const AboutPage = () => { return ( <div> <h2>AboutPage</h2> + <Head> + <title>About the team</title> + </Head><Head>内部可以放任何合法的<head>标签:<title>、<meta>、<link>、<script>、<style>等均可。它是通用出口,而 SEO 相关的便捷封装则由下一节的<Metadata>组件承担。
嵌套标签的覆盖规则
Redwood 底层使用react-helmet-async,其规则是:组件树中越靠下的标签优先级越高。例如你在 Layout 里设置了一个标题,在 Page 里又设置了另一个标题,最终渲染的是 Page 里的那个。这个特性让你可以在 Layout 中声明共享标签(如og:site_name、robots),在具体页面中只覆盖需要变化的标签(如title、description),实现"继承 + 覆盖"的组合模式。
四、<Metadata>:声明式生成<meta>与 OpenGraph 标签
日常 SEO 往往不止标题和描述,还包括 OpenGraph(Facebook、Slack、Twitter 等在分享链接时"unfurl"预览所需)协议头。Redwood 提供<Metadata>便捷组件,用极简的 props 语法批量生成这些标签;同时它也接受 children,让你可以追加任意自定义<meta>内容。
4.1 一个典型示例
import { Metadata } from '@redwoodjs/web' const AboutPage = () => { return ( <div> <Metadata title="About page" description="About the awesome team" og={{ image: "https://example.com/images/og.png", url: "https://example.com/start" }} robots="nofollow" > <meta httpEquiv="content-type" content="text/html; charset=UTF-8" /> </Metadata> <h2>About Page</h2> <p className="font-light">This is the about page!</p> </div> ) } export default AboutPage这段 JSX 会被转换并注入到<head>,最终 HTML 如下:
<title>About page</title> <meta name="title" content="About page" /> <meta name="description" content="About the awesome team" /> <meta name="robots" content="nofollow" /> <meta property="og:title" content="About page" /> <meta property="og:description" content="About the awesome team" /> <meta property="og:image" content="https://example.com/images/og.png" /> <meta property="og:url" content="https://example.com/start" /> <meta property="og:type" content="website" /> <meta http-equiv="content-type" content="text/html; charset=UTF-8" />设置og:image后,当链接被分享到 Facebook、Slack 等平台时,平台会抓取该图片作为预览图展示(即 "unfurling"):
如果你希望完全绕开<Metadata>的自动插值、手写原生<meta>标签,可以把它作为 children 传入<Metadata>,或直接放进<Head>中。
4.2 Props 规则一:普通键值对 →name/content
任何"普通"的键值 prop 都会生成带name和content属性的<meta>标签:
<Metadata description="Lorem ipsum dolar sit amet..." /> // generates <meta name="description" content="Lorem ipsum dolar sit amet..." />children 里的元素则原样 1:1 拷贝到输出中(注意 React 的httpEquiv会被渲染为 HTML 的http-equiv):
<Metadata description="Lorem ipsum dolar sit amet..."> <meta httpEquiv="refresh" content="30" /> </Metadata> // generates <meta name="description" content="Lorem ipsum dolar sit amet..." /> <meta http-equiv="refresh" content="30" />4.3 Props 规则二:对象值 →property/content命名空间
值为对象的 prop 会生成带property和content属性的<meta>标签,property由嵌套键名以:连接而成:
<Metadata music={{ album: { track: 12 } }}/> // generates <meta property="music:album:track" content="12" />这正是 OpenGraph 这类"嵌套结构"协议所需的语法:
<Metadata og={{ image:"http://host.test/image.jpg" }} /> // generates <meta property="og:image" content="http://host.test/image.jpg" />OpenGraph 规范允许同名的多个property标签,用数组即可实现:
<Metadata og={{ image: ["http://host.test/image1.jpg", "http://host.test/image2.jpg"] }} /> // generates <meta property="og:image" content="http://host.test/image1.jpg" /> <meta property="og:image" content="http://host.test/image2.jpg" />对象与字符串还可以任意组合,构建任意复杂的结构。比如给多张og:image附带各自的尺寸信息:
<Metadata og={{ image: [ 'http://host.test/image1.jpg', { width: 320, height: 240 }, 'http://host.test/image2.jpg', 'http://host.test/image3.jpg', { width: 1024 }, { height: 768 }, ], }} /> // generates <meta property="og:image" content="http://host.test/image1.jpg" /> <meta property="og:image:width" content="320" /> <meta property="og:image:height" content="240" /> <meta property="og:image" content="http://host.test/image2.jpg" /> <meta property="og:image" content="http://host.test/image3.jpg" /> <meta property="og:image:width" content="1024" /> <meta property="og:image:height" content="768" />从源码 packages/web/src/components/Metadata.tsx 可以看到,这是由递归函数propToMetaTag实现的:数组被flatMap摊平逐项处理;对象则把parentKey:key作为新的属性名并切换为property属性递归;最终叶子节点输出<meta {...{ [attr]: parentKey, content: parentValue }} />。测试用例 Metadata.test.tsx 验证了"字符串与对象混合数组"的输出顺序与内容。
4.4 特殊辅助一:OpenGraph 自动补全
只要定义了任意ogprop,<Metadata>就会自动把title、description拷贝为og:title、og:description:
<Metadata title="My Website" og /> // generates <meta name="title" content="My Website" /> <meta property="og:title" content="My Website" />想关闭自动补全,显式把对应键设为null即可:
<Metadata title="My Website" og={{ title: null }}/> // generates <meta name="title" content="My Website" />同样,如果完全不需要任何自动生成的 og 标签,就别传ogprop。
此外,只要定义了ogprop,框架还会自动生成og:type并默认为website:
<Metadata og /> // generates <meta property="og:type" content="website" />可以通过直接设置覆盖默认类型(比如音乐专辑页):
<Metadata og={{ type: 'music:album' }}/> // generates <meta property="og:type" content="music:album" />4.5 特殊辅助二:title与charSet
定义titleprop 时,输出会自动前置一个<title>标签(同时仍然生成name="title"的 meta):
<Metadata title="My Website" /> // generates <title>My Website</title> <meta name="title" content="My Website" />定义charSetprop 时,会生成带charset属性的特殊 meta(源码中charSet被列入EXCLUDE_PROPS,不会走普通键值对路径,见 Metadata.tsx):
<Metadata charSet="utf-8" /> // generates <meta charset="utf-8" />前面有些示例为简洁省略了自动生成的<title>与og:type,实际同时传入title和og时的完整输出是:
<Metadata title="My Website" og /> // generates <title>My Website</title> <meta name="title" content="My Website" /> <meta property="og:title" content="My Website" /> <meta property="og:type" content="website" />测试 Metadata.test.tsx 中的 "typical collection" 用例完整断言了title、description、charSet、locale、og与twitter组合时的输出顺序,可作为排查生成结果差异的参考。
4.6 全站通用标签放哪里?
像charset、locale这类全站一致的标签,不必在每个页面/Cell 里重复设置——直接写进web/index.html一次即可,页面级组件只关心页面特有的标签。
4.7 一个接近真实的完整用法
<Metadata title="My Website" description="An amazing website created with RedwoodJS" robots="noindex,nofollow" og={{ image: "https://example.com/images/og-image.png" }} twitter={{ card: 'summary', site: '@mysite', creator: '@redwoodjs' }} />4.8 关于<MetaTags>的弃用
在 Redwood 6.6.0 之前,这个组件叫<MetaTags>,且内置了一批硬编码的特殊 props(如ogContentUrl、ogWidth、ogHeight、locale等,见 packages/web/src/components/MetaTags.tsx),其中ogContentUrl并不完全符合 OpenGraph 规范(OpenGraph 要求使用og:image等标准属性)。出于兼容性,框架仍会继续渲染<MetaTags>,但它已被标记为弃用(源码中标注了@deprecated Please use <Metadata> instead,见 MetaTags.tsx)。已有应用应迁移到<Metadata>:把ogContentUrl换成og={{ image: ... }},其余如title、description、author、robots、locale等用法基本一一对应。
五、流式 SSR 下的头部注入(源码补充)
默认 CSR 场景下,<Head>/<Metadata>通过react-helmet-async管理文档头。但在流式 SSR(streaming SSR)下,Helmet 并不参与服务端渲染,Redwood 会切换到PortalHead机制(见 Metadata.tsx 与 MetaTags.tsx 中RWJS_ENV.RWJS_EXP_STREAMING_SSR的判断)。
PortalHead.tsx 的实现思路是:服务端渲染时通过useServerInsertedHTML把子元素标记上data-rwjs-head属性并注入流式 HTML,随后由流转换的收尾阶段把这些标记块移动到<head>中;客户端则通过 React Portal 渲染。这正是"SEO 标签在最终 HTML 中可被爬虫读取"的关键实现细节。
六、爬虫、预渲染与动态标签
6.1 爬虫能读到这些标签的前提
需要特别提醒:要让 Twitter、Facebook 等爬虫/抓取器看到你设置的标题和 meta 标签,页面必须被预渲染。如果页面内容是静态的,可以直接使用 Redwood 内置的 Prerender 功能;如果标签是动态的,见下文的"动态标签"一节。
6.2 在 Cell 中根据数据动态设置标签
很多时候 meta 标签依赖数据——比如博客文章页希望<title>就是文章标题。Redwood 支持在渲染期间预渲染 Cell(自 v3.x 起,详见 prerender.md 的 Cell prerendering 一节),因此你可以直接在 Cell 的Success组件里使用<Metadata>,让标签跟随查询结果变化。
import { Metadata } from '@redwoodjs/web' import Post from 'src/components/Post/Post' export const QUERY = gql` query FindPostById($id: Int!) { post: post(id: $id) { title snippet author { name } } } ` export const Loading = /* ... */ export const Empty = /* ... */ export const Success = ({ post }) => { return ( <> <Metadata title={post.title} author={post.author.name} description={post.snippet} /> <Post post={post} /> </> ) }当Success组件渲染时,它会立即更新页面的<title>并设置相应的<meta>标签。结合预渲染,这些由 GraphQL 数据生成的标签会出现在交付给爬虫的静态 HTML 中,从而同时满足"动态内容"与"可被抓取"两个诉求。
七、总结:Redwood 的 SEO 工作流
将本文内容串起来,Redwood 中完成一套完整 SEO 标签的路径是:
- 全局层:在
redwood.toml的[web]段设置应用title,并在web/src/App.(tsx|jsx)的RedwoodProvider上配置titleTemplate,统一所有页面的标题格式; - 页面层:在具体页面/布局中用
<Head>写任意<head>标签,或直接用<Metadata>以声明式 props 生成title、description、robots、OpenGraph、Twitter 卡片等标签,利用"组件树越靠下优先级越高"的规则实现共享与覆盖; - 数据层:在 Cell 的
Success组件中使用<Metadata>,把查询结果映射为动态标签; - 交付层:通过 Redwood 的 Prerender(或流式 SSR 的
PortalHead机制)确保最终 HTML 中包含这些标签,让爬虫与社交平台能够读取。
从 packages/web/src/components/Metadata.tsx 到 Metadata.test.tsx,这套组件的递归转换逻辑、特殊辅助行为与边界情况都有源码与测试可查,遇到生成结果与预期不符时,直接对照测试用例即可快速定位。
- 后端
- 前端
- Web框架
- 开发工具
【免费下载链接】redwood
RedwoodGraphQL
相关推荐
RedwoodJS SEO 与 Meta 标签完全指南:从 App Title 到 Open Graph 动态标签
RedwoodJS SEO 与 Meta 标签完全指南:从 App Title 到 Open Graph 动态标签 本篇技术指南围绕 RedwoodJS 框架内
后端前端Web框架开发工具RedwoodJS 中的 SEO 与 Meta 标签完整指南:从 App 标题到动态 OG 标签
RedwoodJS 中的 SEO 与 Meta 标签完整指南:从 App 标题到动态 OG 标签 这篇指南以 RedwoodJS 的 SEO 与 <meta 标
后端前端Web框架开发工具RedwoodJS SEO 与 Meta 标签实战指南:从应用标题到 Open Graph 与动态标签
RedwoodJS SEO 与 Meta 标签实战指南:从应用标题到 Open Graph 与动态标签 本篇指南围绕 RedwoodJS 的 seo head
后端前端Web框架开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考