Search as Primary Navigation
At 223 pages, search transitions from a secondary fallback utility to a primary navigation vector. Implementing robust, lightning-fast static-site search is critical for both performance and complex discoverability.
Pagefind Configuration
Pagefind represents the optimal search architecture for large-scale static sites built on Eleventy. It operates entirely without backend infrastructure, indexing built HTML files and chunking the search index into smaller WebAssembly (WASM) fragments.
Performance Characteristics
| Metric | Capability |
|---|---|
| Index size | < 300kB for 10,000 pages |
| Query speed | Instant client-side |
| Bandwidth | Minimal, ideal for 3G/prepaid |
| Infrastructure | Zero server dependencies |
Basic Integration
// eleventy.config.js
module.exports = function(eleventyConfig) {
eleventyConfig.on('eleventy.after', async () => {
const { createIndex } = await import('pagefind');
const { index } = await createIndex();
await index.addDirectory({
path: '_site'
});
await index.writeFiles({
outputPath: '_site/pagefind'
});
});
};
Indexing Strategy
Targeted Indexing
Extraneous DOM elements must be explicitly excluded from the index to prevent false positives and index bloat.
<!-- Exclude navigation from search index -->
<nav data-pagefind-ignore>
<!-- Navigation content -->
</nav>
<!-- Exclude footer -->
<footer data-pagefind-ignore>
<!-- Footer content -->
</footer>
<!-- Exclude "Related Links" sidebars -->
<aside class="related-links" data-pagefind-ignore>
<!-- Related content -->
</aside>
Content Weighting
Pagefind uses a quadratic scaling system for content weighting. By default, <h1> elements carry a weight of 7.0.
<!-- Boost emergency protocols in crisis queries -->
<div data-pagefind-weight="10.0">
<h2>If ICE is at Your Door</h2>
<p>DO NOT OPEN THE DOOR. You have the right to remain silent.</p>
</div>
<!-- Boost legal deadlines -->
<div class="deadline-warning" data-pagefind-weight="8.0">
<p>You have <strong>30 days</strong> to file your appeal.</p>
</div>
<!-- Standard content weight (default 1.0) -->
<p>Additional background information...</p>
Metadata Surfacing
Each page must define metadata for rich search result cards:
<head>
<meta data-pagefind-meta="title" content="Workplace Raid Response Guide">
<meta data-pagefind-meta="image" content="/images/workplace-rights.jpg">
<meta data-pagefind-meta="image_alt" content="Worker rights illustration">
<meta data-pagefind-meta="audience" content="General Public">
<meta data-pagefind-meta="urgency" content="Emergency">
</head>
Filter Attributes
Enable faceted filtering via data attributes:
<!-- In page frontmatter/template -->
<body data-pagefind-filter-audience="General Public"
data-pagefind-filter-type="Guide"
data-pagefind-filter-topic="Workplace Enforcement">
Faceted Search Implementation
Faceted search enables users to narrow the 223-page corpus by taxonomy dimensions.
Client-Side Pill Toggles
<div class="search-facets">
<fieldset class="facet-group">
<legend>Audience</legend>
<label class="facet-pill">
<input type="checkbox" name="audience" value="general-public">
<span>General Public</span>
</label>
<label class="facet-pill">
<input type="checkbox" name="audience" value="attorneys">
<span>Attorneys</span>
</label>
<label class="facet-pill">
<input type="checkbox" name="audience" value="advocates">
<span>Advocates</span>
</label>
</fieldset>
<fieldset class="facet-group">
<legend>Format</legend>
<label class="facet-pill">
<input type="checkbox" name="format" value="guide">
<span>Guides</span>
</label>
<label class="facet-pill">
<input type="checkbox" name="format" value="printable">
<span>Printables</span>
</label>
<label class="facet-pill">
<input type="checkbox" name="format" value="tool">
<span>Tools</span>
</label>
</fieldset>
</div>
.facet-pill {
display: inline-flex;
align-items: center;
padding: 0.5rem 1rem;
border: 2px solid var(--color-border);
border-radius: 2rem;
cursor: pointer;
transition: all 0.2s;
}
.facet-pill:has(:checked) {
background: var(--color-primary);
border-color: var(--color-primary);
color: white;
}
.facet-pill input {
position: absolute;
opacity: 0;
pointer-events: none;
}
Pagefind Filter Integration
// Search with facets
const pagefind = await import('/pagefind/pagefind.js');
await pagefind.init();
async function performSearch(query, filters) {
const results = await pagefind.search(query, {
filters: {
audience: filters.audience || [],
type: filters.type || [],
topic: filters.topic || []
}
});
return results;
}
// Example: Search for "workplace" filtered to "Printables"
const results = await performSearch('workplace', {
type: ['printable']
});
Search Results UX
Full-Screen Overlay
On mobile, search should expand into a full-screen overlay presenting both the input field and popular filter facets simultaneously.
<div class="search-overlay" hidden>
<div class="search-overlay__header">
<input type="search"
class="search-overlay__input"
placeholder="Search legal resources..."
aria-label="Search">
<button class="search-overlay__close" aria-label="Close search">×</button>
</div>
<div class="search-overlay__facets">
<!-- Facet pills -->
</div>
<div class="search-overlay__results">
<!-- Results populated via JS -->
</div>
</div>
.search-overlay {
position: fixed;
inset: 0;
background: var(--color-surface);
z-index: 2000;
display: flex;
flex-direction: column;
}
.search-overlay__header {
display: flex;
padding: 1rem;
gap: 1rem;
border-bottom: 1px solid var(--color-border);
}
.search-overlay__input {
flex: 1;
padding: 1rem;
font-size: 1.125rem;
border: 2px solid var(--color-primary);
border-radius: 8px;
}
.search-overlay__results {
flex: 1;
overflow-y: auto;
padding: 1rem;
}
Result Card Display
<article class="search-result">
<div class="search-result__meta">
<span class="search-result__type">Guide</span>
<span class="search-result__urgency search-result__urgency--emergency">
Emergency
</span>
</div>
<h3 class="search-result__title">
<a href="/know-your-rights/workplace-raids/">
Workplace Raid Response Guide
</a>
</h3>
<p class="search-result__excerpt">
...you have the right to <mark>remain silent</mark> and refuse...
</p>
<div class="search-result__actions">
<a href="/printables/workplace-card/" class="search-result__action">
Download Printable
</a>
</div>
</article>
Zero-Result Fallbacks
When no results match, provide constructive alternatives:
<div class="search-no-results">
<h2>No results found for "{{ query }}"</h2>
<div class="no-results__suggestions">
<h3>Try these instead:</h3>
<ul>
<li><a href="/know-your-rights/">Browse Know Your Rights guides</a></li>
<li><a href="/printables/">View all printable resources</a></li>
<li><a href="/emergency/">Emergency help</a></li>
</ul>
</div>
<div class="no-results__popular">
<h3>Popular searches:</h3>
<ul>
<li><a href="?q=ice+at+door">ICE at my door</a></li>
<li><a href="?q=workplace+raid">Workplace raid</a></li>
<li><a href="?q=daca+renewal">DACA renewal</a></li>
</ul>
</div>
</div>
Legal Term Normalization
Map colloquial terms to formal legal terminology:
// pagefind.config.js
export default {
processTerm: (term) => {
const synonyms = {
'deportation': 'removal',
'deported': 'removed',
'green card': 'lawful permanent resident',
'papers': 'documentation',
'la migra': 'ice',
'inmigración': 'immigration',
'abogado': 'attorney',
'corte': 'court',
'fianza': 'bond'
};
return synonyms[term.toLowerCase()] || term;
}
};
Autocomplete Suggestions
Populate search with high-volume problem-area queries:
const topSearches = [
'ice at my door',
'workplace raid rights',
'checkpoint refuse search',
'daca renewal',
'find immigration lawyer',
'red card',
'detention visitation'
];
const searchInput = document.querySelector('.search-input');
searchInput.addEventListener('focus', () => {
showSuggestions(topSearches);
});
searchInput.addEventListener('input', debounce(async (e) => {
const query = e.target.value;
if (query.length < 2) {
showSuggestions(topSearches);
return;
}
const results = await pagefind.search(query);
showResults(results);
}, 200));
Search Analytics (Privacy-Preserving)
Track search behavior without compromising user privacy:
// Log search queries without PII
function logSearch(query, resultCount) {
// Hash the query to prevent storing actual search terms
const queryHash = hashString(query);
// Only log aggregate patterns
if (resultCount === 0) {
incrementCounter('zero_result_searches');
}
// Track category clicks, not individual behavior
incrementCounter(`search_category_${resultCount > 0 ? 'found' : 'empty'}`);
}
Testing Checklist
Search Functionality
- [ ] Pagefind index builds without errors
- [ ] Navigation elements excluded from index
- [ ] Emergency content weighted higher
- [ ] Faceted filters work correctly
- [ ] Zero-result fallbacks display
- [ ] Autocomplete suggestions appear
Performance
- [ ] Index loads < 300kB
- [ ] Query results < 100ms
- [ ] Works on 3G connection
- [ ] Mobile overlay smooth
Accessibility
- [ ] Search accessible via keyboard
- [ ] Screen reader announces results
- [ ] Focus management correct
- [ ] ARIA labels complete
Related Resources
- Information Architecture - Taxonomy design
- Mobile-First - Mobile search patterns
- Performance - Index optimization
- SEO Strategy - Search visibility