Kestrel 的 FAQ 页面越来越长,顾客常常滚过了自己要找的答案。商家给我们看了一个竞争对手的帮助页面,顶部有一个搜索框,并希望我们照着做:输入一个词,只显示相关的问题。
这就是那个 Shopify FAQ 搜索的故事:snippet 本身、让第一版在线上店铺什么也找不到的 bug,以及最终成为一堂关于 Shopify 主题内联脚本课程的修复。
关于本案例:这是根据真实 Shopify 项目整理的综合案例。品牌名称和部分细节已更改。代码、bug 和修复都与实际发生的完全一致。

为什么不用 Shopify 自带的搜索?
Shopify 店铺搜索覆盖商品、系列、页面和博客文章。商家在主题版块区块里填写的文字,也就是大多数 FAQ 版块存放问题的地方,并不在这个索引里。顾客在店铺里搜索“retour”,找到的是商品,而不是关于退货的答案。
但 FAQ 的答案本来就在页面的 HTML 里。在浏览器中筛选它们,不需要请求、不需要应用、也不需要索引。顾客还在输入,结果就已经出现了。
第一版:一个 snippet 加一行 render
商家在代码编辑器里修改主题,所以我们把改动尽量缩小:一个包含标记、样式和脚本的新 snippet,以及 FAQ 版块中用来渲染它的一行代码。
{%- render 'faq-search', section: section -%}
<div class="faqs ..."> <!-- the theme's existing FAQ markup -->
脚本是一个小型自定义元素。每次输入时,它做四件事:
- 规范化文本:转为小写并去掉重音符号,这样“e”也能找到“é”。
- 把搜索词拆成单词,只有标题或答案中包含全部单词的问题才保留。
- 当某个分类下没有任何匹配的问题时,连同标题隐藏整个分类。
- 完全没有匹配时,显示“未找到相关问题”的提示,并引导顾客联系客服。
我们在无头浏览器里用一份 FAQ 标记的副本做了测试。每次搜索都返回了正确的问题。于是我们把它交付了。
那张截图
回复里附了一张截图。商家把某个答案里的一整句话粘贴进了搜索框。页面显示“未找到相关问题”,而下面所有问题依然可见。什么都没隐藏,也什么都没找到。
两个现象指向同一件事:脚本以为页面上没有问题。我们打开线上页面,直接查询这个元素。页面上有两组 FAQ、共七个问题,而搜索元素自己的分组列表是空的。
原因:内联脚本执行得太早
snippet 渲染在问题的上方,它的 <script> 标签就在 snippet 里。浏览器从上到下解析 HTML。解析到脚本时,它定义了自定义元素;由于 <faq-search> 已经在文档中,它的 connectedCallback 会立即执行。此时解析器还没有读到下方的问题。
我们的第一版在 connectedCallback 里查找 FAQ 分组并保存起来。结果它保存了一个空列表,而且只保存一次,永远不变。我们的测试之所以通过,是因为测试页面恰好把脚本放在了问题之后。

修复只需要一个 getter。脚本不再在元素连接时保存分组,而是在顾客每次输入时重新查找:
get groups() {
return Array.from(this.closest('.section').querySelectorAll('.faq'));
}
等到有人开始输入时,页面早已解析完毕。对几十个问题来说,这次查找几乎不耗时;而且当商家编辑时主题编辑器重新渲染版块,它也照样有效,缓存的列表就做不到。
第二个更隐蔽的 bug
在把代码移进最终 snippet 时,我们又发现一个问题。负责隐藏被筛掉内容的 CSS 规则写成了 .faq [data-faq-hidden],中间有个空格。它能隐藏分组内的问题,却隐藏不了分组本身,于是可能留下一个下面什么都没有的分类标题。现在这条规则两者都覆盖:.faq [data-faq-hidden], .faq[data-faq-hidden]。

在它真正运行的地方测试
这一次,我们直接在线上 FAQ 页面上测试:在浏览器中针对真实标记运行修正后的逻辑,然后才请商家做任何修改。
| “levertijd” | 只显示关于配送时间的问题 |
| “garantie” | 显示来自两个不同分类的保修问题 |
| “€70 gratis” | 只显示运费问题,通过答案匹配到 |
| “retour” | 只显示退货问题,另一个分类被隐藏 |
| “xyz” | 无结果,并显示“未找到相关问题” |
我们的收获
- 按照真实页面的顺序来测试。在内联脚本中定义的自定义元素,只能看到它上方的 HTML。
- 除非确定内容已经存在,否则不要在
connectedCallback中缓存 DOM 查询。getter 比等待DOMContentLoaded更简单,也能经受主题编辑器的重新渲染。 - 商家发来截图时,把几个现象放在一起看。“没有结果”加上“什么都没隐藏”,在打开控制台之前就锁定了问题。
这个搜索后来成为一个完整帮助页面的一部分,详见我们的 Shopify FAQ 页面模板案例。我们为同一家店铺打造的商品页优惠,请看买一送一赠品选择器。如果你的 FAQ 或主题的其他部分也需要这样的工作,请了解我们的 Shopify 主题开发服务。
常见问题
FAQ 搜索框会影响 SEO 吗?
不会。所有问题和答案都保留在 HTML 中,只有在顾客输入搜索词之后,才会在其浏览器中被隐藏。
给 Shopify FAQ 页面加搜索需要应用吗?
不需要。问题本来就在页面上,一个小脚本就能筛选。比应用更快,也没有月费。
FAQ 很多时也能用吗?
能。在浏览器中筛选几百个问题是瞬间完成的。再多的话,把 FAQ 拆分成几个页面通常对顾客更友好。
它也会搜索答案吗?
会。每个问题都会按标题和答案文字进行匹配,忽略重音符号,而且搜索中的所有单词都必须出现。