VOOZH about

URL: https://thenewstack.io/how-to-build-site-search-with-astro-qwik-and-fuse-js/

⇱ How to Build Site Search with Astro, Qwik and Fuse.js - The New Stack


TNS
SUBSCRIBE
Join our community of software engineering leaders and aspirational developers. Always stay in-the-know by getting the most important news and exclusive content delivered fresh to your inbox to learn more about at-scale software development.
REQUIRED
It seems that you've previously unsubscribed from our newsletter in the past. Click the button below to open the re-subscribe form in a new tab. When you're done, simply close that tab and continue with this form to complete your subscription.
The New Stack does not sell your information or share it with unaffiliated third parties. By continuing, you agree to our Terms of Use and Privacy Policy.
Welcome and thank you for joining The New Stack community!
Please answer a few simple questions to help us deliver the news and resources you are interested in.
REQUIRED
REQUIRED
REQUIRED
REQUIRED
REQUIRED
Great to meet you!
Tell us a bit about your job so we can cover the topics you find most relevant.
REQUIRED
REQUIRED
REQUIRED
REQUIRED
REQUIRED
Welcome!

We’re so glad you’re here. You can expect all the best TNS content to arrive Monday through Friday to keep you on top of the news and at the top of your game.

What’s next?

Check your inbox for a confirmation email where you can adjust your preferences and even join additional groups.

Follow TNS on your favorite social media networks.

Become a TNS follower on LinkedIn.

Check out the latest featured and trending stories while you wait for your first TNS newsletter.

PREV
1 of 2
NEXT
VOXPOP
As a JavaScript developer, what non-React tools do you use most often?
Angular
0%
Astro
0%
Svelte
0%
Vue.js
0%
Other
0%
I only use React
0%
I don't use JavaScript
0%
Thanks for your opinion! Subscribe below to get the final results, published exclusively in our TNS Update newsletter:
NEW! Try Stackie AI
From clobbered drafts to real-time sync
Apr 14th 2026 10:00am, by David Moore
TypeScript 6.0 RC arrives as a bridge to a faster future
Mar 14th 2026 9:00am, by Darryl K. Taft
Mastra empowers web devs to build AI agents in TypeScript
Jan 28th 2026 11:00am, by Loraine Lawson
2024-02-29 10:03:40
How to Build Site Search with Astro, Qwik and Fuse.js
Frontend Development / JavaScript

How to Build Site Search with Astro, Qwik and Fuse.js

How to build a site search using Astro’s content collections, static endpoints and Qwik’s Astro integration with Fuse.js.
Feb 29th, 2024 10:03am by Paul Scanlon
👁 Featued image for: How to Build Site Search with Astro, Qwik and Fuse.js
Original photo by Emiliano Vittoriosi on Unsplash.
In this post, I’ll explain how to build a site search using Astro’s content collections, static endpoints and Qwik’s Astro integration with Fuse.js. I’ve prepared a demo site and open source repo, which you’ll find at the following links: 👁 Screenshot of demo site search

What Are Content Collections?

Astro has a convenient way to “bulk” query or transform content of similar types. In the case of my demo, this would apply to blog posts that are all written in MDX. All blog posts share the same template or layout and schema. Here’s the schema for blog posts.
// src/content/config.js

import { z, defineCollection } from 'astro:content';

export const collections = {
 posts: defineCollection({
 type: 'content',
 schema: z.object({
 draft: z.boolean().optional(),
 audioFeedId: z.string().optional(),
 base: z.string(),
 title: z.string(),
 tags: z.array(z.string()).optional(),
 date: z.date(),
 author: z.string(),
 featuredImage: z.string(),
 }),
 }),
};
You can see the src in the repo here: src/content/config.js. And for good measure, here’s the frontmatter for one of my blog posts (but all blog posts will use the same schema).
// src/content/posts/2024/02/the-qwik-astro-audiofeed-experiment.mdx

---
base: posts
title: The Qwik, Astro, Audiofeed Experiment
tags: [Qwik, Astro, Audiofeed, AI]
date: 2024-02-06
author: Paul Scanlon
featuredImage: https://res.cloudinary.com/www-paulie-dev/image/upload/v1707261626/paulie.dev/2024/02/get-started-with-qwik-astro_qtxmyq.jpg
---
You can see the src in the repo here: the-qwik-astro-audiofeed-experiment.mdx.

