Search Index Configuration

Stati can generate a JSON search index at build time, enabling client-side search functionality for your static site.

Overview

Search index generation in Stati:

  • Build-time generation: Search index is generated during the build process
  • Section-level indexing: Extracts searchable sections from markdown headings
  • Cache-friendly: Hash-based filenames for optimal cache busting
  • Auto-discovery: Automatically injects a meta tag for client-side discovery
  • Configurable: Control heading levels, content length, and exclusions

Basic Configuration

Enable search index generation in your stati.config.ts:

import { defineConfig } from '@stati/core';

export default defineConfig({
  search: {
    enabled: true,
  },
});

This generates a search-index-[hash].json file in your output directory containing all your pages.

Configuration Options

Full Configuration Example

import { defineConfig } from '@stati/core';

export default defineConfig({
  search: {
    enabled: true,
    indexName: 'search-index',
    hashFilename: true,
    maxContentLength: 1000,
    maxPreviewLength: 500,
    headingLevels: [2, 3, 4, 5, 6],
    exclude: ['/private/**'],
    includeHomePage: false,
    autoInjectMetaTag: true,
  },
});

Option Reference

Option Type Default Description
enabled boolean false Enable search index generation
indexName string 'search-index' Base filename for the search index (without extension)
hashFilename boolean true Include content hash in filename for cache busting
maxContentLength number 1000 Maximum content length per section (in characters)
maxPreviewLength number 500 Maximum preview length for page-level entries (in characters)
headingLevels number[] [2, 3, 4, 5, 6] Heading levels to include in the index
exclude string[] [] Glob patterns for pages to exclude
includeHomePage boolean false Include the home page in the search index
autoInjectMetaTag boolean true Auto-inject search index meta tag into HTML

Content Filtering

Exclude Pages

Use exclude patterns to exclude specific content from the search index:

search: {
  enabled: true,
  exclude: [
    '/private/**',      // Exclude all pages under /private/
    '/admin/**',        // Exclude admin pages
    '/api/**',          // Exclude API documentation
  ],
}

Include Home Page

By default, the home page is excluded from the search index. To include it:

search: {
  enabled: true,
  includeHomePage: true,
}

Control Heading Levels

Specify which heading levels to index:

search: {
  enabled: true,
  headingLevels: [2, 3],  // Only index h2 and h3 headings
}

Search Index Structure

The generated search index is a JSON file with the following structure:

{
  "version": "1.0.0",
  "generatedAt": "2025-01-15T10:30:00.000Z",
  "documentCount": 150,
  "documents": [
    {
      "id": "/getting-started/installation#prerequisites",
      "url": "/getting-started/installation",
      "anchor": "prerequisites",
      "title": "Installation",
      "heading": "Prerequisites",
      "level": 2,
      "content": "Before installing Stati, ensure you have Node.js 22 or later...",
      "breadcrumb": "Getting Started > Installation",
      "tags": ["setup", "installation"]
    }
  ]
}

Document Fields

Field Type Description
id string Unique identifier (page URL + section anchor)
url string Page URL path
anchor string Section anchor (heading ID), empty for page-level entries
title string Page title from frontmatter
heading string Section heading text
level number Heading level (1 for page title, 2-6 for headings)
content string Text content of the section (stripped of markdown)
breadcrumb string Breadcrumb path for display
tags string[] Optional tags from frontmatter

Template Access

Access the search index path in your templates via stati.assets.searchIndexPath:

<% if (stati.assets.searchIndexPath) { %>
<script>
  window.SEARCH_INDEX_PATH = '<%= stati.assets.searchIndexPath %>';
</script>
<% } %>

Auto-Injected Meta Tag

When autoInjectMetaTag is enabled (default), Stati automatically injects a meta tag into your HTML:

<meta name="stati:search-index" content="/search-index-a1b2c3d4.json">

This allows client-side JavaScript to discover the search index location:

const metaTag = document.querySelector('meta[name="stati:search-index"]');
const searchIndexPath = metaTag?.getAttribute('content');

if (searchIndexPath) {
  const response = await fetch(searchIndexPath);
  const searchIndex = await response.json();
  // Use searchIndex for client-side search
}

Here’s a basic example of implementing client-side search using the generated index:

// Fetch the search index
async function loadSearchIndex() {
  const metaTag = document.querySelector('meta[name="stati:search-index"]');
  if (!metaTag) return null;

  const response = await fetch(metaTag.getAttribute('content'));
  return response.json();
}

// Simple search function
function search(index, query) {
  const lowerQuery = query.toLowerCase();
  return index.documents.filter(doc =>
    doc.title.toLowerCase().includes(lowerQuery) ||
    doc.heading.toLowerCase().includes(lowerQuery) ||
    doc.content.toLowerCase().includes(lowerQuery)
  );
}

// Usage
const index = await loadSearchIndex();
const results = search(index, 'installation');

For production use, consider using a client-side search library like Fuse.js or Lunr.js for better search capabilities including fuzzy matching and relevance scoring.

Cache Busting

When hashFilename is enabled (default), the search index filename includes a hash:

search-index-a1b2c3d4.json

This ensures browsers fetch the latest version when your content changes. The hash is deterministic per build, so the filename remains consistent within a single build but changes between builds when content is updated.

To disable hash-based filenames:

search: {
  enabled: true,
  hashFilename: false,  // Generates 'search-index.json'
}

Performance Considerations

Content Length

The maxContentLength and maxPreviewLength options control how much text is included in the index:

search: {
  enabled: true,
  maxContentLength: 500,   // Shorter sections for smaller index
  maxPreviewLength: 200,   // Shorter previews
}

Smaller values result in a smaller index file but less context for search results.

Heading Levels

Limiting heading levels reduces the number of documents in the index:

search: {
  enabled: true,
  headingLevels: [2],  // Only index h2 headings
}

Excluding Content

Exclude non-essential pages to keep the index focused:

search: {
  enabled: true,
  exclude: [
    '/changelog/**',
    '/license',
    '/404',
  ],
}

Draft Pages

Draft pages (pages with draft: true in frontmatter) are automatically excluded from the search index, regardless of other configuration settings.