首页
/ Create React App 中修改 Title 标签与生成动态 Meta 标签的完整指南

Create React App 中修改 Title 标签与生成动态 Meta 标签的完整指南

2026-09-04 18:05:37作者:廉皓灿Ida

在基于 Create React App(CRA)构建的 React 应用中,页面的 <title><meta> 标签直接影响浏览器标签页显示、SEO 收录与社交分享卡片。本篇指南基于 CRA 官方文档,系统讲解如何修改静态标题、在客户端动态更新标题、以及在不支持服务端渲染(SSR)的前提下,通过"占位符 + 服务端替换"的方案为每个 URL 生成动态 <meta> 标签并向页面注入服务端数据,同时结合 react-scripts 的 webpack 配置与 react-dev-utils 源码,深入剖析 index.html 在构建流程中是如何被插值、压缩和注入脚本的。

默认模板中的 title 与 meta 标签

CRA 生成的项目通过 public/index.html 作为 HTML 模板。查看仓库中的官方模板 packages/cra-template/template/public/index.html,其 <head> 部分包含了如下默认设置:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

其中与标题和元数据直接相关的有三个标签:

  • <title>React App</title>:浏览器标签页显示的页面标题,默认值为 "React App";
  • <meta name="description" content="Web site created using create-react-app" />:页面描述,搜索引擎抓取的核心字段之一;
  • <meta name="theme-color" content="#000000" /><link rel="manifest" ... />:PWA 相关元数据,manifest 的具体内容见 packages/cra-template/template/public/manifest.json

TypeScript 模板 packages/cra-template-typescript/template/public/index.html 的 HTML 结构与之完全一致。

修改静态的 title 标签

最简单的做法是找到生成项目 public 目录下的源 HTML 文件,把 <title> 标签中的 "React App" 改成任意字符串。例如:

<title>My Product</title>

官方文档特别指出:平时很少需要直接编辑 public 目录中的文件。以添加样式表为例,添加样式表 完全可以不碰 HTML 文件来完成。index.html 在 CRA 中主要承担的角色是:放置 web 字体、meta 标签、分析脚本等全局静态内容,构建步骤会自动把打包后的 <script> 注入到 <body> 中(模板中的注释也明确说明了这一点)。

在客户端动态更新页面标题

如果需要根据应用内容动态更新页面标题(例如路由切换时),有两条路径:

直接使用 document.title API

对于简单的场景,可以直接使用浏览器原生 API:

document.title = '用户中心 - 我的产品';

这是标准 DOM API,无需任何第三方依赖,适合在事件回调或 useEffect 中同步标题。

使用 React Helmet 管理复杂场景

当需要在多个 React 组件之间"声明式"地设置和覆盖 <title> 及其他 <head> 内容(如组件卸载后恢复标题)时,文档推荐引入第三方库 React Helmet。它解决的典型问题是:嵌套组件各自声明标题时,哪个组件离渲染终点更近,哪个的声明就生效,从而避免手动保存/恢复 document.title 的繁琐。

在服务端生成动态 标签

Create React App 不支持服务端渲染(SSR),因此很多人会困惑:如何让 <meta> 标签随当前 URL 动态变化?官方文档给出的推荐方案是 HTML 占位符 + 服务端响应前替换

第一步:在 HTML 中埋入占位符

public/index.html<head> 中加入带双下划线前缀的占位符:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta property="og:title" content="__OG_TITLE__" />
    <meta property="og:description" content="__OG_DESCRIPTION__" />
  </head>
</html>

这些 og:* 标签是社交网络分享卡片(Open Graph 协议)读取的字段。占位符命名建议采用 __UPPER_SNAKE__ 风格,与 HTML 中的真实属性值区分开来,避免误替换。

第二步:服务端读取 index.html 并按 URL 替换

无论后端使用什么技术栈,逻辑都相同:

  1. 读取 build/index.html 到内存;
  2. 根据当前请求 URL 路由,计算出该页面的真实标题与描述;
  3. __OG_TITLE____OG_DESCRIPTION__ 及其他占位符替换为对应值;
  4. 务必对插值内容做 sanitize 与 HTML 转义,再嵌入 HTML,防止属性值注入。

文档还补充了一个工程细节:如果你使用 Node 服务端,可以让客户端和服务端共享同一套路由匹配逻辑,避免两端路由规则不一致导致占位符与 URL 对不上;在简单场景下,两端各自维护一份路由复制也完全可行。

备选方案:预渲染静态 HTML

如果你没有自己的服务端,也可以选择把每个页面预先构建成一个静态 HTML 文件,再加载 JavaScript bundle。这一方案在 预渲染为静态 HTML 文件 一文中有专门介绍,其核心收益是:无论 JS bundle 是否下载成功,用户都能拿到每个页面的核心内容,同时提高了各路由被搜索引擎收录的概率。

从服务端向页面注入数据

与上一节的占位符思路一致,你还可以在 HTML 中留下用于注入全局变量的占位符:

<!doctype html>
<html lang="en">
  <head>
    <script>
      window.SERVER_DATA = __SERVER_DATA__;
    </script>
  </head>
</html>

服务端在发送响应前,把 __SERVER_DATA__ 替换为真实的 JSON 数据,客户端代码随后可直接读取 window.SERVER_DATA 使用。

