Home

🔍 Bringing Search to Life: My Gatsby Blog Transformation! ✨

🔍 Bringing Search to Life: My Gatsby Blog Transformation! ✨

Hey everyone! 👋 Ever felt like your blog was missing that one crucial feature that makes it super user-friendly? For me, it was a search bar! After tinkering around, I successfully integrated a dynamic search functionality into my Gatsby blog, and I’m thrilled to share the journey with you.

This post will walk you through how I added a powerful search feature, powered by Fuse.js, right into my blog’s sidebar. Let’s dive in! 🚀

The Goal: A Seamless Search Experience 🎯

My main objective was to allow visitors to quickly find posts by typing keywords, without having to navigate through endless pagination. I wanted something fast, efficient, and well-integrated into the existing design. The sidebar seemed like the perfect spot!

The Core Ingredients 🧑‍🍳

Here’s a peek at the main files involved and how they played their part:

  • Search.tsx: This is the brain of the operation! It handles the search input, debouncing (so we don’t search on every single keystroke), and displaying results. It leverages Fuse.js for fuzzy searching.
  • Sidebar.tsx: My blog’s sidebar component. This is where the Search component would visually live.
  • IndexTemplate.tsx: The main template for my blog’s home page. This file is crucial for fetching all the necessary post data via GraphQL and passing it down to the Sidebar (and subsequently, to Search).
  • cleanUrl.ts (or .js): A small but mighty utility file to fix a pesky URL issue I encountered.

Step-by-Step Implementation 🪜

1. Crafting the Search Component (Search.tsx) 🧠

The Search.tsx component is a React functional component that manages the search logic.

  • State Management: I used useState for the query (what the user types), debouncedQuery (a delayed version of the query to prevent excessive searches), and results (the array of found posts).

  • Fuse.js Integration: Fuse.js is fantastic for fuzzy searching. I initialized it within a useMemo hook to ensure the search index is only rebuilt when the posts data changes, optimizing performance. I configured it to search frontmatter.title and excerpt.

    // Search.tsx snippet
    const fuse = useMemo(() => {
      return new Fuse(posts, {
        keys: ["frontmatter.title", "excerpt"],
        includeScore: true,
        threshold: 0.4,
      });
    }, [posts]);
  • Debouncing the Query: A useEffect hook handles debouncing the search query. This is super important for performance, as it delays the search operation until the user pauses typing.

    // Search.tsx snippet
    useEffect(() => {
      const timerId = setTimeout(() => {
        setDebouncedQuery(query);
      }, 300); // 300ms delay
      return () => { clearTimeout(timerId); };
    }, [query]);
  • Performing the Search: Another useEffect triggers the actual search when debouncedQuery changes. It filters results to show only after at least 2 characters are typed.

  • Displaying Results: Search results are rendered as a list of Gatsby Link components, showing the post title and a snippet of its excerpt.

2. Empowering the Sidebar (Sidebar.tsx) 🤝

To make the search bar visible, it needed a home. The Sidebar.tsx was the natural choice.

  • Prop Drilling: The Sidebar component now accepts a posts prop. This posts array, containing all blog entries, is then passed directly to the Search component.

  • Rendering Search: I simply imported Search and placed it within the sidebar’s JSX:

    // Sidebar.tsx snippet
    import Search from "../components/Search/Search";
    // ...
    const Sidebar = ({ isIndex, posts }: Props) => {
      // ...
      return (
        <div className={styles.sidebar}>
          <div className={styles.inner}>
            {/* ... other sidebar components */}
            <Search posts={posts} /> {/* Here's our search bar! */}
            {/* ... other sidebar components */}
          </div>
        </div>
      );
    };

3. Fetching All Posts in IndexTemplate.tsx 📦

This was a critical step to ensure the Search component had all the data it needed to perform its magic.

  • GraphQL Query for All Posts: I added a new GraphQL query alias, allPosts, to fetch all markdown remarks that are posts and not drafts. This query fetches slug, title, date, description, and excerpt.

    // IndexTemplate.tsx snippet
    export const query = graphql`
      query IndexTemplate($limit: Int!, $offset: Int!) {
        // ... existing allMarkdownRemark query for pagination
        allPosts: allMarkdownRemark(
          filter: { frontmatter: { template: { eq: "post" }, draft: { ne: true } } }
        ) {
          nodes {
            fields { slug }
            frontmatter { title date description }
            excerpt
          }
        }
      }
    `;
  • Passing Data: The nodes from data.allPosts are then extracted and passed as the posts prop to the Sidebar.

    // IndexTemplate.tsx snippet
    const allPosts = data.allPosts.nodes;
    return (
      <Layout>
        <Sidebar isIndex posts={allPosts} /> {/* Passing posts to Sidebar */}
        {/* ... */}
      </Layout>
    );

4. The URL Cleanup Utility (cleanUrl.ts) 🧹

During testing, I noticed a peculiar bug where clicking a search result would lead to URLs like http://localhost:8000/posts//posts/AI-Journey-2024/ – a duplicated /posts/ segment! 🤦‍♀️

To fix this, I created a small utility function cleanDuplicatePostsPath that uses a regular expression to find and replace the redundant "/posts//posts/" with a single "/posts/".

// src/utils/cleanUrl.ts
export function cleanDuplicatePostsPath(urlPath: string): string {
  const cleanedPath = urlPath.replace(/\/posts\/\/posts\//g, '/posts/');
  return cleanedPath;
}

This function is then imported into Search.tsx and applied to the post.fields.slug before it’s passed to the Link component.

// Search.tsx snippet using cleanUrl.ts
import { cleanDuplicatePostsPath } from '../utils/cleanUrl'; // Adjust path
// ...
const cleanedSlug = cleanDuplicatePostsPath(post.fields.slug);
<Link to={cleanedSlug}>

The Result! 🎉

And just like that, my Gatsby blog now has a fully functional, sleek search bar right in the sidebar! It’s a game-changer for navigation and finding specific content.

If you’re looking to add a search feature to your Gatsby blog, I highly recommend this approach. It’s robust, efficient, and provides a much better user experience.

Happy blogging! 💻✨


Published Jun 4, 2025