Gatsby 站点搜索接入指南:从 js-search 客户端搜索到 Algolia API 搜索
2026/9/19 1:43:04 网站建设 项目流程

Gatsby 站点搜索接入指南:从 js-search 客户端搜索到 Algolia API 搜索

【免费下载链接】gatsbyReact-based framework with performance, scalability, and security built in.项目地址: https://gitcode.com/gh_mirrors/ga/gatsby

导读

本文围绕 Gatsby 官方文档 adding-search.md 的核心主题展开,系统讲解如何为 Gatsby 站点添加搜索功能:先厘清搜索的三大核心组件(搜索索引、搜索引擎、搜索 UI),再深入两种主流实现路线——基于js-search的纯客户端搜索,以及基于 Algolia 的 API 托管式搜索。读完本文,你将掌握两种方案的适用场景、完整配置步骤、关键代码实现与常见故障排查方法,并能直接在真实项目中落地。

站点搜索的三大组件

Gatsby 官方文档指出,为站点添加搜索功能需要三个必要组件:

搜索组件说明
搜索索引(Search index)以搜索友好格式存储的数据副本。索引用于优化搜索查询的速度与性能;没有索引,每次搜索都需要扫描站点中的每个页面,内容一多效率就会急剧下降。
搜索引擎(Search engine)对内容建立索引、接收查询词、在索引中执行查询并返回匹配文档的引擎。搜索引擎既可以是托管服务(如 Algolia),也可以是可自建托管的开源方案(如 Elasticsearch)。
搜索 UI站点中供用户输入查询词并查看查询结果的界面组件。部分搜索服务商会提供开箱即用的 React 组件,可直接嵌入 Gatsby 站点。

对应到 Gatsby 项目中,一个完整搜索流程是:先通过 Gatsby 的 GraphQL 数据层把内容(如 Markdown 博客文章)抽取出来构建索引,再由搜索引擎(客户端库或外部服务)执行查询,最后由 React 组件渲染结果。

添加搜索的两种技术路线

官方文档给出了两种截然不同的思路,选择哪种取决于内容规模、预算与隐私需求:

客户端搜索(Client-side search)

所有工作都在 Gatsby 站点内完成,无需第三方服务。优点是不依赖外部服务、无需付费;代价是需要自己写一部分代码,而且当需要索引的内容很多时,索引数据会显著增大打包体积(因为整个索引都要随站点 JS 一起下发到浏览器)。

官方推荐的实现方式与插件包括:

  • 使用js-search库:Adding Search with JS Search
  • 两个官方收录的 Gatsby 插件:
    • gatsby-plugin-elasticlunr-search(基于 ElasticLunr 的本地搜索)
    • gatsby-plugin-local-search(基于 FlexSearch 的本地搜索)

基于 API 的搜索引擎(API-based search engine)

另一种思路是把搜索索引托管到外部搜索引擎。这种方式扩展性更好:访问者无需下载整个搜索索引(站点越大索引越大),搜索请求直接发给服务商。代价是需要为托管搜索引擎付费,或购买商业搜索服务。

官方文档列出的可选方案包括:

  • Algolia—— SaaS 托管服务,有官方 Gatsby 插件
  • ElasticSearch—— 开源,有商业托管
  • Solr—— 开源,有商业托管
  • Meilisearch—— 开源,有 Gatsby 插件
  • Typesense—— 开源,有托管版本,也有 Gatsby 插件

其中最常见的是Algolia。下面分别对两条路线给出完整实操。

路线一:使用 js-search 实现客户端搜索

js-search由 Brian Vaughn 开发,是一个在客户端用 JavaScript 对 JSON 数据进行高效搜索的库,支持丰富的自定义选项。官方在 examples/using-js-search 中提供了完整示例站点,仓库内package.json显示的依赖为js-search@^1.4.3axios@^0.20.0

环境准备

基于官方hello worldstarter 创建新站点并安装依赖:

gatsby new js-search-example https://github.com/gatsbyjs/gatsby-starter-default cd js-search-example npm install js-search axios

