/ astro-integrations / How to Use Meilisearch with Astro: Complete Guide
astro-integrations 5 min read

How to Use Meilisearch with Astro: Complete Guide

Step-by-step guide to integrating Meilisearch with your Astro website.

Meilisearch is an open-source search engine that delivers instant, typo-tolerant search results with minimal configuration. It is designed to be an easy-to-deploy alternative to Algolia or Elasticsearch. You can self-host it for free or use Meilisearch Cloud starting at $30/month. For Astro sites with a lot of content, blog posts, documentation, product catalogs, Meilisearch provides a search experience that feels fast and forgiving.

The integration works in two parts: indexing your content (pushing data to Meilisearch at build time) and building a search UI (querying Meilisearch from the browser). Both parts are straightforward, and Meilisearch's JavaScript SDK handles the heavy lifting.

Prerequisites

  • Node.js 18+
  • An Astro project (npm create astro@latest)
  • A Meilisearch instance (self-hosted via Docker or Meilisearch Cloud)
  • Your Meilisearch host URL and API keys

Installation

Install the Meilisearch JavaScript client:

npm install meilisearch

If you want a pre-built search UI component:

npm install @meilisearch/instant-meilisearch instantsearch.js

For a self-hosted Meilisearch instance, run it with Docker:

docker run -d -p 7700:7700 \
  -e MEILI_MASTER_KEY='your-master-key' \
  -v $(pwd)/meili_data:/meili_data \
  getmeili/meilisearch:latest

Configuration

Get your API keys. If self-hosted, Meilisearch generates a search key and admin key from your master key. On Meilisearch Cloud, find them in the project settings.

Add credentials to .env:

MEILISEARCH_HOST=http://localhost:7700
MEILISEARCH_ADMIN_KEY=your-admin-api-key
PUBLIC_MEILISEARCH_HOST=http://localhost:7700
PUBLIC_MEILISEARCH_SEARCH_KEY=your-search-api-key
MEILISEARCH_INDEX=blog_posts

Create an indexing script that pushes your content to Meilisearch:

// scripts/index-to-meilisearch.ts
import { MeiliSearch } from "meilisearch";
import fs from "fs";
import path from "path";
import matter from "gray-matter";

const client = new MeiliSearch({
  host: process.env.MEILISEARCH_HOST!,
  apiKey: process.env.MEILISEARCH_ADMIN_KEY!,
});

const postsDir = path.join(process.cwd(), "src/content/posts");
const files = fs.readdirSync(postsDir).filter((f) => f.endsWith(".mdx"));

const documents = files.map((file) => {
  const raw = fs.readFileSync(path.join(postsDir, file), "utf-8");
  const { data, content } = matter(raw);
  const slug = data.slug || file.replace(".mdx", "");

  return {
    id: slug,
    title: data.title,
    description: data.description,
    content: content.slice(0, 10000),
    tags: data.tags || [],
    publishDate: data.publishDate,
    url: `/blog/${slug}`,
  };
});

const index = client.index(process.env.MEILISEARCH_INDEX!);

// Configure searchable and filterable attributes
await index.updateSettings({
  searchableAttributes: ["title", "description", "content", "tags"],
  filterableAttributes: ["tags", "publishDate"],
  sortableAttributes: ["publishDate"],
  rankingRules: [
    "words",
    "typo",
    "proximity",
    "attribute",
    "sort",
    "exactness",
  ],
});

const task = await index.addDocuments(documents);
console.log(`Indexing task created: ${task.taskUid}`);
console.log(`Indexed ${documents.length} posts`);

Run it with:

npx tsx scripts/index-to-meilisearch.ts

Basic Usage

Build a search component using InstantSearch (compatible with Meilisearch):

---
// src/components/Search.astro
---

<div id="searchbox"></div>
<div id="hits"></div>

