Search is becoming more popular than ever, consumers have become so accustomed to searching for information that providing a search function is really important.
Ghost doesn't come with a search function by default, so adding a search box to your Ghost site can offer the following benefits:
- Boost Conversions: By making it easier for visitors to find what they’re looking for, thus creating a positive user experience.
- Improve SEO: A positive user experience will lead to more time spent on your website, which can help search engines like Google to consider your website to be relevant and high-quality.
- Build Loyalty: By providing an easy way to find relevant information faster, users are more likely to return again.
A good search function is therefore just as important for a professional website as the underlying CMS or design.
We have previously talked about different search options for Ghost, and now we are going into specifics about the Fuse.js library.
Fuse.js library
Fuse.js is a powerful, lightweight fuzzy-search library, with zero dependencies.
The advantage of Fuse.js is that you don’t need to set up a dedicated backend just for the search function. Another advantage is its simplicity and performance.
It's ideal for client-side fuzzy searching of small to moderately large data sets without a dedicated backend, which is the case for most Ghost CMS websites.
Basic Options
Fuse comes with some basic options:
isCaseSensitive
- indicates whether comparisons should be case sensitive.includeScore
- whether the score should be included in the result set.includeMatches
- whether the matches should be included in the result setminMatchCharLength
- only the matches whose length exceeds this value will be returned.shouldSort
- whether to sort the result list, by score.findAllMatches
- when true, the matching function will continue to the end of a search pattern even if a perfect match has already been located in the string.keys
- list of keys that will be searched.
Fuzzy matching options
You can also adjust the fuzzy matching options which will change how the search behaves and how the search score is built:
location
- determines approximately wherein the text is the pattern expected to be found.threshold
- at what point does the matching algorithm give up.distance
- determines how close the match must be to the fuzzy locationignoreLocation
- when true, the search will ignore location and distance, so it won't matter where in the string the pattern appears.
For more details and a complete overview of options check out the official documentation.
Fuse.js in Ghost Themes
If you want to integrate Fuse in your Ghost Theme you can either integrate the
script into the theme workflow or simply add the <script>
tag using a CDN, for example:
<script src="https://cdn.jsdelivr.net/npm/fuse.js/dist/fuse.js"></script>
After this, we should have Fuse.js available globally. For more information check out the official installation guide. Next, we need to feed Fuse with data, meaning we have to fetch all posts that will be the base for the library to perform the search on. For this, you need to create a custom integration, from the integrations screen in Ghost Admin. Copy the API URL and Content API Key as these will be needed to fetch the posts.
Setup Content API
As mentioned before, you need the Ghost Content API to fetch all posts.
If your Ghost theme workflow doesn't include the Content API library you can add
it with a <script>
tag, just like the fuse library.
<script src="https://unpkg.com/@tryghost/content-api@1.5.16/umd/content-api.min.js"></script>
Warning
Important to note that in the above script we are linking to version 1.5.16 of the Content API library (here is the latest version)
Here is the snippet for initializing the Content API (add your actual URL and Key):
const api = new GhostContentAPI({
url: 'your_api_url',
key: 'your_content_api_key',
version: 'v4',
});
Fetch Posts & Setup Fuse.js
Now that we have both the Fuse and the Content API available, we can fetch the posts and set up the search library with the data:
// setup basic options & variable
const fuseOptions = {
keys: [{ name: 'title' }],
};
let fuseSearch = '';
// fetch posts
api.posts
.browse({ limit: 'all', include: 'tags,authors' })
.then((posts) => {
fuseSearch = new Fuse(posts, fuseOptions);
})
.catch((err) => {
console.error(err);
});
In the fuseOptions
keys in this example we will search the post titles, but you
can further extend it to other fields as well.
Fetching the posts, we set the limit to all
and also include tags and authors.
You can additionally specify the fields to be fetched (if you want to avoid
loading all of them).
Finally, the fuseSearch
is initialized passing the posts (data) and the fuseOptions
.
Markup & Search event
As for any search, you need at least an input field, so now we will create a minimal HTML for this to work, this includes an input field and a container for the results:
<label> Search: <input class="js-fuse-search" /> </label>
<div class="js-fuse-results bg-acc-1 p-lg"></div>
We also have to add the event handling for the input field:
// script for handling the search when the input changes
document.querySelector('.js-fuse-search').onkeyup = (e) => {
e.preventDefault();
const results = fuseSearch.search(e.target.value, { limit: 10 });
document.querySelector('.js-fuse-results').innerHTML = '';
results.forEach((result) => {
document.querySelector(
'.js-fuse-results',
).innerHTML += `<h6>${result.item.title}</h6>`;
});
};
The event handler (when a user releases a key) includes triggering the search
fuseSearch.search
which returns a list of posts based on the input value (e.target.value
).
The final step is adding the results to our container.
That's it, the search should be working.
Additional settings & adjustments
Of course, there is much more fine-tuning you could do, here is some to consider:
- adjust the
fuseOptions
if necessary, for example, the keys. You could potentially add other fields to search, for example: tags , and custom_excerpt. - specify the
fields
when fetching posts with the Ghost Content API to avoid loading all fields when it's not necessary - to include other data besides post title adjust the markup, the
result.item
should contain all fields of a post, so you can also show the date, tags, author, or anything else. And of course, you can add CSS to change how it looks.
Complete code & Demo
To make everything easier here is the complete code including all scripts and markup,
you can simply paste this into an HTML card in a post/page and change the your_api_url
and your_content_api_key
with your own integration value.
<!-- Fuse & Content API library -->
<script src="https://unpkg.com/@tryghost/content-api@1.5.16/umd/content-api.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/fuse.js/dist/fuse.js"></script>
<!-- Input field & container for result -->
<label> Search: <input class="js-fuse-search" /> </label>
<div class="js-fuse-results bg-acc-1 p-lg"></div>
<!-- Input Event handling & search -->
<script>
document.querySelector('.js-fuse-search').onkeyup = (e) => {
e.preventDefault();
const results = fuseSearch.search(e.target.value, { limit: 10 });
document.querySelector('.js-fuse-results').innerHTML = '';
results.forEach((result) => {
document.querySelector(
'.js-fuse-results',
).innerHTML += ``;
});
};
</script>
<!-- Initialize Content API & Fuse -->
<script>
const fuseOptions = {
keys: [{ name: 'title' }],
};
let fuseSearch = '';
window.onload = () => {
// setup Content API
const api = new GhostContentAPI({
url: 'your_api_url',
key: 'your_content_api_key',
version: 'v4',
});
// fetch posts
api.posts
.browse({ limit: 'all', include: 'tags,authors' })
.then((posts) => {
// Initialize Fuse
fuseSearch = new Fuse(posts, fuseOptions);
})
.catch((err) => {
console.error(err);
});
};
</script>
And here is a working demo of this setup in the Nikko theme, also the Nikko search is implemented with Fuse including some fine-tuning, to test that, go to the Nikko demo and press the search icon in the header.