或使用 Yarn:

yarn add js-search axios

其中axios用于处理基于 Promise 的 HTTP 请求(官方示例用它拉取远程书籍数据)。

策略选择:两种实现方式

官方文档建议按数据量选择策略:

  • 中小数据集:在组件内直接获取数据并构建索引,实现简单;
  • 大数据集:利用 Gatsby 的createPagesAPI 在构建期预先取数,通过pageContext把数据注入页面模板,减少浏览器端负担。

中小数据集:组件内完成一切

src/components/下创建SearchContainer.js,核心逻辑分五步:

  1. 组件挂载时触发componentDidMount(),用 axios 拉取数据;
  2. 成功后把数据存入 state 并调用rebuildIndex()
  3. 创建并配置 js-search 搜索引擎(索引策略、清洗器、搜索索引类型);
  4. addIndex()指定可搜索字段、用addDocuments()把数据加入索引;
  5. 输入框内容变化时调用search.search(),把结果渲染进表格。

关键配置代码:

const dataToSearch = new JsSearch.Search("isbn") dataToSearch.indexStrategy = new JsSearch.PrefixIndexStrategy() dataToSearch.sanitizer = new JsSearch.LowerCaseSanitizer() dataToSearch.searchIndex = new JsSearch.TfIdfSearchIndex("isbn") dataToSearch.addIndex("title") dataToSearch.addIndex("author") dataToSearch.addDocuments(bookList)
  • Search("isbn")isbn作为文档唯一标识(uid);
  • PrefixIndexStrategy前缀匹配策略;
  • LowerCaseSanitizer将查询与文本统一转为小写,避免大小写导致漏匹配;
  • TfIdfSearchIndex基于词频-逆文档频率打分排序;
  • addIndex("title")/addIndex("author")声明参与索引的字段。

搜索触发逻辑:

searchData = e => { const { search } = this.state const queryResult = search.search(e.target.value) this.setState({ searchQuery: e.target.value, searchResults: queryResult }) }

在页面中引入该组件即可使用(示例见 examples/using-js-search/src/pages/index.js):

import Search from "../components/SearchContainer" // ... <Search />

运行gatsby develop后访问http://localhost:8000即可看到可用的搜索框。

大数据集:借助 Gatsby 构建期 API

这种方式把"取数+建索引"的重活放在构建期完成。先在gatsby-node.js中通过createPages创建动态页面,并把数据塞进pageContext(相关概念见 gatsby-internals-terminology.md):

const path = require("path") const axios = require("axios") exports.createPages = ({ actions }) => { const { createPage } = actions return new Promise((resolve, reject) => { axios .get("https://bvaughn.github.io/js-search/books.json") .then(result => { const { data } = result createPage({ path: "/search", component: path.resolve(`./src/templates/ClientSearchTemplate.js`), context: { bookData: { allBooks: data.books, options: { indexStrategy: "Prefix match", searchSanitizer: "Lower Case", TitleIndex: true, AuthorIndex: true, SearchByTerm: true, }, }, }, }) resolve() }) .catch(err => { console.log(`error creating Page:${err}`) reject(new Error(`error on page creation:\n${err}`)) }) }) }

仓库中的 examples/using-js-search/gatsby-node.js 与该代码一致,可直接对照。

随后在src/templates/创建ClientSearchTemplate.js读取pageContext并渲染搜索组件,在src/components/创建ClientSearch.js依据options动态配置索引策略(支持Prefix match/Exact match/All三种策略、大小写敏感/不敏感两种清洗器、TfIdf 与无序索引两种索引类型、可选的停用词分词器)。最后访问http://localhost:8000/search验证效果。

路线二:使用 Algolia 实现 API 搜索

Algolia 是站点搜索托管平台:它托管搜索索引,你告诉它有哪些页面、页面路径及导航方式,用户搜索时由 Algolia API 返回结果。它提供免费额度(每月有限次数),更高流量需付费。官方完整指南见 adding-search-with-algolia.md。

