Setup Search Functionality to a Gatsby with Algolia, React Hook Form, Zustand, and ShadcnUI
Add to your RSS feed8 September 202420 min readTable of Contents
Implementing a powerful, interactive search functionality on your Gatsby blog can dramatically improve user experience. In this guide, we will set up search using Algolia, integrate form handling with React Hook Form, hashing with zustand, and style everything with ShadcnUI to give your site a polished look.
Here is what we want to do:
- Create an empty Gatsby website with some mdx content
- Setup algolia search and receive API keys from it
- Add shadcn UI and create beautiful search panel and trigger
- Add React Hook Form
Let's begin implementing these features step by step.
Prepare you Gatsby website
1. Setup Gatsby
gatsby new1 What would you like to call your site?2 √ · gatsby-algolia3 What would you like to name the folder where your site will be created?4 √ Gatsby/ gatsby-algolia5 √ Will you be using JavaScript or TypeScript?6 · TypeScript7 √ Will you be using a CMS?8 · No (or I\'ll add it later)9 √ Would you like to install a styling system?10 · Tailwind CSS11 √ Would you like to install additional features with other plugins?12 · Add responsive images13 · Generate a manifest file
2. Init ShadcnUI and TailwindCSS
Add this code to your tsconfig file
1 {2 "compilerOptions": {3 // ...4 "baseUrl": ".",5 "paths": {6 "@/*": [7 "./src/*"8 ]9 }10 // ...11 }12 }
Don't forget to add a comma at the end. You can also follow this instructions by shadcn
Create gatsby-node.ts file:
1 import * as path from 'path';23 export const onCreateWebpackConfig = ({ actions }) => {4 actions.setWebpackConfig({5 resolve: {6 alias: {7 '@/components': path.resolve(__dirname, 'src/components'),8 '@/lib/utils': path.resolve(__dirname, 'src/lib/utils'),9 },10 },11 });12 };
Now run
npx shadcn@latest initAnswer the questions
1 Would you like to use TypeScript (recommended)? no / yes2 Which style would you like to use? › Default3 Which color would you like to use as base color? › Slate4 Where is your global CSS file? › › ./src/styles/globals.css5 Do you want to use CSS variables for colors? › no / yes6 Where is your tailwind.config.js located? › tailwind.config.js7 Configure the import alias for components: › @/components8 Configure the import alias for utils: › @/lib/utils9 Are you using React Server Components? › no
That's it, now let's test it out. First add button
npx shadcn@latest add buttonRemove everything from index.ts file inside the pages folder. Than add shadcn button to your index page for testing
1 import * as React from 'react';2 import type { HeadFC, PageProps } from 'gatsby';3 import { Button } from '../components/ui/button';45 const IndexPage: React.FC<PageProps> = () => {6 return (7 <main>8 <Button>Test</Button>9 </main>10 );11 };1213 export default IndexPage;1415 export const Head: HeadFC = () => <title>Home Page</title>;
Run
npm run developYou must to see now test shadcn button.
3. Add some content to your site
Create content folder in a root of gatsby and file what-is-algolia.mdx
Here is the content, thanks to ChatGPT.
1 ---2 title: What is Algolia?3 slug: algolia4 permalink: algolia5 date: 2024-09-066 author: algolia gatsby7 category: gatsby8 type: post9 tags: ['algolia']10 desc:11 Algolia is a powerful search-as-a-service platform that allows developers to easily add fast and12 relevant search functionality to websites and applications.13 ---1415 In the digital age, delivering fast, relevant, and user-friendly search experiences has become16 crucial for businesses, especially those with large volumes of content, products, or user-generated17 data. Algolia, a leading search-as-a-service platform, addresses this need by providing a powerful,18 customizable, and scalable solution that enables developers to implement real-time search19 functionality with ease. In this article, we'll explore the key features of Algolia, how it works,20 its benefits, and how it compares to other search engines.2122 ## What is Algolia?2324 Algolia is a hosted search engine that allows developers to build and deploy fast, accurate search25 experiences into their websites, apps, or e-commerce platforms. Unlike traditional search engines,26 Algolia focuses on delivering search results with lightning speed and high relevancy. It's known for27 its ability to handle typo-tolerance, instant search, and powerful ranking algorithms that can28 prioritize the most relevant results.2930 With Algolia, developers don’t need to spend time building complex search algorithms from scratch.31 Instead, they can leverage Algolia’s pre-built APIs to integrate advanced search functionality into32 their applications in just a few steps.3334 ### Key Features of Algolia3536 1. **Instant Search** Algolia’s flagship feature is instant search, which delivers results as users37 type. This provides a fast and seamless experience, especially useful in scenarios like38 e-commerce where customers expect quick access to relevant products or categories.39 2. **Real-time Indexing** With Algolia, any updates to your content are reflected instantly in40 search results. Whether it's adding a new product, modifying an article, or deleting old data,41 Algolia's indexing is performed in real-time, ensuring users always get up-to-date information.42 3. **Typo-Tolerance** Algolia is designed to handle user input errors gracefully. It accounts for43 common typos, spelling mistakes, and other variations without sacrificing the relevancy of search44 results. For instance, if a user types "iphon" instead of "iPhone," Algolia still provides45 relevant results.46 4. **Custom Ranking** and Relevance Algolia allows developers to customize the ranking of search47 results based on attributes such as popularity, user behavior, or custom business logic. This is48 especially beneficial for e-commerce platforms that want to prioritize in-stock products or49 high-margin items.50 5. **Faceted Search** and Filtering Faceted search enables users to filter results by categories,51 tags, or other attributes, improving their ability to find exactly what they’re looking for. This52 feature is critical in content-rich websites, blogs, or e-commerce stores with hundreds of53 products.54 6. **Geo-Search** For location-based applications, Algolia offers geo-search capabilities that allow55 users to search for results near their physical location. This feature is commonly used in local56 directories, restaurant finders, and real estate platforms.57 7. **Search Analytics** Algolia provides detailed analytics on user search behavior, showing metrics58 like top queries, no-result queries, and conversion rates. These insights can help businesses59 optimize search relevancy and boost engagement.60 8. **Multi-language Support** Algolia supports multiple languages, making it a good choice for61 global companies. The platform offers language-specific tools and optimizations to ensure that62 search experiences are tailored to users’ native tongues.6364 ### How Does Algolia Work?6566 At the heart of Algolia’s service is its index, a data structure optimized for fast searches. Here’s67 a breakdown of how Algolia works:6869 1. **Indexing Your Data** Before users can search, the data needs to be indexed. Algolia allows70 developers to upload their data in various formats (like JSON) and store it in the Algolia cloud.71 Each item in the dataset is indexed as an object with multiple attributes. This might include72 titles, descriptions, prices, tags, categories, or any other relevant data points.73 2. **Search Query Execution** When a user enters a search query, Algolia matches the input against74 the indexed data using its highly optimized search algorithms. These algorithms consider factors75 like typo tolerance, partial matches, and synonyms to deliver the most relevant results.76 3. **Customization with APIs** Algolia provides easy-to-use APIs for customizing search behavior.77 Developers can fine-tune the ranking formula, implement filters, define sorting orders, and even78 control which attributes should influence the search relevancy. The APIs support popular79 programming languages such as JavaScript, Python, PHP, and Ruby.8081 4. **Ranking and Sorting** Algolia uses a combination of textual relevance and custom business logic82 to rank and sort search results. For example, an e-commerce site might rank products based on83 availability, while a blog might prioritize the most recent articles.8485 5. **Search Results Display** Once the search query is processed, Algolia returns the most relevant86 results almost instantaneously. These results can be displayed on the frontend using various UI87 libraries or custom designs, ensuring they blend seamlessly with your application’s look and88 feel.8990 ## Use Cases for Algolia9192 Algolia's flexibility makes it suitable for various industries and applications. Some of the most93 common use cases include:9495 - **E-commerce Search**: Algolia powers product searches for some of the largest online retailers.96 It ensures customers can find products quickly, even with incomplete or misspelled queries.97 Features like filtering, sorting, and ranking are also easily customizable.98 - **Media and Content Websites**: Content-heavy websites, such as news portals or blogs, use Algolia99 to improve their search functionality. Visitors can quickly find articles, videos, or documents,100 sorted by relevancy or other criteria.101 - **SaaS Applications**: Many SaaS platforms integrate Algolia to offer users fast, searchable data,102 whether it's customer records, project tasks, or shared files.103 - **Mobile Applications**: With SDKs for mobile platforms, Algolia is also a good choice for mobile104 apps that require in-app search functionality.105106 ### Algolia vs. Other Search Engines107108 Algolia is often compared to other search platforms like Elasticsearch and Solr. Here’s how Algolia109 stands out:110111 - **Speed**: Algolia is designed for speed. It provides results in milliseconds, even for complex112 searches across large datasets.113 - **Ease of Use**: Algolia's API and developer tools are designed to be straightforward and114 user-friendly. It doesn’t require deep technical expertise to implement, which reduces time to115 market.116 - **Cloud-Based**: Algolia is fully hosted, which means you don’t need to worry about infrastructure117 management or scaling as your data grows.118 - **Relevancy**: Algolia is more focused on delivering relevant results compared to competitors,119 offering typo tolerance, customizable ranking algorithms, and real-time indexing.120121 However, some of the drawbacks include:122123 - **Cost**: Algolia can be more expensive than self-hosted solutions like Elasticsearch, especially124 as your dataset grows.125 - **Customization**: While Algolia provides a lot of customization, some highly specific or advanced126 use cases may still be better suited for an open-source solution where developers can have127 complete control over the search infrastructure.128129 ### Benefits of Using Algolia130131 - **Fast Implementation**: With pre-built APIs and extensive documentation, integrating Algolia into132 an application takes minutes rather than hours or days.133 - **Improved User Experience**: By providing fast, relevant search results, Algolia enhances the134 overall user experience, leading to higher engagement and retention rates.135 - **Scalability**: Algolia’s infrastructure can handle massive datasets and heavy search traffic,136 making it suitable for growing businesses.137 - **Customization**: Developers have full control over how searches are performed and ranked,138 allowing them to tailor the search experience to their specific business needs.139 - **Global Availability**: With multiple data centers across the world, Algolia offers low-latency140 search results regardless of user location.141142 ## Getting Started with Algolia143144 To start using Algolia, follow these steps:145146 1. **Sign Up**: Create an account on the Algolia website.147 2. **Create an Index**: Upload your data to Algolia and index it.148 3. **Integrate the API**: Use Algolia’s API to implement search functionality in your app or149 website.150 4. **Customize**: Fine-tune the search behavior with Algolia’s customization options like ranking,151 sorting, and filtering.152153 ## Conclusion154155 Algolia is a powerful, easy-to-use search-as-a-service platform that enables developers to deliver156 fast, accurate, and relevant search experiences. Its wide range of features, such as real-time157 indexing, typo-tolerance, faceted search, and geo-search, make it an ideal solution for e-commerce,158 content-heavy websites, and mobile apps. While it may come at a cost, the time and resources saved159 through its rapid deployment and scalability make it a worthwhile investment for businesses looking160 to improve their search functionality.
4. Provide the content path to Gatsby
Install a gatsby-source-filesystem plugin, and provide the path to it
npm install gatsby-source-filesystemthen
1 // gatsby-config.ts23 {4 resolve: 'gatsby-source-filesystem',5 options: {6 name: 'pages',7 path: './src/pages/',8 },9 __key: 'pages',10 },11 {12 resolve: `gatsby-source-filesystem`,13 options: {14 name: 'posts',15 path: `./content`,16 },17 },
5. Create a simple post template
Create a file post-template.tsx in templates directory:
1 import * as React from 'react';2 import { graphql, HeadFC, PageProps } from 'gatsby';3 import { MDXProvider } from '@mdx-js/react';45 const PostTemplate: React.FC<PageProps<Queries.GetSinglePostQuery>> = ({6 data: {7 mdx: {8 frontmatter: { title },9 },10 },11 children,12 }) => {13 return (14 <div className='my-16 px-64'>15 <article className='text-lg'>16 <h1 className='tracking-wide text-4xl font-medium space-y-5 my-5'>{title}</h1>17 <MDXProvider> {children}</MDXProvider>18 </article>19 </div>20 );21 };22 export const query = graphql`23 query GetSinglePost($slug: String) {24 mdx(frontmatter: { slug: { eq: $slug } }) {25 frontmatter {26 title27 }28 }29 }30 `;3132 export const Head: HeadFC<Queries.GetSinglePost, unknown> = ({33 data: {34 mdx: {35 frontmatter: { title },36 },37 },38 }) => {39 return (40 <>41 <title>{title}</title>42 </>43 );44 };4546 export default PostTemplate;
6. Setup gatsby-plugin-mdx plugin
npm install gatsby-plugin-mdx @mdx-js/reactAdd this config to the plugins list, inside a gatsby-config file
1 {2 resolve: 'gatsby-plugin-mdx',3 options: {4 extensions: ['.md', '.mdx'],5 },6 },
7. Querying for Content with GraphQL
In Gatsby, querying for content is done using GraphQL, a powerful query language integrated into the framework. It allows you to fetch data from your sources like Markdown files, CMS systems, or other APIs. We need just a slug and contentFilePath to create our pages.
1 // gatsby-node.ts23 export const createPages: GatsbyNode[`createPages`] = async ({4 graphql,5 actions,6 reporter,7 }: CreatePagesArgs) => {8 const { createPage } = actions;9 const postTemplate = path.resolve(`src/templates/post-template.tsx`);1011 const result = await graphql<Queries.GatsbyNodeCreatePagesQuery>(12 `13 query GatsbyNodeCreatePages {14 allMdx {15 nodes {16 frontmatter {17 slug18 }19 internal {20 contentFilePath21 }22 }23 }24 }25 `,26 );2728 if (result.errors) {29 reporter.panicOnBuild('Error loading MDX result', result.errors);30 }3132 const posts = result.data.allMdx.nodes;3334 posts.forEach((node) => {35 createPage({36 path: `/${node.frontmatter.slug}`,37 component: `${postTemplate}?__contentFilePath=${node.internal.contentFilePath}`,38 context: {39 slug: node.frontmatter.slug,40 category: node.frontmatter.category,41 },42 });43 });44 };
Let's test it:
npm run developNow open http://localhost:8000/algolia to see the post.
Install Dependencies
We need to install the zustand, react-hook-form, algoliasearch, gatsby-plugin-algolia, and some Shadcn components:
npm i zustand react-hook-form algoliasearch gatsby-plugin-algolia react-instantsearch
Create an Algolia account
Sign-up on Algolia website.
Copy the Application ID, Search-Only API Key, and Admin API Key from your Algolia dashboard. Then, create a .env file at the root of your project and add these keys for secure access:
1 ALGOLIA_APP_ID = your_application_id;2 ALGOLIA_SEARCH_KEY = your_search_only_key;3 ALGOLIA_ADMIN_KEY = your_admin_key;
This setup will allow your Gatsby project to securely access Algolia's services without exposing sensitive credentials in the codebase.
Create Algolia queries file
Inside a lib directory, add algolia-queries.ts file:
1 import escapeStringRegexp from 'escape-string-regexp';23 const pagesIndexName = `Pages`;45 export const searchIndices = [{ name: pagesIndexName, title: pagesIndexName }];67 const pageQuery = `8 {9 pages: allMdx(filter: {frontmatter: {type: {in: "post"}}}) {10 edges {11 node {12 id13 frontmatter {14 title15 permalink16 }17 excerpt(pruneLength: 5000)18 }19 }20 totalCount21 }22 }23 `;2425 function pageToAlgoliaRecord({ node: { id, frontmatter, ...rest } }) {26 return {27 objectID: id,28 ...frontmatter,29 ...rest,30 };31 }3233 export const algoliaQueries = [34 {35 query: pageQuery,36 transformer: ({ data }) => data.pages.edges.map(pageToAlgoliaRecord),37 indexName: pagesIndexName,38 settings: { attributesToSnippet: [`excerpt:20`] },39 },40 ];
then add gatsby-plugin-algolia to config file:
1 {2 resolve: `gatsby-plugin-algolia`,3 options: {4 appId: 'YOUR_APP_ID',5 apiKey: 'YOUR_API_KEY',6 queries: algoliaQueries,7 enablePartialUpdates: true,8 matchFields: ['slug', 'date'],9 },10 },
Run
npm run buildThis will import the queries we build to algolia search
Now check Algolia dashboard
Create a search store
We will hash all search queries to show the last searches.
Create a folder store in a root directory, and searchStore.ts file
1 import { create } from 'zustand';23 interface SearchTerms {4 searchTerms: Map<number, string>;5 addSearchTerm: (term: string) => void;6 }78 const useSearchStore = create<SearchTerms>((set, get) => ({9 searchTerms: new Map(),10 addSearchTerm: (searchTerm: string) => {11 set((state) => {12 const isSame = Array.from(state.searchTerms.entries()).find(13 ([number, term]) => searchTerm.includes(term) || searchTerm.startsWith(term),14 );15 const isMoveKeys = state.searchTerms.size > 1 && !isSame;1617 const clonedMap = new Map<number, string>();18 if (isMoveKeys) {19 state.searchTerms.forEach((val, key) => {20 if (key !== 3) {21 clonedMap.set(key + 1, val);22 }23 });24 }2526 return {27 searchTerms: isMoveKeys28 ? clonedMap.set(1, searchTerm)29 : isSame30 ? new Map(state.searchTerms).set(state.searchTerms.size, searchTerm)31 : clonedMap.set(1, searchTerm),32 };33 });34 },35 }));3637 export default useSearchStore;
Now, each time a user enters a search query, it will be hashed, and the most recent searches will appear in our form for easy reference.
Create Search Component
Add dialog component from Shadcn
npx shadcn@latest add dialogAdd input component
npx shadcn@latest add inputInstall ScrollArea and VisuallyHidden from @radix-ui
npm install @radix-ui/react-visually-hidden @radix-ui/react-scroll-area
Add a form component (ShadcnUI)
npx shadcn@latest add formCreate a search form (trigger)
Make static search queries, for default search block:
Create a file static-search.queries.ts inside the lib folder:
1 // static-search.queries.ts23 type StaticSearchQuery = {4 name: string;5 href: string;6 };78 export const StaticSearchQueries: StaticSearchQuery[] = [9 {10 name: 'Article #1',11 href: '/1',12 },13 {14 name: 'Article #2',15 href: '/2',16 },17 {18 name: 'Article #3',19 href: '/3',20 },21 ];
Now create a folder search inside a components folder. Create also folders blocks and forms inside a search.
Let's begin from block...create file header-search.block.tsx
1 import * as React from 'react';23 import { Input } from '../../ui/input';4 import { SearchIcon } from 'lucide-react';56 type HeaderSearchBlockProps = {7 setIsOpen: (bool: boolean) => void;8 };910 const HeaderSearchBlock = ({ setIsOpen }: HeaderSearchBlockProps) => {11 return (12 <div13 className='relative'14 onClick={() => {15 console.log('IsOpen', setIsOpen);16 setIsOpen(true);17 }}18 >19 <SearchIcon className='absolute left-2.5 top-2 h-4 w-4 text-muted-foreground' />20 <Input21 type='search'22 placeholder='Search...'23 className='hidden md:inline-block pl-8 h-8 focus-visible:ring-offset-1 focus-visible:ring-1'24 />25 </div>26 );27 };28 export default HeaderSearchBlock;
Now create a search-form.tsx, and search-form.props.tsx inside the forms directory
1 // **search-form.props.tsx**23 import * as React, { DetailedHTMLProps } from 'react';45 export interface SearchFormProps6 extends DetailedHTMLProps<HTMLDivElement<HTMLDivElement, unknown>, HTMLDivElement> {7 query: string;8 refine: (string) => void;9 }
Create also private _components folder inside the forms, with 2 components.
- Default Search - default screen of your search
1 // default-search-form.tsx23 import * as React from 'react';4 import { Link } from 'gatsby';5 import { Activity } from 'lucide-react';67 import useSearchStore from '../../../../store/searchStore';8 import { StaticSearchQueries } from '../../../../lib/queries/search/static-search.queries';910 type DefaultSearchFormProps = {11 refine: (string) => void;12 };1314 const DefaultSearchForm = ({ refine }): DefaultSearchFormProps => {15 const searchTerms = useSearchStore((state) => state.searchTerms);1617 React.useEffect(() => {18 if (!searchTerms) {19 return;20 }21 }, [searchTerms]);2223 return (24 <div className='space-y-4 px-2 py-4'>25 <div>26 <div className='mb-2 px-2 text-xs font-semibold uppercase text-slate-600 dark:text-slate-300'>27 Recent28 </div>29 <ul>30 {searchTerms &&31 Array.from(searchTerms.entries())32 .sort((a, b) => a[0] - b[0])33 .map(([number, term]) => {34 return (35 <li36 key={term}37 onClick={(e) => refine(term)}38 className='group flex items-center rounded px-2 py-1 text-sm leading-6 outline-none focus-within:bg-slate-50 hover:bg-slate-50 dark:focus-within:bg-slate-800 dark:hover:bg-slate-800 text-black dark:text-white cursor-pointer'39 >40 <Activity41 size={12}42 className='fill-slate-400 dark:fill-slate-600 mr-3 group-hover:animate-pulse group-hover:scale-125'43 />44 <span>{term}</span>45 </li>46 );47 })}48 </ul>49 </div>50 <div>51 <div className='mb-2 px-2 text-xs font-semibold uppercase text-slate-600 dark:text-slate-300'>52 Suggestions53 </div>54 <ul>55 {StaticSearchQueries.map((searchQuery) => {56 return (57 <li key={searchQuery.name}>58 <Link59 className='group flex items-center rounded px-2 py-1 text-sm leading-6 outline-none focus-within:bg-slate-50 hover:bg-slate-50 dark:focus-within:bg-slate-800 dark:hover:bg-slate-800 text-black dark:text-white'60 to={searchQuery.href}61 >62 <Activity63 size={12}64 className='fill-slate-400 dark:fill-slate-600 mr-3 group-hover:animate-pulse group-hover:scale-125'65 />66 <span>{searchQuery.name}</span>67 </Link>68 </li>69 );70 })}71 </ul>72 </div>73 </div>74 );75 };76 export default DefaultSearchForm;
And search result component:
search-result.tsx
1 import * as React from 'react';23 import { Link } from 'gatsby';4 import { useStats, Index, Hits, Highlight, Snippet } from 'react-instantsearch';5 import { Activity } from 'lucide-react';67 import { searchIndices } from '../../../../lib/algolia-queries';89 const SearchResult = () => {10 const { nbHits } = useStats();1112 return (13 <div className='space-y-4 px-2 py-4'>14 <div>15 <ul>16 {searchIndices.map(({ name, title }, idx) => (17 <Index indexName={name} key={name} className='py-0'>18 <div className='flex justify-between my-2'>19 <span className='px-2 font-semibold uppercase text-slate-700 dark:text-slate-200 text-lg'>20 {title}21 </span>22 {idx == 0 && nbHits > 0 && (23 <span className='text-slate-600 dark:text-slate-400 flex justify-end text-lg'>24 {nbHits} post{nbHits !== 1 ? 's' : ''} found25 </span>26 )}27 </div>28 <Hits29 hitComponent={({ hit }) => (30 <div>31 <Link32 className='group flex items-center rounded px-2 py-1 text-sm leading-6 outline-none focus-within:bg-slate-50 hover:bg-slate-50 dark:focus-within:bg-slate-800 dark:hover:bg-slate-800 text-black dark:text-white'33 to={`/${hit.permalink}`}34 >35 <Activity36 size={12}37 className='fill-slate-400 dark:fill-slate-600 mr-3 group-hover:animate-pulse group-hover:scale-125'38 />39 <Highlight attribute='title' hit={hit} className='text-xl' />40 </Link>41 <Snippet42 attribute='excerpt'43 hit={hit}44 className='text-slate-600 dark:text-slate-400'45 />46 </div>47 )}48 />49 </Index>50 ))}51 </ul>52 </div>53 </div>54 );55 };56 export default SearchResult;
Now connect those components in search-form.tsx
1 import * as React from 'react';2 import { Form, useForm } from 'react-hook-form';3 import * as VisuallyHidden from '@radix-ui/react-visually-hidden';4 import * as ScrollArea from '@radix-ui/react-scroll-area';56 import { SearchFormProps } from './search-form.props';7 import SearchResult from './_components/search-result';8 import DefaultSearchForm from './_components/default-search-form';9 import useSearchStore from '../../../store/searchStore';10 import { Input } from '@/components/ui/input';11 import {12 FormControl,13 FormDescription,14 FormField,15 FormItem,16 FormLabel,17 FormMessage,18 } from '@/components/ui/form';19 import { SearchIcon } from 'lucide-react';20 import { useSearchBox } from 'react-instantsearch';2122 type SearchInput = {23 searchQuery: string;24 };2526 const SearchForm = () => {27 const { query, refine } = useSearchBox();28 const searchTerms = useSearchStore((state) => state.searchTerms);29 const addSearchTerm = useSearchStore((state) => state.addSearchTerm);30 const { register, handleSubmit } = useForm<SearchInput>({31 defaultValues: {32 searchQuery: '',33 },34 mode: 'onChange',35 });3637 const onChange = ({ searchQuery = '' }: SearchInput) => {38 refine(searchQuery);39 const isSame = Array.from(searchTerms.entries()).find(40 ([number, term]) => term.includes(searchQuery) || term.startsWith(searchQuery),41 );42 if (!isSame && searchQuery?.length > 4) {43 addSearchTerm(searchQuery);44 }45 };4647 return (48 <>49 <form onChange={handleSubmit(onChange)} className='flex items-center relative'>50 <VisuallyHidden.Root>51 <label htmlFor='search-modal'>Search</label>52 </VisuallyHidden.Root>53 <SearchIcon className='absolute left-2.5 top-2 h-4 w-4 text-muted-foreground' />54 <Input55 {...register('searchQuery')}56 placeholder='search'57 className='pl-8 h-8 mr-8 focus-visible:ring-offset-1 focus-visible:ring-1'58 type='search'59 placeholder='Search'60 aria-label='Search'61 defaultValue={query}62 />63 </form>64 <ScrollArea.Root className='max-h-[calc(85vh-44px)]'>65 <ScrollArea.Viewport className='h-full w-full'>66 {query && query.length > 1 ? <SearchResult /> : <DefaultSearchForm refine={refine} />}67 </ScrollArea.Viewport>68 <ScrollArea.Scrollbar69 className='flex h-full w-2 touch-none select-none border-l border-l-transparent p-[1px] transition-colors'70 orientation='vertical'71 >72 <ScrollArea.Thumb className='relative flex-1 rounded-full bg-slate-300' />73 </ScrollArea.Scrollbar>74 <ScrollArea.Scrollbar75 className='flex h-2.5 touch-none select-none flex-col border-t border-t-transparent p-[1px] transition-colors'76 orientation='horizontal'77 >78 <ScrollArea.Thumb className='relative flex-1 rounded-full bg-slate-300' />79 </ScrollArea.Scrollbar>80 <ScrollArea.Corner className='bg-blackA5' />81 </ScrollArea.Root>82 </>83 );84 };85 export default SearchForm;
Last one step - create a index in a search dir:
1 // index.tsx23 import * as React from 'react';4 import { useState, useEffect } from 'react';5 import { Link } from 'gatsby';67 import { InstantSearch, useSearchBox } from 'react-instantsearch';8 import * as VisuallyHidden from '@radix-ui/react-visually-hidden';9 import { Search as SearchIcon } from 'lucide-react';1011 import { Input } from '../ui/input';12 import HeaderSearchBlock from './blocks/header-search.block';13 import SearchForm from './forms/search-form';14 import {15 Dialog,16 DialogClose,17 DialogContent,18 DialogDescription,19 DialogOverlay,20 DialogPortal,21 DialogTitle,22 DialogTrigger,23 } from '../ui/dialog';24 import { Button } from '../ui/button';25 import { algoliasearch } from 'algoliasearch';26 import { searchIndices } from '../../lib/algolia-queries';2728 type SearchProps = {29 isOpen: boolean;30 setIsOpen: (value: boolean) => void;31 };3233 const Search = ({ isOpen, setIsOpen }: SearchProps) => {34 const searchClient = React.useMemo(() => algoliasearch(`ALGOLIA_APP_ID`, `ALGOLIA_API_KEY`), []);3536 useEffect(() => {37 const handleKeyDown = (event: KeyboardEvent) => {38 if (event.key === '/' && !isOpen) {39 event.preventDefault();40 setIsOpen(true);41 }42 if (event.metaKey && event.key === 'k') {43 setIsOpen(true);44 }45 };4647 window.addEventListener('keydown', handleKeyDown);4849 return () => {50 window.removeEventListener('keydown', handleKeyDown);51 };52 }, [isOpen]);5354 return (55 <Dialog open={isOpen} onOpenChange={setIsOpen}>56 <DialogTrigger className='relative'>57 <SearchIcon className='absolute left-2.5 top-2 h-4 w-4 text-muted-foreground' />58 <Input59 type='search'60 placeholder='Search...'61 className='pl-8 h-8 focus-visible:ring-offset-1 focus-visible:ring-1'62 />63 </DialogTrigger>64 <DialogPortal>65 <DialogOverlay className='data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/20 dark:bg-black/70' />66 <DialogContent className='data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed left-[50%] top-[50%] z-50 grid max-h-[85vh] w-[90vw] max-w-3xl translate-x-[-50%] translate-y-[-50%] overflow-hidden border bg-slate-100 dark:bg-slate-700 shadow-lg duration-300 sm:rounded'>67 <VisuallyHidden.Root>68 <DialogTitle>Search</DialogTitle>69 <DialogDescription>Start typing to search the documentation</DialogDescription>70 </VisuallyHidden.Root>71 <React.Suspense72 fallback={<div class='h-6 w-6 animate-spin rounded-full border-b-2 border-current' />}73 >74 <InstantSearch searchClient={searchClient} indexName={searchIndices[0].name}>75 <SearchForm />76 </InstantSearch>77 </React.Suspense>78 </DialogContent>79 </DialogPortal>80 </Dialog>81 );82 };8384 export default Search;
Connect the search component to index page
Remove button and put search component
1 // index.tsx23 import * as React from 'react';4 import type { HeadFC, PageProps } from 'gatsby';5 import { Button } from '../components/ui/button';6 import Search from '../components/search';78 const IndexPage: React.FC<PageProps> = () => {9 return (10 <main>11 <Search />12 </main>13 );14 };1516 export default IndexPage;1718 export const Head: HeadFC = () => <title>Home Page</title>;
Try to search algolia
Conclusion
By combining Algolia's robust search capabilities, React Hook Form's form management, and ShadcnUI's elegant styling, you can easily implement a powerful, responsive, and user-friendly search experience for your Gatsby site. This setup offers great scalability and performance, ensuring users can quickly and efficiently find content.