Gatsby 私有 API 数据接入全指南:三种方案与源码级实践
【免费下载链接】gatsbyReact-based framework with performance, scalability, and security built in.项目地址: https://gitcode.com/gh_mirrors/ga/gatsby
本文聚焦 Gatsby 如何从私有 API(Private API)拉取数据。私有 API 通常指位于公司内网、需要认证或不对公网开放的接口,本文给出的三种接入方案——gatsby-source-graphql插件、构建期直接抓取的无 GraphQL 方案、以及自建 source plugin——可以覆盖绝大多数私有数据源的接入场景。读完本文,你将掌握每种方案的适用边界、完整配置示例与关键权衡,并能结合环境变量与自动化重建机制,构建一套可靠、可维护的私有数据驱动站点。
私有 API 与公有 API:Gatsby 视角下的本质区别
Gatsby 可以从 headless CMS、数据库、SaaS 服务、公有 API 以及你自己的私有 API 中拉取数据。从 Gatsby 的角度看,从私有 API 或公有 API 拉取数据在机制上没有任何区别——唯一的不同在于 API 对 Gatsby 的可用性:私有 API 往往部署在防火墙之后、需要身份认证,或者在开发环境与生产环境之间有不同的可达性配置。
因此,接入私有 API 的核心工作并不在于"如何让 Gatsby 认识私有数据",而在于回答三个问题:
- 用什么样的机制把数据取进来(GraphQL stitching、构建期抓取、还是完整 source plugin);
- 私有 API 的认证信息与端点地址如何安全地注入构建过程(环境变量);
- 数据更新后如何触发站点重建,避免展示过期数据。
三种接入方案总览
Gatsby 官方给出了三种从私有 API 拉取数据的方式,选择顺序取决于你的 API 形态与团队的技术背景:
| 方案 | 适用场景 | 数据是否进入 Gatsby 数据层 | 上手成本 |
|---|---|---|---|
使用gatsby-source-graphql | 私有 API 本身就是 GraphQL API | 是(远程 schema 被拼接进 Gatsby GraphQL) | 低,只需配置 |
| 构建期直接抓取("Using Gatsby without GraphQL") | 非 GraphQL API,且团队 GraphQL 经验有限 | 否(作为非结构化数据处理) | 低,但有明显取舍 |
| 自建 source plugin | 以上都不满足,或需要长期、深度集成 | 是(创建 Gatsby 节点) | 高,参考官方教程 |
下面逐一展开每种方案的实操细节与底层原理。
方案一:用 gatsby-source-graphql 拼接私有 GraphQL API
如果私有 API 是基于 GraphQL 的,最直接的方式是使用官方插件gatsby-source-graphql。它的原理是远程 schema 拼接(schema stitching):插件将远程 API 的 Query 类型包装成一个自定义类型(typeName),并把整个远程 schema 挂到 Gatsby GraphQL 的某个字段(fieldName)下,从而让你像查询本地数据一样查询私有 API。
最小配置
在gatsby-config.js中加入插件即可:
module.exports = { plugins: [ { resolve: "gatsby-source-graphql", options: { // 远程 schema Query 类型的任意名称 typeName: "SWAPI", // 远程 schema 在 Gatsby 查询中暴露的字段名,查询时使用 fieldName: "swapi", // 私有 GraphQL API 地址 url: "https://your-private-api.example.com/graphql", }, }, ], }配置完成后,在页面组件中就可以这样查询:
{ # fieldName 在配置中定义 swapi { allSpecies { name } } }从源码可以看出,插件在createSchemaCustomization阶段通过addThirdPartySchema动作把远程 schema 注入 Gatsby 的数据层(见 packages/gatsby-source-graphql/src/gatsby-node.js)。其中transforms.js里的NamespaceUnderFieldTransform负责把远程 Query 类型重命名为typeName并挂到fieldName字段下,StripNonQueryTransform则会把远程 schema 中的 Mutation 和 Subscription 剔除——也就是说,通过该插件只能查询(Query)远程 API,不能调用其变更操作(见 packages/gatsby-source-graphql/src/transforms.js)。
另外注意,插件的参数校验(pluginOptionsSchema)要求typeName与fieldName必填,且url与createLink二选一(见 gatsby-node.js),这为下面的高级配置埋下了伏笔。
私有 API 的认证:headers 与 fetch
私有 API 通常需要认证。Gatsby 官方推荐把凭证通过环境变量注入构建过程,避免把密钥提交进版本库。插件提供多种携带认证信息的方式:
方式一:静态 headers
{ resolve: "gatsby-source-graphql", options: { typeName: "GitHub", fieldName: "github", url: "https://api.github.com/graphql", // HTTP 请求头 headers: { Authorization: `Bearer ${process.env.GITHUB_TOKEN}`, }, }, }方式二:headers 函数(支持异步获取 token)
{ resolve: "gatsby-source-graphql", options: { typeName: "GitHub", fieldName: "github", url: "https://api.github.com/graphql", // headers 也可以是一个函数,允许异步获取凭证 headers: async () => { return { Authorization: await getAuthorizationToken(), } }, // 额外传给 node-fetch 的选项 fetchOptions: {}, }, }方式三:自定义 fetch 函数——当默认的fetch行为无法满足签名等需求时,可以传入与fetch兼容的自定义实现:
{ resolve: "gatsby-source-graphql", options: { typeName: "GitHub", fieldName: "github", url: "https://api.github.com/graphql", // fetch 兼容 API fetch: (uri, options = {}) => fetch(uri, { ...options, headers: sign(options.headers) }), }, }方式四:createLink 手动构造 Apollo Link——这是最灵活的方式,适合需要拦截、注入或包装请求的场景,也可以返回 Promise:
{ resolve: "gatsby-source-graphql", options: { typeName: "GitHub", fieldName: "github", // 手动创建 Apollo Link,可以返回 Promise createLink: pluginOptions => { return createHttpLink({ uri: "https://api.github.com/graphql", headers: { Authorization: `Bearer ${process.env.GITHUB_TOKEN}`, }, fetch, }) }, }, }从源码看,插件会优先使用createLink的结果;否则使用url、fetch、fetchOptions、headers组装一个createHttpLink(见 gatsby-node.js),batch开启时则替换为 DataLoader 版的createDataloaderLink。
生产级网络:用 Apollo Link 组合重试与错误处理
私有 API 处于内网或复杂网络环境中,请求可能超时、失败或返回错误。插件允许通过createLink组合多个 Apollo Link,为请求附加重试、错误处理、日志等能力。常用 Link 包括:
@apollo/client/link/retry:对失败或超时的查询进行重试;@apollo/client/link/error:统一错误处理;@apollo/client/link/http:发送 HTTP 查询(默认使用)。
带重试的 HTTP Link 配置示例:
const { createHttpLink, from } = require(`@apollo/client`) const { RetryLink } = require(`@apollo/client/link/retry`) const retryLink = new RetryLink({ delay: { initial: 100, max: 2000, jitter: true, }, attempts: { max: 5, retryIf: (error, operation) => Boolean(error) && ![500, 400].includes(error.statusCode), }, }) module.exports = { plugins: [ { resolve: "gatsby-source-graphql", options: { typeName: "SWAPI", fieldName: "swapi", url: "https://your-private-api.example.com/graphql", // pluginOptions 包含全部插件选项 createLink: pluginOptions => from([retryLink, createHttpLink({ uri: pluginOptions.url })]), }, }, ], }控制 schema:createSchema 与 transformSchema
默认情况下,插件通过 introspection(内省)从远程 API 获取 schema,并把结果缓存在.cache目录中;修改 schema 后需要清缓存(例如重启gatsby develop)才能生效。如果你希望手动控制 schema 的获取方式,可以使用createSchema回调——它可以从本地 introspection JSON 或 SDL 文件构建 schema,且使用createSchema时插件不再缓存 schema:
const fs = require("fs") const { buildSchema, buildClientSchema } = require("graphql") { resolve: "gatsby-source-graphql", options: { typeName: "SWAPI", fieldName: "swapi", url: "https://your-private-api.example.com/graphql", // 从 introspection JSON 构建 createSchema: async () => { const json = JSON.parse( fs.readFileSync(`${__dirname}/introspection.json`) ) return buildClientSchema(json.data) }, // 或者从 SDL 文本构建 // createSchema: async () => { // const sdl = fs.readFileSync(`${__dirname}/schema.sdl`).toString() // return buildSchema(sdl) // }, }, }对于更高级的场景(例如给远程 schema 添加自定义指令或字段),可以使用transformSchema选项在 schema 被拼接进 Gatsby 之前修改它。该回调接收{ schema, link, resolver, defaultTransforms, options }对象,返回最终用于拼接的 schema。仓库 README 提供了完整的默认实现示例(见 packages/gatsby-source-graphql/README.md),可供参考对照。
数据刷新与性能调优
定时重新拉取:默认情况下,gatsby-source-graphql只在服务重启时重新拉取数据。如果数据更新较为频繁,可以配置refetchInterval(单位:秒)让插件周期性刷新:
{ resolve: "gatsby-source-graphql", options: { typeName: "SWAPI", fieldName: "swapi", url: "https://your-private-api.example.com/graphql", // 每 60 秒重新拉取一次 refetchInterval: 60, }, }仓库自带的示例站点examples/using-gatsby-source-graphql就使用了refetchInterval: 60配合 GraphCMS 作为演示(见 examples/using-gatsby-source-graphql/gatsby-config.js)。
查询批处理(query batching):默认每个查询单独发起一次网络请求。当页面数量大、查询密集时,可以通过batch: true开启批处理——插件底层使用 DataLoader,把同一批次的多条查询合并成一次网络请求发送到服务器,再拆分结果返回。从源码目录结构看,批处理逻辑集中在 batching/dataloader-link.js 与 batching/merge-queries.js,并有配套的单元测试(见 packages/gatsby-source-graphql/src/batching/tests/)。
注意批处理的边界:它只对大约同时开始的查询有效,因此吞吐量受 Gatsby 并行执行的查询数限制(默认约 4 个)。可以通过环境变量GATSBY_EXPERIMENTAL_QUERY_CONCURRENCY提高并行度,并用dataLoaderOptions.maxBatchSize控制每批最多合并的查询数:
cross-env GATSBY_EXPERIMENTAL_QUERY_CONCURRENCY=20 gatsby develop{ resolve: "gatsby-source-graphql", options: { typeName: "SWAPI", fieldName: "swapi", url: "https://your-private-api.example.com/graphql", batch: true, // DataLoader 选项,默认每批最多合并 5 条查询 dataLoaderOptions: { maxBatchSize: 10, }, }, }以 20 个并行查询、每批 5 条为例,仍然会有 4 个批次并行执行。批处理对某些配置可以带来明显的速度提升,但同一批次中若任意一条查询返回错误,整个批次都会失败,因此需要结合重试 Link 一起使用。另外,如果远程服务器支持 Apollo 风格批处理,也可以考虑通过createLink传入HttpLinkDataLoader,其错误报告更友好,但通常比查询合并慢。
已知限制(务必先评估)
该插件的 README 明确列出以下限制(见 packages/gatsby-source-graphql/README.md):
- 不支持增量构建(Incremental Builds),内容较多的大型站点可能遇到构建速度问题;
- 不支持 CMS Preview / 实时预览;
- 对 GraphQL 数据层支持不完整,包括图片优化 / Image CDN 与指令(directive)支持。
因此官方建议:如果数据源已有现成的 source plugin(例如 WordPress 用gatsby-source-wordpress、Contentful 用gatsby-source-contentful),应优先使用;gatsby-source-graphql更适合**简单的概念验证(POC)**或确实没有现成插件的场景。
方案二:无 GraphQL 方案——构建期抓取非结构化数据
如果私有 API 不是 GraphQL 接口,且团队对 GraphQL 不熟悉,可以采用"非结构化数据(unstructured data)"方案:在gatsby-node.js中直接抓取数据,通过createPagesAPI 把数据传给页面模板。此处的"非结构化"指数据在 Gatsby 数据层之外被直接使用,而不是转换为 Gatsby 节点。
实操:createPages 中抓取数据
以仓库文档中的"Pokémon"示例为例(详见 using-gatsby-without-graphql 指南),在gatsby-node.js中:
exports.createPages = async ({ actions: { createPage } }) => { // `getPokemonData` 是抓取数据的函数 const allPokemon = await getPokemonData(["pikachu", "charizard", "squirtle"]) // 创建列出所有 Pokémon 的页面 createPage({ path: `/`, component: require.resolve("./src/templates/all-pokemon.js"), context: { allPokemon }, }) // 为每只 Pokémon 创建页面 allPokemon.forEach(pokemon => { createPage({ path: `/pokemon/${pokemon.name}/`, component: require.resolve("./src/templates/pokemon.js"), context: { pokemon }, }) }) }关键点:
createPages是 Gatsby Node API 之一,挂载在 Gatsby 的引导(bootstrap)序列中;createPageaction 是实际创建页面的动作;- 通过
context传入的数据会以pageContextprop 的形式注入页面模板:
export default function Pokemon({ pageContext: { pokemon } }) { return ( <div style={{ width: 960, margin: "4rem auto" }}> <h1>{pokemon.name}</h1> <img src={pokemon.sprites.front_default} alt={pokemon.name} /> <h2>Abilities</h2> <ul> {pokemon.abilities.map(ability => ( <li key={ability.name}> <Link to={`./pokemon/${pokemon.name}/ability/${ability.name}`}> {ability.name} </Link> </li> ))} </ul> <Link to="/">Back to all Pokémon</Link> </div> ) }优势与取舍(务必阅读原文全文)
这种方案的优点是:对 GraphQL 新手友好、没有中间转换环节——抓取数据后直接建页面。但官方文档明确指出,放弃 Gatsby 数据层意味着同时放弃以下能力:
- 在页面组件旁以声明式方式声明所需数据;
- 消除前端数据样板代码(无需自己请求与等待数据);
- 把前端复杂性下沉到查询中,在构建期完成数据转换;
- 利用 GraphQL 精确加载视图所需的数据(避免数据冗余,这也是 Gatsby 性能的重要来源);
- 开发时利用热重载——例如在上述 Pokémon 站点中若想给详情页加"查看其他 Pokémon"区块,就需要改
gatsby-node.js并重启 dev server;而使用 GraphQL 查询则可以直接加查询并热重载。
同时,非结构化方案还无法享受 transformer 插件生态的优化,例如gatsby-plugin-image(图片优化)、gatsby-transformer-sharp(图片处理查询字段)等;并且当从多个数据源分别抓取时,抓取代码会逐渐变得难以维护。
Gatsby 的官方建议是:小型站点可以用此方案快速起步;当站点变复杂、需要转换数据或接入更多数据源时,先到插件库查找现成的 source / transformer 插件,没有则参考插件开发指南自建(见 using-gatsby-without-graphql 指南)。仓库中还提供了完整可运行的示例 examples/using-gatsby-without-graphql,可以直接对照学习。
方案三:自建 source plugin——为私有 API 打造第一方集成
如果私有 API 非 GraphQL、数据需要长期深度集成(节点建模、关系建立、schema 定制、图片处理等),那么正确的做法是创建一个 source plugin。官方为此提供了从零开始的八部分系列教程(Part 0:介绍与前置条件 至 Part 8)。
source plugin 的本质:在 Gatsby 引导阶段从外部 API 抓取数据,创建 Gatsby 节点(node)并建立节点间关系,可选地定制站点的 GraphQL schema。从代码结构上讲,它与其他插件一样,必须包含:
- 一个带入口点(entrypoint)的
package.json; - 一个或多个 Gatsby API 文件(通常是
gatsby-node.js)。
source plugin 与 transformer 插件的职责边界:source 只负责把数据带进来,transform 负责把一种数据形态转换成另一种(例如 Markdown → HTML),职责分离让每个插件更可复用。需要注意,如果数据是本地的(文件系统或站点仓库内),一般不需要自建 source plugin,直接使用gatsby-source-filesystem配合 transformer 插件即可(见 Part 0 文档)。
发布与否取决于你自己:可以发布到 npm 供社区使用,也可以仅作为站点内的 local plugin。教程后半部分专门讲解了如何以易于发布的方式组织插件开发(使用 yarn workspaces 构建 monorepo 是官方推荐的开发方式)。
其他关键考虑
1. 实时性需求:优先考虑运行时查询
如果私有 API 数据更新非常频繁,或者站点有"实时更新"的期望,那么在构建期拉取可能不合适——更合理的做法是在**运行时(runtime)**直接查询数据。这与前面方案一、方案二的"构建期拉取"是不同取向:构建期拿到的是构建那一刻的快照,运行时查询拿到的才是最新数据。
2. 优先寻找现成插件
在写代码之前,先确认是否有现成插件可以替代"直连 API"。例如,如果你能直接访问存储数据的 MongoDB 数据库,那么gatsby-source-mongodb插件(packages/gatsby-source-mongodb)会比通过 API 抓取更顺手。可以浏览插件库中所有gatsby-source-*插件,评估是否存在可复用的方案。
3. 构建环境与 API 可达性
私有 API 往往只在内网可达,这直接影响构建策略:
- 如果私有 API 仅在公司网络内可用,你需要把构建 CI 服务器也接入内网(即让 CI 能访问该 API),否则就需要在本机运行
gatsby build; - 开发环境与生产环境可能需要不同的 API 端点配置。此时应使用环境变量区分环境。
Gatsby 对环境变量有内置支持(详见 environment-variables 指南):
- 开发时自动从
.env.development加载,构建时从.env.production加载; - 若要在
gatsby-*.js文件与 Functions 中使用 Node 侧环境变量,需在gatsby-config.js顶部加载 dotenv:
require("dotenv").config({ path: `.env.${process.env.NODE_ENV}`, }) module.exports = { plugins: [ { resolve: `gatsby-source-custom`, options: { apiKey: process.env.API_KEY, }, }, ], }GATSBY_API_URL=https://dev.example.com/api API_KEY=927349872349798- 只有以
GATSBY_前缀命名的变量才会暴露给浏览器代码;API_KEY这类敏感变量只在 Node 侧可用,不会被注入前端——这对私有 API 凭证的保护至关重要; .env*文件应加入.gitignore,避免密钥进入版本库;- 需要 staging / test 等更多环境时,可以自定义 dotenv 的
path(例如STAGING=true gatsby build配合.env.${process.env.NODE_ENV}.staging); - 注意
NODE_ENV、PUBLIC_DIR等保留环境变量不可覆盖,另有GATSBY_CPU_COUNT可控制构建 worker 并行度。
4. 数据更新后自动重建站点
为了避免站点展示过期数据,建议建立"数据更新 → 触发重建"的自动化流程。例如站点托管在 Netlify 时,可以让私有 API 在数据变更后调用 Netlify 的 webhook 来触发一次新的部署构建。这种"构建时快照 + 事件驱动重建"的组合,是私有 API 驱动站点的典型运维模式,具体事件机制随托管平台而异,可查阅你所用平台的构建通知(build notifications)文档。
总结
接入私有 API 的三条路径各有明确分工:
- GraphQL 私有 API→
gatsby-source-graphql:配置最轻,支持认证(headers / fetch / createLink)、schema 定制、定时刷新与查询批处理,但要接受不支持增量构建与图片优化的限制; - 非 GraphQL、快速起步→ 构建期用
createPages抓取非结构化数据:上手简单,但要放弃数据层的声明式查询、热重载与 transformer 生态; - 长期深度集成→ 自建 source plugin:投入最大、能力最完整,官方教程提供了完整的八部分指引。
无论选择哪条路径,私有 API 接入的工程化要点是共通的:用环境变量隔离开发/生产端点与认证凭证、把 API 可达性纳入 CI 网络规划、并用自动化重建保证数据新鲜度。建议先用方案一或方案二快速跑通 POC,再根据数据复杂度和更新频率演进到更完整的方案。
【免费下载链接】gatsbyReact-based framework with performance, scalability, and security built in.项目地址: https://gitcode.com/gh_mirrors/ga/gatsby
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考