搜索的两个阶段

  1. 索引(Indexing):由gatsby-plugin-algolia插件处理——每次执行gatsby build时把页面推送到 Algolia,用 GraphQL 自定义要索引的页面与字段;
  2. 搜索界面(Search UI):本指南使用 Algolia 提供的react-instantsearch组件库搭建(也可自行实现自定义 UI)。

提示:如果为技术文档类站点做搜索,Algolia 提供DocSearch产品,可自动从页面内容创建索引,省去手动索引,是文档站的首选方案。

项目初始化

以 Gatsby 官方博客 starter 为基础(其文章存放在content/blog,Markdown 文件的 frontmatter 含title字段,该字段会在索引查询中引用,若改名需同步修改查询):

gatsby new gatsby-algolia-guide https://github.com/gatsbyjs/gatsby-starter-blog

配置索引插件

安装插件并准备凭据:

npm install gatsby-plugin-algolia

注册 Algolia 账号后在 "API Keys" 页面复制Application ID、Search-Only API Key、Admin API Key,在项目根目录创建.env文件:

GATSBY_ALGOLIA_APP_ID=<App ID> GATSBY_ALGOLIA_SEARCH_KEY=<Search-Only API Key> ALGOLIA_ADMIN_KEY=<Admin API Key>

注意:Admin Key 拥有写权限,必须保密,不能出现在随站点分发的任何代码中;同时建议不要把.env提交进 git,可另建不含真实值的.env.example供协作者参考(环境变量机制详见 environment-variables.md)。

修改gatsby-config.js,先加载 dotenv,再注册插件:

