- 前端
- 静态站点
- Web框架
【免费下载链接】gatsby
React-based framework with performance, scalability, and security built in.
examples/sitemap是 Gatsby 仓库中演示站点地图(sitemap)生成插件用法的完整示例站点,而examples/sitemap/posts/2017-05-22-second-post.md正是该示例站中用于演示的两篇占位博文之一。本文以这篇博文为线索,完整追踪一条从 Markdown 文件、frontmatter 字段、GraphQL 节点、页面创建到最终被gatsby-plugin-sitemap收录进sitemap-index.xml的构建期链路;读完本文,你将掌握 Gatsby 示例站点中博文类内容的处理流程、draft 字段对页面与站点地图的实际影响,以及 sitemap 插件全部配置项背后的源码级工作原理。
一、关联文档的定位:sitemap 示例站中的第二篇博文
该文件位于 examples/sitemap/posts/2017-05-22-second-post.md,与同目录下的 2017-03-09-first-post.md 一起构成示例站的博客内容源。示例站的整体说明见 examples/sitemap/README.md:
Gatsby example site explaining how to use the sitemap generator plugin.
也就是说,这个示例站的全部目的,就是演示gatsby-plugin-sitemap插件如何把站内页面生成站点地图。而这篇博文的具体角色,是作为"被收录进站点地图的内容页"之一——它与普通页面(如/、/page-2/)一同进入allSitePage,最终出现在生成的 XML 文件中。
需要如实说明的是,文档正文是经典的 Lorem ipsum 占位文本(约 7 段、52 行),并不承载真实业务内容;它真正的技术含量集中在文件头部的 YAML frontmatter,以及该文件在示例管线中被读取、过滤、创建页面、进入站点地图的整个流程。后文将逐一拆解。
二、博文 frontmatter:title / date / draft 三个字段的实际去向
文件头部的 frontmatter 是全文的技术起点:
--- title: First Post date: "2017-03-09" draft: false ---三个字段在本示例中的去向各不相同,需要分别核实:
- title:被博客模板直接消费。在 template-blog-post.js 中,页面组件通过 GraphQL 查询
markdownRemark.frontmatter.title,并用<h1>{frontmatter.title}</h1>渲染成页面标题。 - date:在本示例中并未被
gatsby-node.js或博客模板引用,仅由gatsby-transformer-remark解析进frontmatter供 GraphQL 查询。从源码结构看,这个字段更多是作为博客元数据的常规写法保留,示例并未使用它生成 slug 或 lastmod。 - draft:这是影响面最大的字段。它不直接作用于站点地图插件,而是通过控制"是否创建页面"间接决定博文能否进入站点地图(详见第四节)。
另外值得注意的一个示例细节:这篇名为2017-05-22-second-post.md的文件,frontmatter 中的title写的是First Post,date与第一篇也完全相同("2017-03-09")。这是示例数据中常见的复制粘贴占位写法,并不影响演示效果——因为该示例的页面路径(slug)完全由文件名决定,与 frontmatter 无关。
三、数据管线第一站:从 posts 目录到 MarkdownRemark 节点
要让 Markdown 文件变成可查询的数据,示例在 gatsby-config.js 中挂载了两个插件:
{ resolve: `gatsby-source-filesystem`, options: { name: `posts`, path: `${__dirname}/posts`, }, }, `gatsby-transformer-remark`,gatsby-source-filesystem以name: "posts"为数据源命名,把posts/目录下(含两篇博文)的所有文件注册为File节点;gatsby-transformer-remark再把 Markdown 文件转换为MarkdownRemark节点,frontmatter 被解析为frontmatter字段,正文被解析为html/excerpt等字段。
经此一步,本文档(2017-05-22-second-post.md)便成为 GraphQL 数据层中一个可查询的MarkdownRemark节点,为下一阶段的页面创建做准备。依赖声明可见 examples/sitemap/package.json,其中gatsby、gatsby-plugin-sitemap、gatsby-source-filesystem、gatsby-transformer-remark均使用next版本。
四、页面创建:draft 过滤、文件名 slug 与博客模板
页面创建逻辑位于 gatsby-node.js,这是"博文是否进入站点地图"的第一道闸门。
4.1 draft 过滤决定页面是否创建
createPagesAPI 中执行的查询如下:
allMarkdownRemark( limit: 1000 filter: { frontmatter: { draft: { ne: true } } } ) { edges { node { fields { slug } } } }查询条件frontmatter.draft ne: true的含义是"draft 不等于 true"。因此:
draft: false(本文档的状态)→ 节点被查询到 → 创建博客页面 → 页面进入allSitePage→ 最终出现在站点地图中;draft: true→ 节点被过滤 → 不创建页面 → 该博文不会出现在站点地图中。
这是"用 draft 控制发布状态"的常见实践:未发布的草稿连页面都不存在,自然无需在站点地图中处理。limit: 1000则是查询上限,避免一次性取回过多节点。
4.2 slug 完全由文件名决定
onCreateNode钩子对每个MarkdownRemark节点生成slug字段:
nodeSlug = ensureSlashes( path.basename(fileNode.relativePath, path.extname(fileNode.relativePath)) )即取文件名去扩展名后,由ensureSlashes保证首尾各有一个/。于是本文档的 slug 为:
2017-05-22-second-post → /2017-05-22-second-post/随后createPage使用该 slug 作为页面路径,并以src/templates/template-blog-post.js为模板组件、把{ slug }放进页面 context:
createPage({ path: edge.node.fields.slug, component: slash(blogPostTemplate), context: { slug: edge.node.fields.slug }, })模板再通过markdownRemark(fields: { slug: { eq: $slug } })查询并渲染html与frontmatter.title。至此,一篇博文正式成为站点的一个独立页面,其路径/2017-05-22-second-post/也进入了allSitePage。
五、sitemap 收录:gatsby-plugin-sitemap 的构建期管线
5.1 配置入口:siteUrl 与 exclude
示例在 gatsby-config.js 中这样接入插件:
siteMetadata: { title: `GatsbyJS RSS`, description: `A blog with RSS powered by GatsbyJS.`, siteUrl: `https://www.gatsbyjs.com`, }, // ... { resolve: `gatsby-plugin-sitemap`, options: { exclude: [`/secret`], }, },siteMetadata.siteUrl是构造绝对 URL 的基准地址。示例首页 index.js 对此有明确提示:插件用siteMetadata.siteUrl把相对路径拼成完整域名地址。如果未配置siteUrl,插件默认的resolveSiteUrl会直接抛错(见 internals.js 中的错误提示)。exclude: ["/secret"]把/secret页面排除在站点地图之外。该页面的组件 secret.js 中写着 "This page should be excluded from sitemap.xml",与配置一一对应;与之相对,page-2.js 中写着 "This page should be included in sitemap.xml"。
5.2 生产模式才生成:onPostBuild 中的默认查询与解析函数
插件在onPostBuild阶段执行(对应实现见 packages/gatsby-plugin-sitemap/src/gatsby-node.js),因此只有生产构建才会产出站点地图。插件文档明确提示:"To test your sitemap, run:gatsby build && gatsby serve"。
默认查询定义在 options-validation.js 中:
{ site { siteMetadata { siteUrl } } allSitePage { nodes { path } } }查询结果随后依次流经四个默认解析函数(均定义在 internals.js):
- resolveSiteUrl:从
data.site.siteMetadata.siteUrl取出站点域名,缺失则抛错; - resolvePages:从
data.allSitePage.nodes取出页面数组,缺失则抛错; - filterPages(默认 defaultFilterPages):按
excludes数组过滤页面,匹配使用minimatch的 glob 规则,并在比较前用withoutTrailingSlash去掉末尾斜杠; - serialize:把每个页面对象转成站点地图条目,默认输出
{ url, changefreq: 'daily', priority: 0.7 }。
onPostBuild中任何一个环节出错(查询错误、解析失败、resolvePages未返回数组、序列化异常),都会通过reporter.panic中止构建并输出[gatsby-plugin-sitemap]:前缀的错误信息。
5.3 过滤规则:自定义 exclude 与始终排除的页面
过滤由pageFilter函数完成:先对每个页面套用内置默认排除列表,再套用用户的excludes配置。默认排除列表(不可通过filterPages关闭)为:
/dev-404-page /404 /404.html /offline-plugin-app-shell-fallback也就是说,即使你没有配置任何exclude,开发用的 404 页面、离线回退页面也绝不会出现在站点地图中。
5.4 写出产物:sitemap-index.xml 与分片规则
过滤与序列化完成后,onPostBuild调用simpleSitemapAndIndex把结果写入public目录:
- 固定产出
sitemap-index.xml(站点地图索引文件),每达到entryLimit(默认45000)条 URL 就额外生成一个sitemap-X.xml分片;本示例页面数量远低于 45000,因此只会生成sitemap-0.xml与sitemap-index.xml; - 每条 URL 会通过
prefixPath与siteUrl拼接为绝对地址; - 插件还通过 SSR 钩子(见 gatsby-ssr.js)在页面
<head>中注入<link rel="sitemap" type="application/xml" href="/sitemap-index.xml">(可用createLinkInHead: false关闭),便于搜索引擎自动发现。
六、这篇博文最终去哪:站点地图中的 URL 清单
综合上述链路,将本示例各页面在最终站点地图中的去留整理如下(依据:示例配置 + 插件默认过滤逻辑 + 各页面源码):
| 页面路径 | 来源 | 是否进入站点地图 | 原因 |
|---|---|---|---|
/ | index.js | 是 | 普通首页 |
/page-2/ | page-2.js | 是 | 普通页面 |
/secret/ | secret.js | 否 | 被exclude: ["/secret"]排除 |
/2017-03-09-first-post/ | 第一篇博文生成的页面 | 是 | draft: false,创建页面 |
/2017-05-22-second-post/ | 本文档生成的页面 | 是 | draft: false,创建页面 |
/dev-404-page/、/404/、/404.html/等 | Gatsby 自动生成 | 否 | 插件内置默认排除 |
可见,本文档2017-05-22-second-post.md的最终归宿,是作为绝对地址https://www.gatsbyjs.com/2017-05-22-second-post/被写入sitemap-0.xml(拼接规则由siteUrl+prefixPath决定)。这也是它作为示例素材的核心演示价值:只要draft: false且路径不在排除列表中,一篇 Markdown 博文就会自动进入站点地图,全程无需人工维护 URL 清单。
七、从 0 到 1 复现:安装、开发与生产构建
示例的脚本定义在 examples/sitemap/package.json:
"scripts": { "develop": "gatsby develop", "build": "gatsby build", "start": "npm run develop" }在示例目录下执行:
npm install npm run develop # 开发模式,仅用于预览页面,不会生成站点地图 npm run build # 生产构建,生成 public/ 下的站点地图 npx gatsby serve # 本地预览构建产物注意插件文档的明确提示:站点地图只在生产模式(gatsby build)下生成,开发模式下不会出现。构建后可在gatsby serve提供的本地服务中访问/sitemap-index.xml查看索引文件,进而打开分片sitemap-0.xml检查本文档对应的 URL 是否被正确收录。
八、进阶自定义与源码级验证依据
8.1 插件选项速查表
以下默认值均来自插件源码 options-validation.js(Joi 校验),可直接在gatsby-config.js中覆盖:
| 选项 | 类型与默认值 | 作用 |
|---|---|---|
output | string,默认/ | 站点地图在public中的存放目录 |
createLinkInHead | boolean,默认true | 是否在页面<head>注入指向sitemap-index.xml的<link> |
entryLimit | number,默认45000 | 每个分片的最大 URL 数,超出则生成多个sitemap-X.xml |
query | GraphQL 字符串 | 取站点 URL 与页面列表的查询;默认取site.siteMetadata.siteUrl与allSitePage.nodes.path |
excludes | 数组,默认[] | 要排除的路径,支持 minimatch glob 匹配 |
resolveSiteUrl | 函数 | 从查询结果返回站点 URL,可同步或异步 |
resolvePagePath | 函数 | 从页面对象返回不带域名的 URI |
resolvePages | 函数 | 从查询结果返回页面对象数组 |
filterPages | 函数 | 决定页面是否排除,返回true排除、false保留 |
serialize | 函数 | 把页面对象转成站点地图条目(url、lastmod等) |
8.2 何时必须自定义 query / resolvePages / resolveSiteUrl
从options-validation.js的注释与internals.js的抛错逻辑可以明确三点触发条件:
- 站点 URL 不来自
site.siteMetadata.siteUrl时,必须提供自定义resolveSiteUrl; - 覆盖
query后若页面 URI 不在path字段上,必须提供自定义resolvePagePath; - 查询结构不是
allSitePage.nodes时(例如从 WordPress、Contentful 等 CMS 取内容),必须自定义resolvePages,把多数据源合并进页面数组。
excludes数组除字符串外也可放入其他数据类型,但此时必须同步自定义filterPages(默认实现对非字符串元素会抛错)。
8.3 lastmod:搜索引擎真正读取的字段
插件默认的serialize只为每条 URL 输出changefreq: "daily"和priority: 0.7——对所有页面一视同仁,没有区分重要性与更新频率。插件文档引用了 Google 官方建议:Google 忽略<priority>与<changefreq>,但会读取<lastmod>,且错误标注会让 Google 停止读取该字段。因此实践上应自定义serialize,为每条 URL 提供准确的lastmod(如来自 CMS 的modifiedGmt字段),这正是插件文档"Recommended usage"一节给出的示例做法。
8.4 测试用例如何锁定这些行为
插件行为并非仅靠文档说明,而是有单元测试背书,见 packages/gatsby-plugin-sitemap/src/tests/internals.js:
withoutTrailingSlash:验证/保持不变、/test/path/去掉末尾斜杠;prefixPath:验证{ url: "/test/path/", siteUrl: "https://example.net", pathPrefix: "/root" }拼接为https://example.net/root/test/path/;serialize:验证默认输出恰为{ url, changefreq: "daily", priority: 0.7 };pageFilter:验证/404.html被默认规则剔除、自定义excludes生效,且连续多次运行结果稳定。
这些测试与本文所述"本示例中/secret被排除、本文档博文被收录"的最终结果出自同一套代码路径,可作为你在自定义配置时验证预期行为的参照。
综上,examples/sitemap/posts/2017-05-22-second-post.md虽然只是一篇占位博文,但它串起了 Gatsby 内容类站点从 Markdown 到站点地图的完整闭环:frontmatter 的draft字段控制页面是否创建,文件名决定页面路径,而gatsby-plugin-sitemap在构建期以siteMetadata.siteUrl为基准、以exclude与内置排除列表为规则,把最终页面清单序列化成分片化的sitemap-index.xml。理解了这条链路,你就可以在自己的 Gatsby 博客中精确控制每一篇博文的搜索引擎可见性。
- 前端
- 静态站点
- Web框架
【免费下载链接】gatsby
React-based framework with performance, scalability, and security built in.
相关推荐
6 步搭出你的 GPUI 应用:Zed 的 Rust UI 框架如何管状态、开窗口、绑快捷键
6 步搭出你的 GPUI 应用:Zed 的 Rust UI 框架如何管状态、开窗口、绑快捷键 GPUI 是 Zed 编辑器自研的 UI 框架:GPU 加速、混合
开发工具代码编辑器桌面应用使用 gatsby-plugin-sitemap 为 Gatsby 站点自动生成 sitemap.xml
使用 gatsby plugin sitemap 为 Gatsby 站点自动生成 sitemap.xml 导读 sitemap.xml 是搜索引擎了解站点页面结
Vitepress 站点地图(Sitemap)生成指南
Vitepress 站点地图 Sitemap 生成指南 什么是站点地图 Sitemap 站点地图 Sitemap 是一个XML文件,它列出了网站中所有可供搜索引
前端文档
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考