<script>
  import { instantMeiliSearch } from "@meilisearch/instant-meilisearch";
  import instantsearch from "instantsearch.js";
  import { searchBox, hits } from "instantsearch.js/es/widgets";

  const { searchClient } = instantMeiliSearch(
    import.meta.env.PUBLIC_MEILISEARCH_HOST,
    import.meta.env.PUBLIC_MEILISEARCH_SEARCH_KEY
  );

  const search = instantsearch({
    indexName: "blog_posts",
    searchClient,
  });

  search.addWidgets([
    searchBox({
      container: "#searchbox",
      placeholder: "Search posts...",
      cssClasses: {
        input: "search-input",
      },
    }),
    hits({
      container: "#hits",
      templates: {
        item: (hit) => `
          <a href="${hit.url}" class="search-result">
            <h3>${hit._highlightResult.title.value}</h3>
            <p>${hit._highlightResult.description.value}</p>
          </a>
        `,
        empty: () => `<p>No results found. Try a different search term.</p>`,
      },
    }),
  ]);

  search.start();
</script>

For a simpler approach without InstantSearch, query Meilisearch directly:

---
// src/components/SimpleSearch.astro
---

<input type="text" id="search-input" placeholder="Search..." />
<div id="results"></div>

<script>
  import { MeiliSearch } from "meilisearch";

  const client = new MeiliSearch({
    host: import.meta.env.PUBLIC_MEILISEARCH_HOST,
    apiKey: import.meta.env.PUBLIC_MEILISEARCH_SEARCH_KEY,
  });

  const index = client.index("blog_posts");
  const input = document.getElementById("search-input") as HTMLInputElement;
  const resultsContainer = document.getElementById("results");

  let debounceTimer: ReturnType<typeof setTimeout>;

  input?.addEventListener("input", () => {
    clearTimeout(debounceTimer);
    debounceTimer = setTimeout(async () => {
      const query = input.value;
      if (!query || !resultsContainer) {
        if (resultsContainer) resultsContainer.textContent = "";
        return;
      }

      const searchResults = await index.search(query, { limit: 10 });

      // Clear previous results
      resultsContainer.textContent = "";

      // Build results using DOM APIs
      searchResults.hits.forEach((hit) => {
        const link = document.createElement("a");
        link.href = hit.url;

        const title = document.createElement("strong");
        title.textContent = hit.title;

        const desc = document.createElement("p");
        desc.textContent = hit.description;

        link.appendChild(title);
        link.appendChild(desc);
        resultsContainer.appendChild(link);
      });
    }, 200);
  });
</script>

Production Tips

  1. Re-index on content changes. Add the indexing script to your build process (npm run build && npx tsx scripts/index-to-meilisearch.ts) or trigger it via a CMS webhook. Stale search results are worse than no search at all.

  2. Use filtering for faceted search. Meilisearch supports filtering on configured attributes. Let users filter by tags, date ranges, or categories without additional API calls. Combine with the sort parameter for "newest first" or "most relevant" orderings.

  3. Keep the search key public and the admin key private. The search API key can only perform search operations. Never expose the admin key on the frontend. It is safe to include the search key in client-side code.

  4. Optimize your index size. Only index fields that users search against. If you have large content bodies, truncate them to the first 5,000-10,000 characters. Most relevant matches come from titles and first paragraphs anyway.

  5. Use Meilisearch Cloud for production. Self-hosting is great for development, but Meilisearch Cloud handles backups, updates, and uptime monitoring. The $30/month plan covers 100,000 documents and 10,000 searches per month, which is plenty for most content sites.

Alternatives to Consider

  • Algolia if you want a more mature hosted search with advanced features like personalization, A/B testing, and AI recommendations.
  • Pagefind if you want a free, build-time search that runs entirely in the browser with no external service required.
  • Typesense if you want another open-source alternative with similar performance characteristics and a slightly different query syntax.

Wrapping Up

Meilisearch and Astro are a natural fit for content-heavy sites that need instant, forgiving search. The setup is simpler than Elasticsearch, the performance rivals Algolia, and the self-hosting option means you can run it for free. The InstantSearch compatibility means you get a polished search UI with minimal effort, and the typo tolerance ensures users find what they are looking for even with imperfect queries. If your Astro site has enough content to warrant a search feature, Meilisearch is one of the best options in terms of developer experience and cost.