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
- Hugo installed (any version, recommended 0.90+)
- Server/local environment supports Docker (for Meilisearch deployment)
- 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
- 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); - Enable HTTPS: Use Nginx reverse proxy for Meilisearch, configure HTTPS to avoid plaintext transmission;
- Index Optimization: Configure language-specific tokenization:
- Meilisearch v1.0+ has built-in support for Chinese, English, Spanish, French, German, Japanese, and Korean
- No additional plugins needed – Meilisearch automatically detects language based on content
5.2 Multi-language Specific Optimization
-
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 -
Language Detection in Hugo Template: For better integration with Hugo’s built-in language features, modify the search code to use Hugo’s
.Languagevariable: “`html
“`
- 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”]}’ “`
-
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
- 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;
- Index Compression: Meilisearch automatically compresses indexes, but for extreme volumes, consider disabling less important features (like typo tolerance) for some languages;
- 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
.Languagevariable 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.