Search (Pagefind)
Relevant source files
The following files were used as context for generating this wiki page:
The search functionality in the Charlotte project is powered by Pagefind, a static site search library. It operates by creating a search index at build time and then performing searches entirely on the client-side, eliminating the need for a server-side search backend. The user interface for the search is implemented as a responsive Svelte component that adapts to both desktop and mobile viewports.
This system provides a fast and efficient search experience for the static content of the blog, such as posts and pages. The entire process is integrated into the project's build pipeline.
Sources: src/components/Search.svelte, package.json:8
System Architecture
The search system is composed of two main stages: build-time indexing and client-side querying.
- Build-Time Indexing: During the production build process, after the Astro site is generated into the
dist/directory, thepagefindCLI is executed. It crawls the static HTML files and builds a search index. - Client-Side Querying: When a user interacts with the search input on the live site, a Svelte component calls the Pagefind JavaScript API, which is loaded in the browser. The API uses the pre-built index to find and return search results without any server requests.
The following diagram illustrates the overall data flow from build to user interaction.
Build & Indexing Process
The integration of Pagefind is defined in the package.json file. The pagefind dependency is listed, and the build script orchestrates the indexing process.
{
"scripts": {
"build": "pnpm clean && astro build && pagefind --site dist"
},
"dependencies": {
"pagefind": "^1.4.0"
}
}
Sources: package.json:8,28
The build script command pagefind --site dist instructs Pagefind to index the contents of the dist directory, which is the output of the astro build command. This creates a pagefind/ sub-directory within dist containing the search index and the necessary client-side JavaScript.
Sources: package.json:8
Frontend Implementation (Search.svelte)
The entire user-facing search experience is managed by the Search.svelte component. It handles user input, communicates with the Pagefind API, and renders the results.
Sources: src/components/Search.svelte
Component Structure and UI
The component renders two different search UIs based on the viewport size:
- Desktop: A search bar (
<input>) is displayed directly in the main navigation header. It expands on focus. - Mobile: A search icon button is shown. Clicking it toggles a floating search panel which contains its own search input.
<!-- search bar for desktop view -->
<div id="search-bar" class="search-container hidden lg:flex ...">
<Icon icon="material-symbols:search" ...></Icon>
<input name="search" placeholder="{i18n(I18nKey.search)}" bind:value={keywordDesktop} on:focus={() => search(keywordDesktop, true)}
class="transition-all pl-10 text-sm bg-transparent outline-0
h-full w-40 active:w-60 focus:w-60 ..."
>
</div>
<!-- toggle btn for phone/tablet view -->
<button on:click={togglePanel} aria-label="Search Panel" id="search-switch"
class="btn-plain scale-animation lg:!hidden ...">
<Icon icon="material-symbols:search" class="text-[1.25rem]"></Icon>
</button>
Sources: src/components/Search.svelte:141-156
Search Logic and State
The component manages its state with several variables:
| Variable | Type | Description |
|---|---|---|
keywordDesktop | string | Bound to the desktop search input value. |
keywordMobile | string | Bound to the mobile search input value. |
result | SearchResult[] | An array holding the search results returned from Pagefind. |
isSearching | boolean | A flag to indicate when a search is in progress. |
pagefindLoaded | boolean | Tracks if the Pagefind client-side script has successfully loaded. |
initialized | boolean | Tracks if the search component's initialization logic has run. |
Sources: src/components/Search.svelte:8-12
Initialization
The onMount lifecycle hook handles the asynchronous initialization of the Pagefind library.
- It attaches event listeners for
pagefindreadyandpagefindloaderrorto thedocument. - When
pagefindreadyfires, it setsinitializedandpagefindLoadedtotrue. - A
setTimeoutfallback is included to initialize the component in case the events are missed. - In development mode (
import.meta.env.DEV), initialization happens immediately since Pagefind is not available.
Sources: src/components/Search.svelte:101-125
Search Execution Flow
The search function is the core of the query logic. It is triggered by user input.
This diagram shows the sequence of events when a user performs a search.
Sources: src/components/Search.svelte:57-99
Development vs. Production Mode
The component has distinct behavior for development and production environments, detected via import.meta.env.PROD and import.meta.env.DEV.
- Production: It uses the live
window.pagefind.search()function to query the real index. - Development: Since the
pagefindcommand doesn't run in dev mode, the index is not available. The component falls back to displaying a hardcodedfakeResultarray to allow for UI testing.
const fakeResult: SearchResult[] = [
{
url: url("/"),
meta: {
title: "This Is a Fake Search Result",
},
excerpt:
"Because the search cannot work in the <mark>dev</mark> environment.",
},
{
url: url("/"),
meta: {
title: "If You Want to Test the Search",
},
excerpt: "Try running <mark>npm build && npm preview</mark> instead.",
},
];
Sources: src/components/Search.svelte:14-28, 70-78
Data Structures
The structure of a search result object is defined by the SearchResult interface in the global type declarations. This ensures type safety when handling data from the Pagefind API.
interface SearchResult {
url: string;
meta: {
title: string;
};
excerpt: string;
content?: string;
word_count?: number;
filters?: Record<string, unknown>;
anchors?: Array<{
element: string;
id: string;
text: string;
location: number;
}>;
weighted_locations?: Array<{
weight: number;
balanced_score: number;
location: number;
}>;
locations?: number[];
raw_content?: string;
raw_url?: string;
sub_results?: SearchResult[];
}
Sources: src/global.d.ts:34-54
The Search.svelte component primarily uses the following fields to display results:
| Field | Type | Description |
|---|---|---|
url | string | The URL of the matching page, used for the link href. |
meta.title | string | The title of the page, displayed as the main result heading. |
excerpt | string | A snippet of content with the search term highlighted (<mark> tags). |
Sources: src/global.d.ts:35-38, src/components/Search.svelte:177-185
Conclusion
The search feature in Charlotte is a robust, client-side system built on Pagefind. It seamlessly integrates into the static site's build process via npm scripts and provides a fast, responsive user experience through a dedicated Svelte component. The architecture cleverly distinguishes between production (live search) and development (mock data) environments, ensuring a smooth workflow for both developers and end-users.