Skip to content
SHOPAII

Hugo + Meilisearch Integration Guide (Optimized for 100K+ Data Volume)

Hugo · Published · 10 min read

Hugo + Meilisearch Integration Guide (Optimized for 100K+ Data Volume)

When a Hugo site has more than 100,000 records, Meilisearch is the optimal search solution—lightweight, open-source, with millisecond-level response times and friendly Chinese language support, eliminating the need for large index.json files. Here’s the complete integration guide:

I. Prerequisites

  1. Hugo installed (any version, recommended 0.90+)
  2. Server/local environment supports Docker (for Meilisearch deployment)
  3. Basic frontend HTML/JS knowledge

II. Step 1: Deploy Meilisearch Service

Meilisearch supports one-click Docker deployment with minimal resource usage (100K records require only a few hundred MB of memory):

1.1 Start Meilisearch Container

# Create data directory (for persistent index storage)
mkdir -p ~/meili_data

# Start Meilisearch (replace YOUR_MASTER_KEY with a custom key, e.g.: MyStrongKey123!)
docker run -d \
  --name meilisearch \
  -p 7700:7700 \
  -v ~/meili_data:/meili_data \
  getmeili/meilisearch:latest \
  --master-key=YOUR_MASTER_KEY \
  --env=production

1.2 Verify Successful Deployment

Visit http://your-server-ip:7700 to see the Meilisearch management interface (Meilisearch Dashboard), confirming successful deployment.

III. Step 2: Sync Hugo Data to Meilisearch

The core involves pushing article data (keeping only search-required fields) from Hugo builds to Meilisearch for indexing.

2.1 Custom Hugo JSON Template (Minimize Data)

Create/modify layouts/_default/list.json, outputting only search-required fields (reducing sync data volume):

{{- $.Scratch.Set "searchData" (slice) -}}
{{- range .Site.RegularPages -}}
  {{- $item := dict
    "id" .File.UniqueID       # Unique identifier (required)
    "title" .Title            # Title
    "summary" (.Summary | plainify)  # Plain text summary (remove HTML tags)
    "permalink" .Permalink    # Article link
    "date" (.Date.Format "2006-01-02") # Date
    "categories" .Params.categories    # Categories (optional)
    "tags" .Params.tags                # Tags (optional)
  -}}
  {{- $.Scratch.Add "searchData" $item -}}
{{- end -}}
{{- $.Scratch.Get "searchData" | jsonify -}}

2.2 Build Hugo to Generate Simplified JSON

# Build Hugo site, generating JSON files for each language
# For single-language: public/index.json
# For multi-language: public/index.json (default), public/zh/index.json (Chinese), etc.
hugo

At this point, language-specific JSON files will be generated: – public/index.json (default language) – public/zh/index.json (Chinese) – public/es/index.json (Spanish) Each will be significantly smaller in size (approximately 5-10 MB for 100K records per language).

2.3 Sync JSON to Meilisearch

Two methods available, official tools recommended. For multi-language sites, create separate indexes for each language.

Method 1: Using hugo-meilisearch plugin (Recommended)

# Install plugin (requires Go 1.18+ environment)
go install github.com/meilisearch/hugo-meilisearch@latest

# Sync DEFAULT language data to Meilisearch
hugo-meilisearch \
  -f public/index.json \          # Default language JSON file
  -h http://your-server-ip:7700 \ # Meilisearch address
  -k YOUR_MASTER_KEY \            # Master key set in step 1
  -i articles_en                  # Index name with language suffix

# Sync CHINESE language data to Meilisearch
hugo-meilisearch \
  -f public/zh/index.json \       # Chinese language JSON file
  -h http://your-server-ip:7700 \ # Meilisearch address
  -k YOUR_MASTER_KEY \            # Master key set in step 1
  -i articles_zh                  # Index name with language suffix

# Sync SPANISH language data to Meilisearch (add for each language)
hugo-meilisearch \
  -f public/es/index.json \       # Spanish language JSON file
  -h http://your-server-ip:7700 \ # Meilisearch address
  -k YOUR_MASTER_KEY \            # Master key set in step 1
  -i articles_es                  # Index name with language suffix

Method 2: Manual API sync (when no Go environment)

# Sync DEFAULT language
curl \
  -X POST 'http://your-server-ip:7700/indexes/articles_en/documents?primaryKey=id' \
  -H 'Content-Type: application/json' \
  -H 'X-Meili-API-Key: YOUR_MASTER_KEY' \
  --data-binary @public/index.json

# Sync CHINESE language
curl \
  -X POST 'http://your-server-ip:7700/indexes/articles_zh/documents?primaryKey=id' \
  -H 'Content-Type: application/json' \
  -H 'X-Meili-API-Key: YOUR_MASTER_KEY' \
  --data-binary @public/zh/index.json