require("dotenv").config() module.exports = { plugins: [ // ... your existing plugins here { resolve: `gatsby-plugin-algolia`, options: { appId: process.env.GATSBY_ALGOLIA_APP_ID, apiKey: process.env.ALGOLIA_ADMIN_KEY, queries: require("./src/utils/algolia-queries") }, } ], }

编写索引查询

src/utils/algolia-queries.js中定义查询。每个 query 对应一个索引,包含:GraphQL 查询(取回待索引页面与数据)、transformer(把 GraphQL 数据转换为 Algolia record)、indexName(索引名,不存在时会在索引过程中自动创建)以及可选的settings

const escapeStringRegexp = require("escape-string-regexp") const pagePath = `content` const indexName = `Pages` const pageQuery = `{ pages: allMarkdownRemark( filter: { fileAbsolutePath: { regex: "/${escapeStringRegexp(pagePath)}/" }, } ) { edges { node { id frontmatter { title } fields { slug } excerpt(pruneLength: 5000) } } } }` function pageToAlgoliaRecord({ node: { id, frontmatter, fields, ...rest } }) { return { objectID: id, ...frontmatter, ...fields, ...rest, } } const queries = [ { query: pageQuery, transformer: ({ data }) => data.pages.edges.map(pageToAlgoliaRecord), indexName, settings: { attributesToSnippet: [`excerpt:20`] }, }, ] module.exports = queries

要点:

  • 每条 record 必须有objectID,作为唯一标识;
  • 本示例索引了slugexcerpt与 frontmatter 的title三个字段,展示在搜索结果中;要索引更多字段,在pageQuery里继续加即可;
  • attributesToSnippet让 Algolia 对excerpt属性生成命中上下文片段(snippet);
  • 若未使用 starter blog,需把pagePath改为实际内容目录;
  • 支持多索引,本指南仅使用单个索引。

验证索引结果

运行gatsby build,输出应包含类似内容:

success Building static HTML for pages - 7.610s - 5/5 0.66/s Algolia: 1 queries to index Algolia: query 0: executing query Algolia: query 0: graphql resulted in 3 records Algolia: query 0: splitting in 1 jobs

核对graphql resulted in后的数字是否等于站点页面数;若不符,说明查询有问题。随后登录 Algolia 控制台,在 "Indices" 中查看Pages索引即可看到已索引的页面数据。

常见故障排查

  • GraphQLError: Field "fileAbsolutePath" is not defined by type MarkdownRemarkFilterInput:说明项目中没有找到页面,请检查gatsby-source-filesystem配置的路径以及查询中的pagePath
  • AlgoliaSearchError: Record at the position XX objectID=xx-xx-xx-xx-xx is too big size=xxxx bytes:Algolia 单条索引记录上限为10KB,超限即报错。注意查询中已将 excerpt 裁剪到 5000 字符,实际使用时务必裁剪长字段、不要索引无关数据。

构建搜索 UI

安装所需框架:

npm install react-instantsearch algoliasearch styled-components gatsby-plugin-styled-components @styled-icons/fa-solid

并在gatsby-config.js中加入gatsby-plugin-styled-components。随后按以下结构创建组件(完整代码见 adding-search-with-algolia.md):

  1. 搜索框src/components/search/search-box.js:一个表单,内含输入框与放大镜图标。核心是react-instantsearchuseSearchBoxHook,它暴露当前查询词query与修改函数refine,输入变化时调用refine(e.target.value)即触发搜索;
  2. 结果组件src/components/search/search-result.js:用Hits渲染命中列表、Highlight高亮命中字段、Snippet显示命中上下文片段、useStats展示命中数量、PoweredBy展示归属标识(Algolia 免费套餐要求展示 "Powered by Algolia")。由于 Algolia 支持多索引,SearchResult遍历indices逐索引渲染;
  3. 串联组件src/components/search/index.js:用algoliasearch/lite创建搜索客户端(通过useMemo缓存客户端实例,避免重复创建、充分利用其查询缓存),InstantSearch包裹搜索框与结果;useClickOutsideHook 实现点击组件外部自动收起弹出层;
  4. 支撑文件use-click-outside.js(监听mousedown/touchstart判断点击是否在组件外)与一组styled-*组件(实现"收起为图标、聚焦展开输入框、结果以 popover 弹出"的交互与样式)。若使用其他 CSS 方案,可跳过styled-*组件,直接替换为裸组件。

接入布局并在本地运行

把搜索组件放进src/components/layout.js的 header 中,并在此处定义要搜索的索引:

import Search from "./search" const searchIndices = [{ name: `Pages`, title: `Pages` }] // ... <header className="global-header"> <Search indices={searchIndices} /> {header} </header>

运行gatsby develop即可得到可用的搜索功能。

部署到 Netlify 的环境变量问题

若把项目部署到 Netlify,构建会报错AlgoliaSearchError: Please provide an application ID——因为 Netlify 拿不到未提交的.env中的配置。解决办法是在 Netlify 站点后台Settings > Build & deploy > Environment > Environment variables中声明GATSBY_ALGOLIA_APP_IDGATSBY_ALGOLIA_SEARCH_KEYALGOLIA_ADMIN_KEY三个环境变量(值与本地.env一致),重新部署后搜索即可正常工作。

两条路线的取舍与总结

维度客户端搜索(js-search)API 搜索(Algolia)
服务依赖无外部服务,纯本地依赖第三方托管服务
成本免费,仅付出开发成本免费额度有限,高流量需付费
可扩展性索引随内容增大,打包体积显著膨胀索引在服务端,站点体积不受影响
实现复杂度需自行编写取数与索引逻辑插件负责索引,UI 用现成组件
适用场景中小型内容站点内容量大、持续增长的站点

无论选择哪条路线,都可以回到 Gatsby 官方文档的顶层指南 adding-search.md 回顾三大组件模型:搜索索引是性能基石、搜索引擎是查询核心、搜索 UI是用户体验的最终呈现——三者在两种方案中的实现位置不同,但职责边界始终清晰。读者也可以参考仓库内benchmarks/examples/目录下的搜索相关示例,结合实际内容规模选择最合适的方案。

【免费下载链接】gatsbyReact-based framework with performance, scalability, and security built in.项目地址: https://gitcode.com/gh_mirrors/ga/gatsby

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询