Skip to content

Add Pagefind searchbar to apps/web header#2075

Merged
yujonglee merged 3 commits intomainfrom
devin/1764684010-pagefind-searchbar
Dec 3, 2025
Merged

Add Pagefind searchbar to apps/web header#2075
yujonglee merged 3 commits intomainfrom
devin/1764684010-pagefind-searchbar

Conversation

@yujonglee
Copy link
Contributor

@yujonglee yujonglee commented Dec 2, 2025

Add Pagefind searchbar to apps/web header

Summary

Integrates Pagefind search into the web app header using vite-plugin-pagefind. The search component is placed in the header navigation area as a temporary location for now (styling/positioning to be refined later per user request).

Changes:

  • Added pagefind and vite-plugin-pagefind dependencies
  • Configured pagefind plugin in vite.config.ts to index dist/client output
  • Updated build script to run pagefind indexing after vite build
  • Created Search component that dynamically loads Pagefind UI (both JS and CSS at runtime)
  • Added Search component to header (desktop nav only)
  • Added cleanup function to destroy PagefindUI instance on unmount

Updates since last revision

Fixed CI build failure (ENOENT: no such file or directory, open '/pagefind/pagefind-ui.css'):

  • Moved CSS loading from static @import in styles.css to dynamic <link> injection in the Search component
  • This avoids PostCSS trying to resolve the pagefind CSS during build (before pagefind generates it)
  • Added proper cleanup function with destroy() call for the PagefindUI instance

Review & Testing Checklist for Human

  • Run a production build (pnpm -F @hypr/web build) and verify pagefind generates an index in dist/client/pagefind/
  • Test search functionality after build - search should return results from docs/blog content
  • Check search UI appearance in header - verify CSS loads correctly and styling looks right (no flash of unstyled content)
  • Verify dev mode doesn't throw errors (search won't work in dev, but should fail gracefully with console.debug)

Recommended test plan: Run pnpm -F @hypr/web build && pnpm -F @hypr/web serve, then open the preview and test the search bar in the header.

Notes

  • Search only works after a production build since pagefind indexes the built HTML
  • Both CSS and JS are loaded dynamically at runtime - will 404 during dev mode (expected)
  • User indicated styling/positioning will be refined later

Link to Devin run: https://app.devin.ai/sessions/38abf192afc94b029e7b8fa4173accf5
Requested by: yujonglee (@yujonglee)

- Install vite-plugin-pagefind and pagefind dependencies
- Configure pagefind plugin in vite.config.ts
- Update build script to run pagefind after vite build
- Create Search component using Pagefind UI
- Add Search component to header
- Import Pagefind UI CSS in styles.css

Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@netlify
Copy link

netlify bot commented Dec 2, 2025

Deploy Preview for hyprnote ready!

Name Link
🔨 Latest commit 90e38f9
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote/deploys/692f87a0209259000896b739
😎 Deploy Preview https://deploy-preview-2075--hyprnote.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link

netlify bot commented Dec 2, 2025

Deploy Preview for hyprnote-storybook ready!

Name Link
🔨 Latest commit 90e38f9
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote-storybook/deploys/692f87a073953a0008143acf
😎 Deploy Preview https://deploy-preview-2075--hyprnote-storybook.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 2, 2025

📝 Walkthrough

Walkthrough

The pull request introduces Pagefind static site search functionality to the web application. It adds Pagefind as a production and dev dependency, updates build and typecheck scripts to generate search indexes post-build, creates a new Search component that dynamically loads and initializes Pagefind UI, integrates it into the header, and refactors CI detection in the environment configuration.

Changes

Cohort / File(s) Summary
Search Feature Implementation
apps/web/src/components/search.tsx
New React component that dynamically loads Pagefind UI on mount, injects required CSS, initializes PagefindUI with configuration (showSubResults: true, showImages: false), includes error handling for import failures, and cleans up the instance on unmount.
Header Integration
apps/web/src/components/header.tsx
Added import and rendered the Search component within the header navigation to make the search feature available in the UI.
Build & Dependencies
apps/web/package.json
Added Pagefind (^1.4.0) to both dependencies and devDependencies; updated build script to run pagefind --site ./dist/client after Vite build; prefixed typecheck script with CI=true environment variable.
Environment Configuration
apps/web/src/env.ts
Added isCI constant defined as process.env.CI === "true" and refactored to use it for the skipValidation check, centralizing CI detection logic.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

  • Focus areas:
    • Verify Pagefind dynamic import error handling and CSS injection logic in the Search component
    • Confirm lifecycle hooks and ref cleanup are correctly implemented
    • Validate build script integration and CI environment variable handling

