Skip to content

Switch search from Pagefind to Algolia#835

Open
teallarson wants to merge 8 commits intomainfrom
teallarson/algolia-search
Open

Switch search from Pagefind to Algolia#835
teallarson wants to merge 8 commits intomainfrom
teallarson/algolia-search

Conversation

@teallarson
Copy link
Contributor

@teallarson teallarson commented Feb 27, 2026

Summary

  • Replaces Pagefind's build-time static search index with Algolia's crawler-based search (no build step needed — Algolia crawls the live site automatically)
  • Adds a custom AlgoliaSearch component using react-instantsearch with a keyboard-accessible modal (⌘K), theme-aware light/dark styling, and brand red hit highlights
  • Removes pagefind, rehype-stringify, remark, remark-rehype deps and the custompagefind build script
  • Fixes a pre-existing bug where /site.webmanifest 404'd because the middleware matcher didn't exclude .webmanifest files

Test plan

  • Search button appears in navbar with correct light/dark theming
  • ⌘K opens the search modal
  • Typing returns results with brand red highlights on matched text
  • Clicking a result navigates to the correct page
  • Escape / clicking backdrop closes the modal
  • pnpm build completes without a Pagefind step

🤖 Generated with Claude Code


Note

Medium Risk
Medium risk because it swaps the site search implementation and removes the build-time indexing step; misconfigured Algolia env vars or index settings would leave search unavailable. Also tweaks middleware matching for .webmanifest, which could affect routing if the matcher regex is wrong.

Overview
Switches docs search from build-time Pagefind indexing to Algolia crawler-based search by disabling Nextra’s built-in search, adding a client-side AlgoliaSearch modal (button + ⌘/Ctrl+K, hit highlighting, empty/no-results states), and wiring it into the main layout.

Removes the Pagefind tooling and build hooks (scripts/pagefind.ts, custompagefind scripts, related deps) and updates styling/env examples for Algolia configuration. Also fixes a routing edge case by excluding .webmanifest files from the middleware matcher so /site.webmanifest is served correctly.

Written by Cursor Bugbot for commit 79545f9. This will update automatically on new commits. Configure here.

Replaces Pagefind's build-time static search index with Algolia's
crawler-based search. The index is populated automatically by Algolia's
crawler — no build step needed. Adds a custom InstantSearch modal
component that is theme-aware (light/dark) and uses the brand red for
hit highlights.

Also fixes a pre-existing bug where site.webmanifest 404'd in dev
because the middleware matcher didn't exclude .webmanifest files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Feb 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Feb 27, 2026 10:24pm

Request Review

`React.ComponentType<{ components?: ... }>` was incompatible with
`MDXContent` returned by @mdx-js/mdx's evaluate(), due to React 19
components returning ReactNode (including undefined) while @types/mdx
expects Element | null. Using React.ElementType resolves the boundary
without type suppressions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not related but docs weren't building right on my branch until i made this change.

- Only initialize algoliasearch when all three env vars are present;
  show a setup instructions message in the modal otherwise
- Add safeHref() to reject non-relative/non-https hit URLs (XSS defense)
- Remove unused inputRef, FOCUS_DELAY_MS, and the focus useEffect —
  SearchBox autoFocus already handles this correctly

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
teallarson and others added 2 commits February 27, 2026 17:13
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@uidotdev/usehooks is already a dependency. useEventListener handles
add/remove lifecycle internally, removing the boilerplate.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces hardcoded hsl(347 ...) values with var(--primary) from
@arcadeai/design-system tokens and color-mix() for transparent tints.
Dark mode text override removed since --primary resolves identically
in both modes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
teallarson and others added 2 commits February 27, 2026 17:21
Replace hardcoded neutral/dark: pairs with semantic tokens where
the mapping is clean and visually equivalent:
- text-neutral-900 dark:text-white → text-foreground
- text-neutral-400/500 dark:text-neutral-500/400 → text-muted-foreground
- border-neutral-200/300 dark:border-white/10 → border-border
- bg-white dark:bg-neutral-900 (modal panel) → bg-popover

Hover states on hits and the trigger button bg retain explicit
neutral/white values since bg-muted dark resolves too dark for those.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…idotdev/usehooks@2.4.1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment on lines +101 to +113
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
e.preventDefault();
setIsOpen((prev) => !prev);
}
if (e.key === "Escape") {
setIsOpen(false);
}
};
window.addEventListener("keydown", handler);
return () => window.removeEventListener("keydown", handler);
}, []);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be neat to either upgrade @uidotdev/usehooks to a verstion that has useEventListener or use https://tanstack.com/hotkeys/latest (never tried it but tanstack is usually legit)

but for now, a useEffect is the best i've got

"react": "19.2.3",
"react-dom": "19.2.3",
"react-hook-form": "7.65.0",
"react-instantsearch": "^7.26.0",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

hitComponent={({ hit }) => (
<SearchHit hit={hit as unknown as HitRecord} />
)}
/>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hits component always renders alongside empty query message

Medium Severity

The Hits component renders unconditionally, but Algolia returns results for empty queries by default. When the search modal opens, users see the "Start typing to search the docs…" message from EmptyQuery and a list of search results from Hits at the same time. The Hits component needs to be conditionally hidden when no query has been entered.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Contributor Author

@teallarson teallarson Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fine? I like the way this shows top docs pages and also prompts for input when empty. Open to other opinions.

export const config = {
matcher: [
"/((?!api|_next/static|_next/image|favicon.ico|manifest|_pagefind|public|.*.svg|.*.png|.*.jpg|.*.jpeg|.*.gif|.*.webp|.*.ico|.*.css|.*.js|.*.woff|.*.woff2|.*.ttf|.*.eot|.*.otf|.*.pdf|.*.txt|.*.xml|.*.json|.*.py|.*.mp4).*)",
"/((?!api|_next/static|_next/image|favicon.ico|manifest|public|.*.svg|.*.png|.*.jpg|.*.jpeg|.*.gif|.*.webp|.*.ico|.*.webmanifest|.*.css|.*.js|.*.woff|.*.woff2|.*.ttf|.*.eot|.*.otf|.*.pdf|.*.txt|.*.xml|.*.json|.*.py|.*.mp4).*)",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a bunch of 404s locally from this. Idk if I should have just ignored them. This fixed it.

Copy link
Contributor

@evantahler evantahler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

goodbye pagefind!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants