Svelte Algolia
Utility for server-side Algolia index updates plus a client-side search component for Svelte apps. Only adds a single dependency:
There are three steps to setting up svelte-algolia
:
npm i -D svelte-algolia
- Setup your server-side index updates.
- Integrate the client-side search component into your site.
2. Server-Side Index Updates
-
Create an algoliaConfig
object:
import 'dotenv/config'
async function loadPokedex() {
const json = await import('pokedex.json')
return json.default.map((el) => ({ ...el, id: el.someUniqAttribute }))
}
const algoliaConfig = {
appId: process.env.VITE_ALGOLIA_APP_ID,
apiKey: process.env.ALGOLIA_ADMIN_KEY,
indices: [
{ name: `Pokedex`, getData: loadPokedex },
{ name: `Hitchhiker's Guide`, getData: guideLoader },
],
settings: {
attributesToHighlight: [`name`],
},
}
The getData
function is expected to return an array of objects containing the data you wish to index (a product catalog, blog posts, documentation pages, pokémons or whatever). Each object in the data array should have a key named id
, _id
or objectID
for Algolia to recognize it and overwrite existing data where appropriate.
The settings object applies to all indices. You can also pass a settings object to each index individually which overrides the general one.
-
Pass your config to indexAlgolia
:
import { indexAlgolia } from 'svelte-algolia/server-side'
indexAlgolia(algoliaConfig)
You can call this function wherever you'd like to update your indices, e.g. in svelte.config.js
or in src/hooks.ts
(as this demo site does). Typically, you would include this in every production build of your app.
Config Options
const defaultConfig = {
verbosity: 1,
partialUpdates: false,
matchFields: [],
settings: {},
}
Auto-update Indices during Builds
To use this package as part of a build process (e.g. in a SvelteKit app), simply call indexAlgolia
in your build config:
if (process.env.NODE_ENV === `production`) {
const { indexAlgolia } = await import(`svelte-algolia/server-side`)
const algoliaConfig = {
}
indexAlgolia(algoliaConfig)
}
3. Client Side UI
<Search />
needs your Algolia app's ID and search key to access its search indices as well as a mapping from index names to corresponding Svelte-component that should render search hits coming from that index. Each hit component receives a hit
object as prop with all attributes stored in the Algolia index.
<script>
import Search from 'svelte-algolia'
import PokemonHit from '../components/PokemonHit.svelte'
const appId = '0OJ5UL9OJX'
const searchKey = '63f563566cdd6de606e2bb0fdc291994'
// in a real app you'd get your credentials like this:
const appId = import.meta.env.VITE_ALGOLIA_APP_ID
const searchKey = import.meta.env.VITE_ALGOLIA_SEARCH_KEY
</script>
<header>
<nav>{...}</nav>
<Search
{appId}
{searchKey}
indices={{ Pokedex: PokemonHit }}
placeholder="Search Pokedex" />
</header>
For example, the PokemonHit.svelte
component on the demo site looks like this:
<script>
export let hit
</script>
<h2>{@html hit.name}</h2>
<div>
<ul>
<li>Type: {@html hit.type.join(`, `)}</li>
<li>Height: {@html hit.height}</li>
<li>Weight: {@html hit.weight}</li>
<li>Weaknesses: {@html hit.weaknesses.join(`, `)}</li>
</ul>
<img src={hit.img} alt={hit.nameOrig} />
</div>
<style>
/* highlights text matching the search string */
:global(em) {
color: darkcyan;
line-height: 1.2em;
border-radius: 3pt;
font-style: normal;
}
div {
display: flex;
justify-content: space-between;
}
</style>
Substrings in attributes matching the current search string will be wrapped in <em>
which need the {@html ...}
tag to be rendered correctly but can then be styled to highlight why a particular hit matches the current search string. The original value (i.e. without <em>
tags) of every highlighted attribute is available as hit.[attr]Orig
. See hit.nameOrig
above.
Props
Full list of props/bindable variables for this component:
-
appId: string
Algolia app ID
-
ariaLabel: string = `Search`
Tells assistive technology how to announce the input element to the user.
-
hasFocus: boolean = false
Bindable boolean indicating whether the text input or results pane currently has focus.
-
indices: | Record<string, typeof SvelteComponent> | [string, typeof SvelteComponent][]
Object mapping the name of each index the Search
component should tap into for finding Search results to the Svelte component that should render those hits.
-
loadingMsg: string = `Searching...`
String to display in the results pane while Search results are being fetched.
-
noResultMsg = (query: string): string => `No results for '${query}'`
Function that returns the string to display when search returned no results.
-
placeholder: string = `Search`
Placeholder shown in the text input before user starts typing.
-
resultCounter = (hits: SearchHit[]): string =>
hits.length > 0 ? `<span>Results: ${hits.length}<span>` : ``
Function that returns a string which will be displayed next to the name of each index to show how many results were found in that index. Return empty string to show nothing.
-
searchKey: string
Search-only API key
Events
Search.svelte
listens for on:close
events on every hit component it renders and will set hasFocus
to false
to close itself when received. You can use this e.g. to close the search interface when the user clicks on a link in one of the search results and navigates to a different page on your site:
<script>
import { createEventDispatcher } from 'svelte'
export let hit
const dispatch = createEventDispatcher()
</script>
<h3>
<a href={hit.slug} on:click={() => dispatch(`close`)}>{@html hit.title}</a>
</h3>
<p>{@html hit.body}</p>
It also emits a focus
event every the user clicks the search icon and focus enters the text input.
<Search on:focus={() => console.log("Let's search!")} />
Styling
Search.svelte
offers the following CSS variables listed here with their defaults (if any) that can be passed in directly as props:
button
color: var(--search-icon-color)
h2
color: var(--search-heading-color)
input
background: var(--search-input-bg)
color: var(--search-input-color)
font-size: var(--search-input-font-size, 1em)
input::placeholder
color: var(--search-input-color)
input.hasFocus + button
color: var(--search-input-color)
div.results
background-color: var(--search-hits-bg-color, white)
box-shadow: var(--search-hits-shadow, 0 0 2pt black)
For example:
<Search
indices={{ Pages: SearchHit, Posts: SearchHit }}
{appId}
{searchKey}
--hitsBgColor="var(--search-body-bg)"
--inputColor="var(--search-text-color)"
--iconColor="var(--search-link-color)"
/>
The top level element is an aside
with class svelte-algolia
. So you can also style the entire DOM tree below it by defining global styles like
:global(aside.svelte-algolia input button svg) {
}
:global(aside.svelte-algolia div.results section h2) {
}
Examples
Some sites using svelte-algolia
in production:
Using svelte-algolia
yourself? Submit a PR to add your site here!
Want to contribute?
PRs are welcome but best open an issue first to discuss changes.
The app ID and search key .env
were intentionally committed so you can clone this repo and work on it without having to create your own index first. To get a dev server running locally so you can try out changes in src/lib
as you make them, use
git clone https://github.com/janosh/svelte-algolia
cd svelte-algolia
sed -i.bak 's/name: `Pokedex`/name: `Pokedex Clone`/' svelte.config.js
npm install
npm run dev
Note the sed
command that changes the index name in site/svelte.config.js
from 'Pokedex'
to 'Pokedex Clone'
so you don't accidentally mess up the search index for this demo site while developing.