# Sync SPANISH language (add for each language)
curl \
  -X POST 'http://your-server-ip:7700/indexes/articles_es/documents?primaryKey=id' \
  -H 'Content-Type: application/json' \
  -H 'X-Meili-API-Key: YOUR_MASTER_KEY' \
  --data-binary @public/es/index.json

2.3.1 Multi-language Sync Script (Recommended)

Create a bash script to automate syncing all languages:

#!/bin/bash
# sync-meilisearch.sh

# Configuration
MEILI_HOST="http://your-server-ip:7700"
MEILI_KEY="YOUR_MASTER_KEY"
HUGO_PUBLIC="public"

# Sync function
sync_language() {
    local lang=$1
    local json_path=$2
    local index_name="articles_${lang}"

    echo "Syncing ${lang} language to ${index_name}..."
    hugo-meilisearch \
        -f "${HUGO_PUBLIC}/${json_path}" \
        -h "${MEILI_HOST}" \
        -k "${MEILI_KEY}" \
        -i "${index_name}"

    echo "✓ ${lang} language synced successfully!"
}

# Sync all languages
sync_language "en" "index.json"      # Default language
sync_language "zh" "zh/index.json"   # Chinese language
sync_language "es" "es/index.json"   # Spanish language
# Add more languages as needed

echo "All languages synced to Meilisearch!"

Make the script executable:

chmod +x sync-meilisearch.sh

Run the script:

./sync-meilisearch.sh

2.4 Verify Index Sync

Access the Meilisearch management interface at http://your-server-ip:7700, enter the articles index, and you’ll see the index list for 100K records, confirming successful sync.

IV. Step 3: Frontend Integration of Meilisearch Search Functionality

Frontend directly calls Meilisearch API for search functionality without loading local JSON, providing extremely fast response times:

3.1 Frontend Search Code Example for Multi-language Sites

<!-- Search box + results display area -->
<div class="search-container">
  <input type="text" id="searchInput" placeholder="Search keywords..." autocomplete="off">
  <div id="searchResults" class="search-results"></div>
</div>

<!-- Include Meilisearch JS SDK (can also use fetch to call API directly) -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/meilisearch.umd.js"></script>

<script>
// 1. Initialize Meilisearch client
const client = new meilisearch.Client(
  'http://your-server-ip:7700',  // Meilisearch address
  'YOUR_READ_ONLY_KEY'           // Use read-only key in production!
);

// 2. Get current language (detect from URL, HTML lang attribute, or Hugo's .Language variable)
// This example detects from URL path (e.g., /zh/ for Chinese, /es/ for Spanish)
let currentLanguage = 'en'; // Default language
const path = window.location.pathname;
if (path.startsWith('/zh/')) {
  currentLanguage = 'zh';
} else if (path.startsWith('/es/')) {
  currentLanguage = 'es';
} 
// Add more language detection as needed

// 3. Get appropriate index for current language
const index = client.index(`articles_${currentLanguage}`);

// 4. Localized search messages
const searchMessages = {
  'en': {
    placeholder: 'Search keywords...',
    noResult: 'No relevant content found'
  },
  'zh': {
    placeholder: '搜索关键词...',
    noResult: '未找到相关内容'
  },
  'es': {
    placeholder: 'Buscar palabras clave...',
    noResult: 'No se encontró contenido relevante'
  }
  // Add more languages as needed
};

// 5. Update search input placeholder based on language
const searchInput = document.getElementById('searchInput');
searchInput.placeholder = searchMessages[currentLanguage].placeholder;

// 6. Search debounce (avoid frequent requests)
let searchTimer = null;
const resultsContainer = document.getElementById('searchResults');

// 7. Core search logic
searchInput.addEventListener('input', (e) => {
  const query = e.target.value.trim();

  // Clear results
  resultsContainer.innerHTML = '';
  if (!query) return;

  // Debounce: execute only once within 300ms
  clearTimeout(searchTimer);
  searchTimer = setTimeout(async () => {
    try {
      // Call Meilisearch search API
      const searchResults = await index.search(query, {
        limit: 10,          // Show 10 results per page
        attributesToHighlight: ['title', 'summary'], // Highlighted fields
      });

      // Render search results
      if (searchResults.hits.length === 0) {
        resultsContainer.innerHTML = `<div class="no-result">${searchMessages[currentLanguage].noResult}</div>`;
        return;
      }

      resultsContainer.innerHTML = searchResults.hits.map(hit => `
        <a href="${hit.permalink}" class="result-item">
          <h3>${hit._highlightResult.title.value || hit.title}</h3>
          <p>${hit._highlightResult.summary.value || hit.summary.substring(0, 100)}...</p>
          <small>${hit.date} | ${hit.categories?.join(', ') || ''}</small>
        </a>
      `).join('');
    } catch (error) {
      console.error('Search error:', error);
      resultsContainer.innerHTML = '<div class="no-result">Search error occurred</div>';
    }
  }, 300);
});
</script>