How to Query Astro’s Content Collections

To build site search functionality, I first need to query all the blog posts. I’ve achieved this using a static endpoint. I called it all-content.json.js and it lives in the src/pages directory. E.g.:
// src/pages/all-content.json.js

import { getCollection } from 'astro:content';

export const GET = async () => {
 const posts = await getCollection('posts');

 const search = posts
 .filter((item) => item.data.draft !== true)
 .map((data) => {
 const {
 slug,
 data: { base, title, date },
 } = data;

 return {
 date: date,
 title: title,
 base: base,
 path: `/${base}/${slug}`,
 };
 })
 .sort((a, b) => b.date - a.date);

 return new Response(JSON.stringify({ search }));
};

Once I’ve queried all the blog posts using getCollection('posts'), I do a quick filter to remove any blog posts that might be in draft mode, then return just the fields from the frontmatter that will be helpful for the search, and then sort them by date. The result is stringified and returned as a standard Response. Here’s what the result looks like.
[
 {
 date: 2024-02-22T00:00:00.000Z,
 title: 'How to Build a Survey With KwesForms and Astro',
 base: 'posts',
 path: '/posts/2024/02/how-to-build-a-survey-with-kwesforms-and-astro'
 },
 {
 date: 2024-02-06T00:00:00.000Z,
 title: 'The Qwik, Astro, Audiofeed Experiment',
 base: 'posts',
 path: '/posts/2024/02/the-qwik-astro-audiofeed-experiment'
 }
 ...
]
You can see the src in the repo here: src/pages/all-content.json.js. This data provides everything I’ll need to start building the search component.

How to Query a Static Endpoint

In order to build the search component (coming next!) I first need to query the data from the static endpoint and pass it on to the search component. I query the data in my layout component, which is present in each page of my demo site, E.g.:
// src/pages/index.astro

---
import Layout from '../layouts/layout.astro';
---

<Layout>
 <h1>Lorem ipsum</h1>
 <p>...</p>
</Layout>
You can see the src in the repo here: src/pages/index.astro. And here’s the layout component which makes a server-side request to the endpoint.
// src/layouts/layout.astro

---
import Search from '../components/search';

const content = await fetch(`${import.meta.env.PROD ? 'https://tns-astro-site-search.netlify.app' : 'http://localhost:4321'}/all-content.json`);
const { search } = await content.json();
---

<html lang='en'>
 <head>...</head>
 <body>
 <header>
 <Search data={search} />
 </header>
 <main>
 <slot />
 </main>
 </body>
</html>
One thing to point out here is the URL used in the fetch. If the site is deployed and PROD is true, the URL to the static endpoint will be https://tns-astro-site-search.netlify.app/all-content.json, and while in development the localhost URL is used. Provided I am able to query the search data, I can pass it on to my search component via the data prop. You can see the src in the repo here: src/layouts/layout.astro.

Building the Search Component

There are two additional dependencies to install in order to build the search component. They are as follows.
npm install fuse.js @qwikdev/astro

Fuse.js

I’ve used Fuse.js to help with the “fuzzy search.” Keyboard strokes are captured and passed through Fuse.js. If any of the letters or words match a title or date, Fuse.js will return the item.

Qwik

I use Qwik’s Astro integration to help manage client-side state. Qwik is more lightweight than React and is less verbose than vanilla JavaScript. The remaining steps will cover how to set up the search and filtering. I’ve created a simple example, which you can preview here: https://tns-astro-site-search.netlify.app/simple. The src can be found here: src/components/simple-search.jsx. Note: The example used in my demo contains a lot of additional CSS and JavaScript to handle the modal, which isn’t required to create search functionality.

Search Component: Step 1

The first step is to create the search component and return an HTML input. Add an onInput$ event handler and create a function named handleInput to capture the keystrokes.
// src/components/simple-search.jsx

import { component$, $ } from '@builder.io/qwik';

const Search = component$(({ data }) => {
 const handleInput = $(async (event) => {
 const {
 target: { value },
 } = event;

 });

 return (
 <div>
 <input type='text' placeholder='Search' onInput$={handleInput} />
 </div>
 );
});

