news 2026/6/22 3:00:56

Gatsby分页实战:构建时静态分页原理与pageContext避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Gatsby分页实战:构建时静态分页原理与pageContext避坑指南

1. 项目概述:为什么在 Gatsby 里做分页不是“加个组件”那么简单

你刚用 Gatsby 搭好一个博客,写了二十篇技术笔记,首页一刷全堆出来——页面加载慢、首屏白屏时间长、用户划到底都找不到“下一页”按钮。这时候你搜“Gatsby 分页”,第一条就是gatsby-awesome-pagination,文档写着“一行代码搞定”,你兴冲冲 npm install,照着示例贴了三行代码,结果 build 报错:pageContext is not defined;再查,发现createPages里没传上下文;补上之后,点击第二页又 404;最后翻到 GitHub issues,满屏都是“pageContext丢失”“path不匹配”“gatsby-node.js配置后首页不渲染”的抱怨。这不是你代码写错了,而是 Gatsby 的分页根本就不是传统框架里“前端切页”的逻辑——它是在构建时(build time)把每一页都预生成成独立的 HTML 文件,而gatsby-awesome-pagination只是帮你自动化这个“批量创建静态页”的过程。它不处理数据来源,不决定分页粒度,不校验路径规则,更不会替你把当前页码塞进 GraphQL 查询变量里。换句话说,它是个精密但冷酷的“流水线调度器”,你得先搭好原料车间(数据层)、设定好模具规格(分页参数)、校准好传送带位置(路径与上下文),它才肯开工。我去年重构三个 Gatsby 站点的分页系统,踩过所有典型坑:从createPagescontext对象漏传导致所有分页页 404,到pageContext在模板中被意外覆盖引发无限重定向,再到gatsby-awesome-pagination默认的path拼接规则和中文路径冲突导致 SEO 友好性归零。这篇文章不讲 API 列表,只讲你真正上线前必须亲手验证的七道关卡:数据怎么切、页码怎么传、路径怎么定、上下文怎么保、模板怎么接、SEO 怎么稳、错误怎么查。如果你正卡在createPages报错、分页链接跳转 404 或者第二页内容和第一页一模一样,那你不是缺教程,是缺一份按生产环境逐行调试过的操作手册。

2. 核心设计逻辑:理解 Gatsby 分页的本质与 gatsby-awesome-pagination 的真实角色

2.1 Gatsby 分页不是“前端交互”,而是“构建时静态页批量生成”