<!-- Simple styles (optional) -->
<style>
.search-container { max-width: 800px; margin: 20px auto; }
#searchInput { width: 100%; padding: 10px; font-size: 16px; }
.search-results { margin-top: 10px; }
.result-item { display: block; padding: 10px; border-bottom: 1px solid #eee; text-decoration: none; color: #333; }
.result-item h3 { margin: 0; color: #0071e3; }
.result-item p { margin: 5px 0; color: #666; }
.no-result { padding: 10px; color: #999; text-align: center; }
mark { background: #fff3cd; }
</style>

V. Production Environment Optimization

5.1 General Optimization

  1. Create Read-Only Key: Don’t use master key directly in production. Create a read-only key in Meilisearch management interface (with search permissions only) for each index (e.g., articles_en, articles_zh);
  2. Enable HTTPS: Use Nginx reverse proxy for Meilisearch, configure HTTPS to avoid plaintext transmission;
  3. Index Optimization: Configure language-specific tokenization:
  4. Meilisearch v1.0+ has built-in support for Chinese, English, Spanish, French, German, Japanese, and Korean
  5. No additional plugins needed – Meilisearch automatically detects language based on content

5.2 Multi-language Specific Optimization

  1. Language-based Scheduled Index Sync: Add cron job to periodically execute Hugo build + multi-language data sync: bash # Example: Sync all languages once daily at 2 AM 0 2 * * * cd /your-site-directory && hugo && ./sync-meilisearch.sh

  2. Language Detection in Hugo Template: For better integration with Hugo’s built-in language features, modify the search code to use Hugo’s .Language variable: “`html

“`

  1. Language-specific Index Settings: Optimize search experience for each language: “`bash # Example: Set Chinese-specific search settings curl -X PUT ‘http://your-server-ip:7700/indexes/articles_zh/settings’ \ -H ‘Content-Type: application/json’ \ -H ‘X-Meili-API-Key: YOUR_MASTER_KEY’ \ –data ‘{“searchableAttributes”: [“title”, “summary”, “categories”, “tags”], “sortableAttributes”: [“date”]}’

# Example: Set English-specific search settings curl -X PUT ‘http://your-server-ip:7700/indexes/articles_en/settings’ \ -H ‘Content-Type: application/json’ \ -H ‘X-Meili-API-Key: YOUR_MASTER_KEY’ \ –data ‘{“searchableAttributes”: [“title”, “summary”, “categories”, “tags”], “sortableAttributes”: [“date”]}’ “`

  1. Multi-language Search Analytics: Track search performance across languages: “`javascript // Add to search logic searchInput.addEventListener(‘input’, async (e) => { // Existing search code…

    try { const searchResults = await index.search(query, {/ options /});

    // Track search event with language if (typeof gtag !== ‘undefined’) { gtag(‘event’, ‘search’, { ‘search_term’: query, ‘language’: currentLanguage, ‘results_count’: searchResults.hits.length }); }

    // Render results… } catch (error) { // Error handling… } }); “`

5.3 Scaling Considerations

  1. Separate Meilisearch Instances (Optional): For large multi-language sites (1M+ records per language), consider using separate Meilisearch instances for different language groups to improve performance;
  2. Index Compression: Meilisearch automatically compresses indexes, but for extreme volumes, consider disabling less important features (like typo tolerance) for some languages;
  3. CDN Caching: Use a CDN to cache search results for common queries, especially for popular languages.

Summary for Multi-language Sites

1. Core Multi-language Process

  • Deploy Meilisearch: Single instance handles all languages efficiently
  • Hugo Build: Generates language-specific JSON files (index.json, zh/index.json, etc.)
  • Multi-index Sync: Creates separate indexes for each language (e.g., articles_en, articles_zh)
  • Language-aware Search: Frontend detects current language and queries the appropriate index

2. Key Multi-language Advantages

  • Language Isolation: Search results are specific to the current language context
  • Performance: Language-specific indexes provide faster search response times
  • Localization: Search interface and results messages adapt to the user’s language
  • Scalability: Easy to add new languages by extending the sync script and frontend code

3. Best Practices for Multi-language Implementation

  • Use Language Suffixes: Name indexes with clear language identifiers (e.g., articles_zh)
  • Automate Sync: Create a script to sync all languages in one operation
  • Leverage Hugo’s Language Features: Use .Language variable for better integration
  • Optimize per Language: Configure language-specific search settings for better relevance
  • Monitor Performance: Track search analytics across languages to identify optimization opportunities

By following these multi-language optimization strategies, you can efficiently scale Meilisearch across large Hugo sites with multiple languages while maintaining excellent search performance and user experience.