export default Search;

Search Component: Step 2

Next import useSignal, and create two new constants to hold the values for all the data and the filtered data.
// src/components/simple-search.jsx

- import { component$, $ } from '@builder.io/qwik';
+ import { component$, $, useSignal } from '@builder.io/qwik';

const Search = component$(({ data }) => {
+ const all = useSignal(data);
+ const filtered = useSignal(data);

 const handleInput = $(async (event) => {
 const {
 target: { value },
 } = event;
 });

 return (
 <div>
 <input type='text' placeholder='Search' onInput$={handleInput} />
 </div>
 );
});

export default Search;

Search Component: Step 3

Next import and initialise Fuse.js. The config for Fuse.js accepts the value from the useSignal const (all.value) and will apply a fuzzy filter threshold of 0.5 when any input values match values for the title or date. fuse.search can be used to filter out any items from the array that don’t meet the config parameters, and a new array is returned. I’ve called this new array “results.”
// src/components/simple-search.jsx

import { component$, $, useSignal } from '@builder.io/qwik';

const Search = component$(({ data }) => {
 const all = useSignal(data);
 const filtered = useSignal(data);

 const handleInput = $(async (event) => {
 const {
 target: { value },
 } = event;

+ const FuseModule = await import('fuse.js');
+ const Fuse = FuseModule.default;

+ const fuse = new Fuse(all.value, {
+ threshold: 0.5,
+ keys: ['title', 'date'],
+ });

+ const results = fuse.search(value).map((data) => {
+ const { item: { base, path, title, date } } = data;

+ return {
+ title,
+ date,
+ path,
+ base,
+ };
 });

 });

 return (
 <div>
 <input type='text' placeholder='Search' onInput$={handleInput} />
 </div>
 );
});

export default Search;

Search Component: Step 4

The next step is to add an if statement. If there’s a value captured from the HTML input, then I set useSignal filtered.value equal to the results, and if there’s no value captured from the HTML input then I set the useSignal filtered.value equal to the all.value. This will either return a filtered list, or the whole list.
// src/components/simple.search.jsx

import { component$, $, useSignal } from '@builder.io/qwik';

const Search = component$(({ data }) => {
 const all = useSignal(data);
 const filtered = useSignal(data);

 const handleInput = $(async (event) => {
 ...

+ if (value) {
+ filtered.value = results;
+ } else {
+ filtered.value = all.value;
+ }

 });

 return (
 <div>
 <input type='text' placeholder='Search' onInput$={handleInput} />
 </div>
 );
});

export default Search;

Search Component: Step 5

The final step is to iterate over the filtered.value (if it has length) and return a list of items. If there are no results, then I return null.
// src/components/simple-search.jsx

import { component$, $, useSignal } from '@builder.io/qwik';

const Search = component$(({ data }) => {
 const all = useSignal(data);
 const filtered = useSignal(data);

 const handleInput = $(async (event) => {
 ...
 });

 return (
 <div>
 <input type='text' placeholder='Search' onInput$={handleInput} />
+ <ul>
+ {filtered.value.length > 0
+ ? filtered.value.map((data, index) => {
+ const { path, title } = data;
+ return (
+ <li key={index}>
+ <a href={path}>{title}</a>
+ </li>
+ );
+ })
+ : null}
+ </ul>
 </div>
 );
});

export default Search;

Finished

And that’s it, that’s all the principles behind how to query data using Astro’s content collections, how to make the data available using a static endpoint, and then implement fuzzy-search using Fuse.js and Qwik’s Astro integration to manage the client-side state. I’ve used this same approach on my site, and it’s working out pretty well so far!
TRENDING STORIES
Paul is a Senior Software Engineer, Independent Developer Advocate and Technical Writer. More from Paul can be found on his site, paulie.dev.
Read more from Paul Scanlon
SHARE THIS STORY
TRENDING STORIES
SHARE THIS STORY
TRENDING STORIES
TNS DAILY NEWSLETTER Receive a free roundup of the most recent TNS articles in your inbox each day.
The New Stack does not sell your information or share it with unaffiliated third parties. By continuing, you agree to our Terms of Use and Privacy Policy.