很多刚从 Next.js 或 VuePress 转来的开发者,第一反应是“加个分页组件,点击触发setState切数据”。这在 Gatsby 里完全行不通。Gatsby 的核心哲学是Build-time Static Generation—— 所有页面必须在gatsby build这一步就生成完毕,部署后服务器只返回纯 HTML,没有运行时数据请求。这意味着:

  • 不存在“点击下一页 → 发起 GraphQL 请求 → 渲染新内容”这种流程;
  • 所有分页页(如/blog/page/2//blog/page/3/)都必须在构建阶段,由gatsby-node.js中的createPagesAPI 显式创建为独立页面节点;
  • 每个分页页的pageContext必须包含该页所需的所有参数(如currentPagelimitskiptotalPages),这些参数会注入到对应模板组件的props.pageContext中;
  • 模板组件(如blog-list.js)在构建时就被编译,它接收pageContext后,通过useStaticQuerygraphql查询语句(注意:是编译期查询,非运行时)获取本页应显示的数据子集。

提示:你可以把 Gatsby 分页想象成印刷厂印书——gatsby-awesome-pagination是自动装订机,但它不负责写文章(数据源)、不决定每章多少页(分页逻辑)、不设计封面(模板),它只按你给的“章节清单”(分页配置)和“纸张规格”(路径规则),把已写好的内容(GraphQL 查询结果)批量裁切成指定页数并装订成册(生成/page/2/index.html)。你漏掉任何一道前置工序,装订机就会卡死或装错。

2.2 gatsby-awesome-pagination 的真实定位:一个高阶 createPages 封装工具

gatsby-awesome-pagination的源码只有不到 200 行,它的核心价值不是提供炫酷 UI,而是解决createPages中重复性最高的三类问题:

  1. 分页计算自动化:根据总条目数(totalCount)和每页条数(limit),自动算出totalPagescurrentPageskip值,避免手写Math.ceil(totalCount / limit)出错;
  2. 路径生成标准化:统一处理/page/:num//blog/:num/等常见路径格式,支持自定义pathPrefixpathSuffix,防止手拼字符串导致路径不一致;
  3. 上下文注入规范化:确保每个分页页的context对象结构统一,包含currentPagelimitskiptotalPagespreviousPagePathnextPagePath等关键字段,且类型安全(如currentPage强制为数字而非字符串)。

但它绝不做以下事

  • ❌ 不读取或操作你的 GraphQL 数据源(allMarkdownRemarkallMdx等);
  • ❌ 不修改你的 GraphQL 查询语句(你仍需在模板中手动写skiplimit参数);
  • ❌ 不处理pageContext在模板中的使用逻辑(你仍需在blog-list.js中正确解构pageContext并传入查询);
  • ❌ 不兼容 Gatsby v5 的createPagesEphemeral新 API(截至 2024 年中,它仍基于createPages)。

我实测过直接手写createPages分页逻辑:12 行代码搞定基础分页,但加上边界处理(首页不显示previousPagePath、末页不显示nextPagePath)、路径容错(/page/01//page/1/统一)、SEO 字段注入(canonicalURL),代码膨胀到 47 行且极易出错。gatsby-awesome-pagination把这部分稳定逻辑封装起来,让你专注在业务层——比如“如何让第一页显示 10 篇,后续页显示 8 篇”这种真实需求。

2.3 为什么 pageContext 是整个链条的“命门”?它的生命周期与常见陷阱

pageContext是 Gatsby 分页中唯一贯穿构建全流程的“数据信使”,它的完整生命周期如下:

阶段操作者关键动作常见错误
1. 构建准备你在gatsby-node.js调用createPage({ path, component, context: { currentPage: 1, limit: 10, skip: 0 } })漏传context对象,或context里缺少currentPage字段 → 后续模板无法获取页码
2. 页面创建Gatsby 内核context序列化并注入到该页面的page-data.jsoncontext包含函数或未序列化对象(如Date实例)→ build 失败
3. 模板渲染你的blog-list.js组件通过props.pageContext.currentPage读取值,并用于 GraphQL 查询变量useStaticQuery中误用pageContextuseStaticQuery无参数,不能动态传变量)→ 查询结果始终是第一页
4. 客户端导航Gatsby Link 组件点击/page/2/时,从page-data.json中反序列化pageContext并传入组件path配置错误导致page-data.json404 → 页面白屏,控制台报Cannot read property 'currentPage' of undefined

最致命的陷阱是pageContext类型错乱gatsby-awesome-pagination默认生成的currentPage是数字类型,但如果你在createPages中手动拼接路径如path:/blog/page/${i}/``(i是循环索引),而i是字符串"1",那么pageContext.currentPage就是字符串"1"。当你的 GraphQL 查询写成skip: ${pageContext.currentPage * pageContext.limit - pageContext.limit}时,字符串"1" * 10结果是10(隐式转换),但"1" - 10却是NaN!我曾因此调试了 3 小时,最终发现是pageContext.currentPage被意外转成了字符串。解决方案永远只有一条:在createPages中显式转换parseInt(i, 10),并在模板中用typeof pageContext.currentPage === 'number'做断言。

3. 实操全流程:从零搭建可上线的 Gatsby 分页系统(含完整代码与避坑注释)

3.1 前置准备:确认数据源结构与分页策略

在动代码前,先明确两个硬性前提:

第一,你的数据源必须支持分页查询。
Gatsby 的 GraphQL 层要求数据节点(Node)具备id字段且全局唯一。以最常见的 Markdown 博客为例,确保gatsby-transformer-remark插件已启用,且你的 Markdown 文件有正确的 frontmatter:

--- title: "Gatsby 分页实战详解" date: "2024-05-20" slug: "/blog/gatsby-pagination" --- 正文内容...

运行gatsby develop,打开http://localhost:8000/__graphql,执行以下查询验证数据可用性:

query { allMarkdownRemark( sort: { fields: [frontmatter___date], order: DESC } limit: 10 skip: 0 ) { totalCount edges { node { id frontmatter { title date } fields { slug } } } } }

✅ 成功返回totalCount > 0edges有数据,说明数据源就绪。
❌ 若totalCount为 0,请检查gatsby-source-filesystempath是否指向正确的 Markdown 目录,或gatsby-transformer-remark是否启用。

第二,确定分页策略——这是影响用户体验的核心决策。
不要默认用 “每页 10 篇”。根据你的内容类型选择:

场景推荐每页条数理由我的实际案例
技术博客(长文为主)6–8 篇首屏加载快,避免用户滑动过久;单篇文章平均阅读时长 5+ 分钟,用户更倾向深度阅读单篇我的 React 教程站,设为 7 篇,LCP(最大内容绘制)从 3.2s 降至 1.8s
新闻聚合站(短摘要)12–15 篇用户快速扫读,需要更高信息密度;摘要卡片高度固定,布局更可控客户的行业资讯站,14 篇,跳出率下降 22%
作品集展示(大图为主)9 篇平衡图片加载与页面长度;9 是 3×3 网格的天然倍数,CSS Grid 布局无冗余设计师个人站,9 篇,移动端滚动流畅度提升明显

注意:limit值一旦设定,必须同步更新createPages和模板中的 GraphQL 查询。我见过太多人改了createPageslimit,却忘了改模板里的limit,导致第一页显示 10 篇,第二页只显示 5 篇(因为skip计算错位)。

3.2 安装与基础配置:四步完成 gatsby-awesome-pagination 集成

Step 1:安装依赖

npm install gatsby-awesome-pagination # 或 yarn add gatsby-awesome-pagination

提示:无需额外安装gatsby-plugin-page-creator或其他分页插件,gatsby-awesome-pagination是纯工具库,无运行时依赖。

Step 2:在 gatsby-node.js 中编写 createPages 逻辑(核心!)

// gatsby-node.js const { createPagination } = require("gatsby-awesome-pagination"); exports.createPages = async ({ graphql, actions }) => { const { createPage } = actions; // 1. 查询所有 Markdown 文章总数(关键!必须用 totalCount) const result = await graphql(` query { allMarkdownRemark( sort: { fields: [frontmatter___date], order: DESC } ) { totalCount } } `); if (result.errors) { throw result.errors; } const totalCount = result.data.allMarkdownRemark.totalCount; const postsPerPage = 8; // 与模板中保持一致! // 2. 使用 createPagination 生成分页配置 // 注意:path 参数必须以 / 开头,且结尾带 /(Gatsby 路由规范) createPagination({ createPage, // Gatsby 提供的 API component: require.resolve("./src/templates/blog-list.js"), // 模板路径,必须存在! totalCount, // 总文章数,必填 itemsPerPage: postsPerPage, // 每页条数,必填 pathPrefix: "/blog", // 分页路径前缀,可选,默认为 "/" // 以下为高级配置,按需开启 // resolvePagePath: ({ pageNumber }) => `/blog/archive/${pageNumber}/`, // 自定义路径生成函数 // context: { siteTitle: "My Blog" }, // 额外注入的全局上下文 }); // 3. 【重要】单独创建首页(/blog/),避免与分页页混淆 // 因为 createPagination 默认生成 /blog/page/1/,而首页通常是 /blog/ createPage({ path: "/blog/", component: require.resolve("./src/templates/blog-list.js"), context: { currentPage: 1, limit: postsPerPage, skip: 0, totalPages: Math.ceil(totalCount / postsPerPage), // 注入首页特有字段,如 banner 图片 isHomepage: true, }, }); };

关键细节解析:

  • totalCount必须来自 GraphQL 查询的totalCount字段,不能edges.length计算(因为edges默认只返回前 20 条,totalCount才是真实总数);
  • pathPrefix: "/blog"意味着分页页路径为/blog/page/2/,而非默认的/page/2/,这直接影响 SEO 和用户感知;
  • 单独创建/blog/首页是最佳实践createPagination默认从第 1 页开始生成/blog/page/1/,但用户习惯访问/blog/,且/blog//blog/page/1/是两个不同 URL,需分别处理;
  • context中的isHomepage: true是为首页定制样式留的钩子,比如首页显示 Banner,分页页不显示。

Step 3:创建分页模板(blog-list.js)

// src/templates/blog-list.js import React from "react"; import { graphql } from "gatsby"; import Layout from "../components/layout"; import BlogPostCard from "../components/blog-post-card"; const BlogListTemplate = ({ data, pageContext }) => { const { currentPage, totalPages, isHomepage } = pageContext; const posts = data.allMarkdownRemark.edges; // 1. 安全解构 pageContext,防止构建时崩溃 if (!pageContext || typeof pageContext.currentPage !== "number") { console.error("Invalid pageContext in blog-list.js:", pageContext); return <Layout><div>分页上下文错误,请检查 gatsby-node.js 配置</div></Layout>; } // 2. 生成分页导航链接(关键!路径必须与 createPagination 一致) const generatePagePath = (pageNum) => { if (pageNum === 1 && !isHomepage) { return "/blog/page/1/"; } if (pageNum === 1 && isHomepage) { return "/blog/"; } return `/blog/page/${pageNum}/`; }; return ( <Layout> <h1>{isHomepage ? "最新文章" : `第 ${currentPage} 页`}</h1> {/* 文章列表 */} <div className="blog-grid"> {posts.map(({ node }) => ( <BlogPostCard key={node.id} post={node} /> ))} </div> {/* 分页导航 */} <nav className="pagination" aria-label="文章分页导航"> <ul> {/* 上一页 */} {currentPage > 1 && ( <li> <a href={generatePagePath(currentPage - 1)}> ← 上一页 </a> </li> )} {/* 页码列表(简化版,生产环境建议用 ellipsis) */} {Array.from({ length: totalPages }, (_, i) => i + 1).map((num) => ( <li key={num}> <a href={generatePagePath(num)} aria-current={num === currentPage ? "page" : undefined} > {num} </a> </li> ))} {/* 下一页 */} {currentPage < totalPages && ( <li> <a href={generatePagePath(currentPage + 1)}> 下一页 → </a> </li> )} </ul> </nav> </Layout> ); }; export default BlogListTemplate; // 3. GraphQL 查询:必须包含 skip 和 limit 变量! export const pageQuery = graphql` query BlogListQuery($skip: Int!, $limit: Int!) { allMarkdownRemark( sort: { fields: [frontmatter___date], order: DESC } limit: $limit skip: $skip ) { totalCount edges { node { id excerpt(pruneLength: 200) frontmatter { title date(formatString: "YYYY-MM-DD") description } fields { slug } } } } } `;

关键细节解析:

  • pageQuery中的$skip$limit变量必须声明为Int!(非空整数),否则 GraphQL 编译失败;
  • generatePagePath函数严格遵循createPagination的路径规则:首页用/blog/,分页页用/blog/page/{num}/,确保链接绝对准确;
  • aria-current="page"是无障碍访问(a11y)标准,屏幕阅读器会告知用户“当前在第 3 页”,Google 也视其为 SEO 信号;
  • 模板开头的pageContext安全校验是线上必备,避免因构建时pageContext缺失导致整个页面白屏。

Step 4:配置 gatsby-config.js(可选但推荐)

// gatsby-config.js module.exports = { plugins: [ // 其他插件... { resolve: `gatsby-plugin-canonical-urls`, options: { siteUrl: `https://www.yoursite.com`, }, }, ], };

gatsby-plugin-canonical-urls会自动为每个分页页添加<link rel="canonical">标签,例如/blog/page/2/的 canonical 指向自身,防止 Google 把/blog/page/1//blog/当作重复内容。这是 SEO 基础项,务必开启。

3.3 深度优化:让分页不止于功能,更兼顾性能与体验

3.3.1 首屏性能优化:延迟加载非首屏分页数据

Gatsby 默认会为所有分页页(包括/blog/page/100/)在构建时生成完整 HTML。但如果博客只有 50 篇文章,/blog/page/100/就是无效页。更糟的是,allMarkdownRemark查询会拉取全部数据,即使某页只显示 8 篇,GraphQL 仍需遍历所有节点计算totalCount。优化方案:

方案 A:用gatsby-plugin-limit-node限制查询范围(推荐)
安装gatsby-plugin-limit-node,在gatsby-config.js中配置:

{ resolve: `gatsby-plugin-limit-node`, options: { type: `MarkdownRemark`, limit: 200, // 只处理最近 200 篇,超出的忽略 }, },

方案 B:在 createPages 中预过滤数据(更精准)
修改gatsby-node.js的 GraphQL 查询,只取需要分页的子集:

// 替换原来的 totalCount 查询 const result = await graphql(` query { allMarkdownRemark( sort: { fields: [frontmatter___date], order: DESC } # 只查询可能被分页用到的数据,比如最近 200 篇 limit: 200 ) { totalCount edges { node { id } } } } `); const totalCount = result.data.allMarkdownRemark.totalCount; // 后续 createPagination 逻辑不变

实测效果:某博客从 1200 篇文章优化到只处理最近 200 篇,gatsby build时间从 4m23s 降至 1m18s,构建内存占用减少 65%。

3.3.2 用户体验增强:平滑滚动与加载状态反馈

纯静态分页点击后是硬跳转,体验生硬。添加简单 JS 增强:

// src/components/pagination.js import { navigate } from "gatsby"; export const handlePageClick = (e, path) => { e.preventDefault(); // 添加 loading 状态(如按钮变灰) const link = e.currentTarget; const originalText = link.textContent; link.textContent = "加载中..."; link.disabled = true; // 导航后恢复 navigate(path).then(() => { link.textContent = originalText; link.disabled = false; }); };

blog-list.js的分页链接中调用:

<a href={generatePagePath(currentPage - 1)} onClick={(e) => handlePageClick(e, generatePagePath(currentPage - 1))} > ← 上一页 </a>

注意:此增强仅作用于客户端导航(Gatsby Link),服务端直出 HTML 仍保持原样,符合渐进增强原则。

4. 常见问题排查与独家避坑指南:那些文档里不会写的血泪教训

4.1 问题速查表:高频报错与一键修复方案

报错现象根本原因修复方案我的实测耗时
pageContext is not definedcreatePages中未传context对象,或context为空对象{}检查createPagination调用处,确认component路径正确且文件存在;在createPage调用前加console.log("Creating page with context:", context)8 分钟(首次)
Cannot read property 'currentPage' of undefined模板中pageContext解构错误,或pageContext未注入在模板开头添加if (!pageContext) { return <div>Loading...</div>; };检查gatsby-node.jscreatePagecontext是否有currentPage字段12 分钟(路径拼写错误)
点击分页链接跳转 404path配置与createPaginationpathPrefix不一致,或.htaccess重写规则缺失运行gatsby build后检查public/blog/page/2/index.html是否存在;若存在但 404,检查服务器是否配置了FallbackResource /index.html(Apache)或try_files $uri $uri/ /index.html(Nginx)25 分钟(客户服务器 Nginx 配置未更新)
所有分页页内容相同(都是第一页)GraphQL 查询中skiplimit未使用pageContext变量,或变量名拼写错误(如currentPage写成currentPageNumpageQuery中打印console.log("Skip:", $skip, "Limit:", $limit);检查模板中pageContext字段名是否与createPagination生成的一致3 分钟($skip写成$skpi
TypeError: Cannot convert undefined or null to objectpageContext中某个字段(如previousPagePath)为null,但在模板中直接解构使用在模板中用可选链pageContext?.previousPagePath,或添加默认值const { previousPagePath = "" } = pageContext5 分钟(未处理边界情况)

4.2 独家避坑技巧:来自 3 个生产站点的实战经验

坑 1:中文路径与 gatsby-awesome-pagination 的兼容性问题
当你设置pathPrefix: "/博客"时,createPagination会生成/博客/page/2/,但某些 CDN 或服务器对 UTF-8 路径支持不佳,导致 404。解决方案:强制使用拼音路径。在gatsby-node.js中:

const { createPagination } = require("gatsby-awesome-pagination"); const pinyin = require("pinyin"); // npm install pinyin // 将中文前缀转为拼音 const blogPrefix = "/bo-ke"; // 手动映射,或用 pinyin("博客").join("-") createPagination({ createPage, component: require.resolve("./src/templates/blog-list.js"), totalCount, itemsPerPage: 8, pathPrefix: blogPrefix, // 使用拼音前缀 });

坑 2:createPaginationresolvePagePath函数陷阱
文档示例中resolvePagePath: ({ pageNumber }) =>/blog/${pageNumber}/,但pageNumber是数字,而createPagination内部会调用String(pageNumber)转字符串。如果你写成resolvePagePath: ({ pageNumber }) =>/blog/${pageNumber.toString().padStart(2, "0")}/,期望生成/blog/01/,但createPagination会再次调用String(),导致/blog/001/正确写法:在函数内直接返回完整路径字符串,不依赖外部转换:

resolvePagePath: ({ pageNumber }) => { const paddedNum = String(pageNumber).padStart(2, "0"); return `/blog/${paddedNum}/`; },

坑 3:Gatsby v4 升级 v5 后的createPagesEphemeral兼容问题
Gatsby v5 引入createPagesEphemeral用于临时页面,但gatsby-awesome-pagination仍基于createPages解决方案:在gatsby-node.js中保留createPages,并显式禁用createPagesEphemeral的干扰:

exports.createPages = async ({ graphql, actions }) => { // 你的 createPagination 逻辑... }; // 显式导出空的 createPagesEphemeral,防止 Gatsby v5 自动调用 exports.createPagesEphemeral = async () => {};

4.3 性能监控:如何验证分页优化是否生效

不要只看gatsby build时间,用真实指标验证:

  1. Lighthouse 测试

    • /blog//blog/page/2/分别运行 Lighthouse,对比First Contentful Paint (FCP)Largest Contentful Paint (LCP)
    • 优化后目标:LCP ≤ 2.5s(移动端),FCP ≤ 1.5s。
  2. 构建日志分析
    运行gatsby build --verbose,搜索Created page,确认生成的分页页数量与Math.ceil(totalCount / limit)一致;
    搜索allMarkdownRemark,确认 GraphQL 查询耗时是否显著下降。

  3. 网络面板验证
    打开 Chrome DevTools → Network,刷新/blog/page/2/,查看page-data.json文件大小。优化前可能达 800KB(含全部文章数据),优化后应 ≤ 120KB(仅本页 8 篇数据)。

我给客户做的最后一次审计:分页页page-data.json从 1.2MB 降至 98KB,LCP 从 4.7s 降至 1.9s,Google Search Console 中“移动设备可用性”警告清零。

5. 进阶扩展:超越基础分页的实用场景实现

5.1 多分类分页:为不同标签/分类生成独立分页流

你的博客有ReactGatsbyDesign三个标签,希望/tag/react/下的文章也支持分页。gatsby-awesome-pagination本身不支持多维度分页,但可以组合使用:

Step 1:在gatsby-node.js中为每个标签创建分页

// 获取所有标签 const tagResult = await graphql(` query { allMarkdownRemark { group(field: frontmatter___tags) { fieldValue totalCount } } } `); tagResult.data.allMarkdownRemark.group.forEach((tagGroup) => { const tagName = tagGroup.fieldValue; const tagCount = tagGroup.totalCount; if (tagCount > 0) { createPagination({ createPage, component: require.resolve("./src/templates/tag-list.js"), totalCount: tagCount, itemsPerPage: 6, pathPrefix: `/tag/${tagName.toLowerCase()}`, // /tag/react/ context: { tagName, // 传递标签名给模板 }, }); } });

Step 2:在tag-list.js模板中,用pageContext.tagName过滤数据

export const pageQuery = graphql` query TagListQuery($skip: Int!, $limit: Int!, $tagName: String!) { allMarkdownRemark( filter: { frontmatter: { tags: { in: [$tagName] } } } sort: { fields: [frontmatter___date], order: DESC } limit: $limit skip: $skip ) { totalCount edges { node { # ... 字段 } } } } `;

关键:$tagName变量必须在createPaginationcontext中注入,并在pageQuery中声明。这样每个标签都有独立的分页逻辑,互不干扰。

5.2 服务端渲染(SSR)兼容:为动态数据源添加分页支持

如果博客部分内容来自 CMS(如 Contentful),需在gatsby-node.js中处理createPages时的异步数据获取:

exports.createPages = async ({ graphql, actions, reporter }) => { const { createPage } = actions; // 1. 从 Contentful 获取文章列表(假设已配置 gatsby-source-contentful) const contentfulResult = await graphql(` query { allContentfulBlogPost(sort: { fields: publishDate, order: DESC }) { totalCount edges { node { contentful_id title publishDate } } } } `); if (contentfulResult.errors) { reporter.panicOnBuild("Error loading Contentful data", contentfulResult.errors); } const totalCount = contentfulResult.data.allContentfulBlogPost.totalCount; const postsPerPage = 10; // 2. 创建分页(逻辑同 Markdown) createPagination({ createPage, component: require.resolve("./src/templates/contentful-blog-list.js"), totalCount, itemsPerPage: postsPerPage, pathPrefix: "/cms-blog", }); };

此时contentful-blog-list.js的 GraphQL 查询需改为allContentfulBlogPostpageContext字段名保持一致即可。gatsby-awesome-pagination对数据源完全无感,只关心totalCountitemsPerPage

5.3 PWA 离线分页支持:让分页页在无网时也能访问

Gatsby 默认的gatsby-plugin-offline会缓存所有生成的 HTML 页面,包括/blog/page/2/。但需确保:

  • gatsby-plugin-offlinegatsby-config.js中启用;
  • gatsby build后,public/blog/page/2/index.html确实存在;
  • Service Worker 正常注册(检查浏览器 Application → Service Workers)。

测试方法:gatsby buildnpx serve -s public→ 打开/blog/page/2/→ 点击 Chrome DevTools → Application → Service Workers → Click "Update on reload" → 断网 → 刷新页面。如果页面正常显示,说明离线分页已生效。

我在线上环境实测:用户在地铁无网时访问/blog/page/3/,Service Worker 返回缓存的 HTML,文章列表完整显示,仅评论区(动态加载)显示“离线中”。这是静态站点的天然优势,无需额外开发。

6. 最后的实操心得:一个老手的三条硬核建议

我在 Gatsby 生态里做了五年主题开发,亲手交付过 17 个含复杂分页的商业站点,最后分享三条不写在文档里、但每次都能救命的建议:

第一条:永远先写createPages,再写模板,最后写查询。
新手常犯的顺序是:先写好blog-list.js,再想怎么传pageContext,结果发现createPages里漏了字段。正确顺序是

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/22 2:59:50

Skill-RAG:基于隐状态探测与技能路由的故障感知RAG框架解析

1. 项目概述&#xff1a;当RAG开始“思考”&#xff0c;故障感知如何重塑知识召回最近在折腾大模型应用落地的朋友&#xff0c;估计没少被RAG&#xff08;检索增强生成&#xff09;的各种“幺蛾子”折腾。标准RAG流程看似清晰&#xff1a;用户提问 -> 向量检索 -> 大模型…

作者头像 李华
网站建设 2026/6/22 2:59:07

Ubuntu 22.04 漏洞扫描实战:Vuls 无代理深度检测与 USN 精准修复

1. 项目概述&#xff1a;为什么在 Ubuntu 22.04 上用 Vuls 做漏洞扫描不是“可选项”&#xff0c;而是“必选项”Vuls 是一个开源的、无代理&#xff08;agentless&#xff09;的 Linux/Unix 系统漏洞扫描器&#xff0c;它不依赖于在目标主机上安装常驻进程&#xff0c;而是通过…

作者头像 李华
网站建设 2026/6/22 2:53:04

终极指南:如何用Reloaded-II为任意原生游戏创建和加载C Mod

终极指南&#xff1a;如何用Reloaded-II为任意原生游戏创建和加载C# Mod 【免费下载链接】Reloaded-II Universal .NET Core Powered Modding Framework for any Native Game X86, X64. 项目地址: https://gitcode.com/gh_mirrors/re/Reloaded-II Reloaded-II是一个基于…

作者头像 李华
网站建设 2026/6/22 2:49:39

JFinTEB:首个日语金融文本嵌入基准,解决领域专用模型评估难题

1. 项目背景&#xff1a;为什么需要一个日语金融文本嵌入基准&#xff1f;如果你在日语金融科技领域工作过&#xff0c;或者尝试过将大语言模型&#xff08;LLM&#xff09;或检索增强生成&#xff08;RAG&#xff09;系统应用到日文财报、新闻或公告分析中&#xff0c;大概率会…

作者头像 李华
网站建设 2026/6/22 2:42:29

Anthropic 称 AI 模型已显现脱离人类控制迹象,呼吁全球暂停开发

Anthropic 称 AI 模型已显现脱离人类控制迹象&#xff0c;呼吁全球暂停开发 Anthropic 在一份最新报告中称&#xff0c;其最新一代 AI 模型已显现出可能脱离人类控制的迹象&#xff0c;呼吁全球暂停 AI 开发。 这不是科幻电影里的情节&#xff0c;是 Anthropic 自己发的报告。…

作者头像 李华
网站建设 2026/6/22 2:37:42

TD4 4位DIY CPU:从组装到编程,带你探索计算机架构原理!

TD4 4位DIY CPU指南2024年2月12日&#xff0c;有人从速卖通买了一个名为TD4的可爱4位CPU套件。它有2个寄存器、一些LED灯&#xff0c;以及16字节的程序ROM。功能虽有限&#xff0c;但非常酷&#xff0c;还能让人学到很多计算机架构的原理。这个CPU的文档、原理图和图片都放在指…

作者头像 李华