安全红线:文档以加粗强调了这一点——发送 JSON 给客户端前必须先做 sanitize。直接把未净化的 JSON 内联进 HTML 是 React 应用中最常见的 XSS 漏洞之一,尤其是当数据中包含 </script> 这样的字符串时会直接破坏脚本边界。替换时应使用 JSON 序列化后再转义 </script> 等敏感序列(例如替换为 <\/script>)。

源码级剖析:index.html 在构建流程中经历了什么

理解 CRA 如何最终产出 build/index.html,有助于把上面的占位符方案落在实处。关键代码在 packages/react-scripts/config/webpack.config.js

plugins: [
  // Generates an `index.html` file with the <script> injected.
  new HtmlWebpackPlugin(
    Object.assign(
      {},
      {
        inject: true,
        template: paths.appHtml,   // 指向 public/index.html
      },
      isEnvProduction
        ? {
            minify: {
              removeComments: true,
              collapseWhitespace: true,
              removeRedundantAttributes: true,
              useShortDoctype: true,
              removeEmptyAttributes: true,
              removeStyleLinkTypeAttributes: true,
              keepClosingSlash: true,
              minifyJS: true,
              minifyCSS: true,
              minifyURLs: true,
            },
          }
        : undefined
    )
  ),
  // Inlines the webpack runtime script. This script is too small to warrant a network request.
  isEnvProduction &&
    shouldInlineRuntimeChunk &&
    new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
  // Makes some environment variables available in index.html.
  new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
  ...
],

从这段配置可以确认三个事实:

  1. 模板来源HtmlWebpackPlugintemplate 取自 paths.appHtml,在 packages/react-scripts/config/paths.js 中定义为 resolveApp('public/index.html')。也就是说,你在 public/index.html 里写的任何 <title><meta>、占位符都会原样保留进构建产物。
  2. 生产构建会自动压缩 HTML:生产模式下 minify 配置会移除注释、折叠空白并压缩内联的 JS/CSS。这意味着模板中 <body> 内大段教学注释(如 "This HTML file is a template...")在 build/index.html 中会被清除,但占位符本身作为属性值或文本内容会被保留——这对服务端替换方案是友好的。
  3. %PUBLIC_URL% 的插值机制:模板中 %PUBLIC_URL%/favicon.ico 这类占位符并非 HtmlWebpackPlugin 的原生能力,而是由 packages/react-dev-utils/InterpolateHtmlPlugin.js 实现的。其核心逻辑很直白:
class InterpolateHtmlPlugin {
  apply(compiler) {
    compiler.hooks.compilation.tap('InterpolateHtmlPlugin', compilation => {
      this.htmlWebpackPlugin
        .getHooks(compilation)
        .afterTemplateExecution.tap('InterpolateHtmlPlugin', data => {
          Object.keys(this.replacements).forEach(key => {
            const value = this.replacements[key];
            data.html = data.html.replace(
              new RegExp('%' + escapeStringRegexp(key) + '%', 'g'),
              value
            );
          });
        });
    });
  }
}

它挂在 afterTemplateExecution 钩子上,对所有形如 %KEY% 的文本做全局正则替换。传入的 env.raw 来自 packages/react-scripts/config/env.js,其中 PUBLIC_URL 的默认值为空字符串,除非你在 package.json 中配置了 homepage,配置后它会成为该 URL 的 pathname。这正是模板注释中"使用 %PUBLIC_URL% 可以同时兼容客户端路由与非根路径部署"的底层原因——从源码结构看,PUBLIC_URLWDS_SOCKET_*FAST_REFRESH 等一起通过 InterpolateHtmlPlugin(HTML 侧)和 webpack.DefinePlugin(JS 侧)两条通道分别注入。

对占位符方案的实现启示

由于 InterpolateHtmlPlugin 只对 %大写KEY% 格式生效,而本文推荐的 __OG_TITLE__ 等占位符采用双下划线格式,二者不会相互干扰:CRA 构建时不会触碰 __OG_TITLE__,它会原样出现在 build/index.html 中,等待你的服务端在响应前做最终替换。这构成了完整的静态站点 + 动态头部的工作流:

  1. npm run build 产出含占位符的 build/index.html
  2. 任意 Web 服务器(Node、Nginx 动态模块等)按请求 URL 路由;
  3. 服务端内存中替换占位符(标题、描述、window.SERVER_DATA);
  4. 对替换值做转义后输出 HTML,浏览器加载 JS bundle 完成 hydrate 之前的首屏展示。

小结与实操建议

  • 改标题:直接编辑 public/index.html<title>;动态标题用 document.title,多组件竞争场景用 React Helmet。
  • 动态 <meta>:CRA 无 SSR,采用"HTML 占位符(__OG_TITLE__ 风格)+ 服务端响应前替换"方案;纯静态托管则考虑预渲染为静态 HTML 文件。
  • 服务端注入数据:window.SERVER_DATA = __SERVER_DATA__; 占位符替换,替换前必须做 JSON 净化以防御 XSS。
  • 构建链路:HtmlWebpackPlugin(模板 + 生产 minify)→ InlineChunkHtmlPlugin(内联 runtime)→ InterpolateHtmlPlugin%PUBLIC_URL% 插值),占位符方案与这条链路天然兼容。

以上方案均以当前仓库 react-scripts 的 webpack 配置为准,适用前提是使用未 eject 的 CRA 模板项目;若已 eject,可自行在 config/webpack.config.js 中验证同样的插件组合。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
903
1.82 K
docsdocs
暂无描述
Markdown
888
5.78 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.51 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.17 K
341