Possibly related PRs

  • PR #2048: Also modifies apps/web/src/env.ts to adjust CI detection and validation handling.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add Pagefind searchbar to apps/web header' directly and clearly summarizes the main change: integrating a Pagefind search component into the web app header. It is concise, specific, and matches the primary objective of the changeset.
Description check ✅ Passed The description is comprehensive and directly related to the changeset. It explains the integration of Pagefind search, lists specific changes (dependencies, configuration, components), documents fixes made, and provides testing guidance for reviewers.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch devin/1764684010-pagefind-searchbar

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/web/src/components/search.tsx (1)

3-26: Consider adding visual loading state.

The component renders an empty div while Pagefind UI loads asynchronously. Users won't see any feedback during the import and initialization, which could feel unresponsive.

Consider adding a loading state:

import { useEffect, useRef, useState } from "react";

export function Search() {
  const containerRef = useRef<HTMLDivElement>(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    let instance: any = null;

    const loadPagefind = async () => {
      if (!containerRef.current) return;

      try {
        const pagefindUI = await import("/pagefind/pagefind-ui.js");
        instance = new pagefindUI.PagefindUI({
          element: containerRef.current,
          showSubResults: true,
          showImages: false,
        });
        setIsLoading(false);
      } catch {
        console.debug("Pagefind UI not available yet");
        setIsLoading(false);
      }
    };

    loadPagefind();

    return () => {
      if (instance && typeof instance.destroy === 'function') {
        instance.destroy();
      }
    };
  }, []);

  return (
    <div className="relative">
      {isLoading && <div className="animate-pulse">Loading search...</div>}
      <div ref={containerRef} />
    </div>
  );
}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 93d9166 and 26ce6da.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • apps/web/package.json (2 hunks)
  • apps/web/src/components/header.tsx (2 hunks)
  • apps/web/src/components/search.tsx (1 hunks)
  • apps/web/src/styles.css (1 hunks)
  • apps/web/vite.config.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Agent implementations should use TypeScript and follow the established architectural patterns defined in the agent framework
Agent communication should use defined message protocols and interfaces

Files:

  • apps/web/vite.config.ts
**/*.config.{ts,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Agent configuration should be centralized and externalized from implementation logic

Files:

  • apps/web/vite.config.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/web/vite.config.ts
  • apps/web/src/components/search.tsx
  • apps/web/src/components/header.tsx
🧠 Learnings (1)
📚 Learning: 2025-11-24T16:32:24.348Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: apps/desktop-e2e/AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:24.348Z
Learning: Applies to apps/desktop-e2e/**/*.{test,spec}.{js,ts,tsx} : Refer to `scripts/setup-desktop-e2e.sh` for end-to-end test environment setup if setup is missing

Applied to files:

  • apps/web/package.json
🧬 Code graph analysis (1)
apps/web/src/components/header.tsx (1)
apps/web/src/components/search.tsx (1)
  • Search (3-26)
🪛 GitHub Actions: web_ci
apps/web/src/styles.css

[error] 1-1: During 'vite build && pagefind --site dist/client' step, PostCSS could not load '/pagefind/pagefind-ui.css' (ENOENT).

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: fmt
  • GitHub Check: Devin
🔇 Additional comments (6)
apps/web/package.json (2)

7-7: LGTM: Build script correctly sequences Vite build and Pagefind indexing.

The build script properly runs vite build first to generate static assets, then pagefind --site dist/client to index the built site for search functionality.


80-84: LGTM: Pagefind dependencies added correctly.

Both pagefind CLI and vite-plugin-pagefind are appropriately added to devDependencies with reasonable version constraints.

apps/web/src/components/header.tsx (1)

11-11: LGTM: Search component cleanly integrated into header.

The Search component is correctly imported and rendered in the desktop navigation. The implementation is straightforward and follows the existing patterns.

Note: The Search component only appears on desktop (within the sm:flex nav). Consider whether mobile users should also have access to search functionality in the mobile menu (lines 239-373).

Also applies to: 180-180

apps/web/src/components/search.tsx (1)

11-11: The dynamic import already has appropriate error handling and gracefully degrades during development.

The code at line 11 includes a try-catch block (lines 10-19) that catches import failures and logs a debug message. Since vite-plugin-pagefind serves pagefind assets through Vite's dev server, the import will fail during development when the pagefind index hasn't been generated yet—but this is expected and handled. The error doesn't crash the application; it simply logs a debug message and renders an empty container. This is the correct pattern for optional features that are only available post-build.

apps/web/vite.config.ts (1)

15-18: The pagefind configuration is correct; consider improving plugin ordering.

The assetsDirectory: "public" and outputDirectory: "dist/client" settings are both correct—they match Vite's standard public directory and your build script target (pagefind --site dist/client). However, placing pagefind as the first plugin is suboptimal per Vite conventions. Since your build script runs indexing via CLI after the build completes (vite build && pagefind --site dist/client), consider either moving the plugin to the end of the array or explicitly setting apply: 'build' to clarify its build-time-only behavior.

apps/web/src/styles.css (1)

2-2: CSS import is correct and does not cause a build failure.

The vite-plugin-pagefind plugin does not automatically inject CSS—CSS must be included manually by the developer. The @import url("/pagefind/pagefind-ui.css") at line 2 is the correct approach. CSS @import url() statements are lazy-loaded at runtime by the browser, not during the Vite build step, so this does not block the build process. Since the build script runs vite build && pagefind --site dist/client, the pagefind-ui.css file will exist at /pagefind/pagefind-ui.css before the application is served. The Search component already handles dynamic loading of pagefind-ui.js with error handling, and the current CSS import pattern aligns with standard Pagefind integration. No changes are needed.

Likely an incorrect or invalid review comment.

- Move CSS import from styles.css to dynamic loading in Search component
- Add cleanup function to destroy PagefindUI instance on unmount
- Fixes build error: ENOENT /pagefind/pagefind-ui.css during vite build

Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
@devin-ai-integration
Copy link
Contributor

Devin is archived and cannot be woken up. Please unarchive Devin if you want to continue using it.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/web/src/components/search.tsx (1)

21-38: Consider adding debug logging and an unmount guard around the async import

Two small improvements to make this more robust and debuggable:

  1. Log failures in dev-friendly way
    Right now the catch {} is silent; adding a console.debug helps explain why search is missing when /pagefind/... 404s (as expected in dev) and matches the PR’s stated behavior.

  2. Guard against work after unmount
    If the component unmounts before the import() resolves, the code can still try to create a PagefindUI instance afterward. It’s harmless today (errors are swallowed), but a simple cancellation flag makes the pattern more future-proof.

For example:

   useEffect(() => {
-    let pagefindInstance: { destroy?: () => void } | null = null;
+    let pagefindInstance: { destroy?: () => void } | null = null;
+    let cancelled = false;

     const loadPagefind = async () => {
-      if (!containerRef.current) return;
+      if (!containerRef.current || cancelled) return;

       const linkId = "pagefind-ui-css";
       if (!document.getElementById(linkId)) {
         const link = document.createElement("link");
         link.id = linkId;
         link.rel = "stylesheet";
         link.href = "/pagefind/pagefind-ui.css";
         document.head.appendChild(link);
       }

       try {
         const pagefindPath = "/pagefind/pagefind-ui.js";
         const pagefindUI = await import(/* @vite-ignore */ pagefindPath);
+        if (!containerRef.current || cancelled) return;
         pagefindInstance = new pagefindUI.PagefindUI({
           element: containerRef.current,
           showSubResults: true,
           showImages: false,
         });
-      } catch {}
+      } catch (error) {
+        console.debug("Pagefind UI not available yet", error);
+      }
     };

     loadPagefind();

     return () => {
+      cancelled = true;
       if (pagefindInstance?.destroy) {
         pagefindInstance.destroy();
       }
     };
   }, []);
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fc4985d and 90e38f9.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • apps/web/package.json (2 hunks)
  • apps/web/src/components/search.tsx (1 hunks)
  • apps/web/src/env.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/package.json
🧰 Additional context used
📓 Path-based instructions (2)
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Agent implementations should use TypeScript and follow the established architectural patterns defined in the agent framework
Agent communication should use defined message protocols and interfaces

Files:

  • apps/web/src/env.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/web/src/env.ts
  • apps/web/src/components/search.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Redirect rules - hyprnote-storybook
  • GitHub Check: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote-storybook
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: ci
  • GitHub Check: fmt
  • GitHub Check: Pages changed - hyprnote-storybook
  • GitHub Check: Pages changed - hyprnote
🔇 Additional comments (2)
apps/web/src/env.ts (1)

4-4: CI flag extraction into isCI keeps behavior while improving clarity

Using a shared isCI constant for skipValidation is straightforward and preserves the previous process.env.CI === "true" semantics while making CI-related checks easier to reuse and reason about.

Also applies to: 38-39

apps/web/src/components/search.tsx (1)

1-42: Search component correctly initializes and cleans up Pagefind UI

The hook setup looks solid: you guard on containerRef.current, inject the CSS only once, dynamically import the UI script, and keep a handle to the PagefindUI instance so destroy() runs on unmount. This also addresses the earlier review note about cleanup in Strict Mode / remount scenarios.

@yujonglee yujonglee merged commit 9034f2a into main Dec 3, 2025
11 of 13 checks passed
@yujonglee yujonglee deleted the devin/1764684010-pagefind-searchbar branch December 3, 2025 00:47
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.

1 participant

Comments