Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

docs(adr): ADR-0005: Server-Side Search Index

Đang mở
#13 0 bình luận 0 reaction 1 người được giao Xem trên GitHub

@aRustyDev đang làm issue này rồi.

Từ ngày 5/1/2026.

Đánh giá

Issue này chưa được đánh giá.

Mô tả

documentation

ADR-0005: Server-Side Search Index

Status

Accepted

Context

MDBook's default HTML renderer includes client-side search powered by elasticlunr.js. The search index is loaded as JSON and executed in the browser.

For an HTMX-based architecture, we need to decide where search execution happens.

Decision Drivers

  1. HTMX Philosophy - Server returns HTML, not JSON
  2. Performance - Large books have large indexes
  3. Authorization - Search results should respect access control
  4. Simplicity - Minimize client-side JavaScript

Options Considered

Option A: Client-Side Search (like mdbook-html)

Ship elasticlunr.js and JSON index to browser.

<input type="search" onkeyup="search(this.value)">
<script>
  const index = elasticlunr.Index.load(searchIndex);
  function search(query) {
    const results = index.search(query);
    document.getElementById('results').innerHTML = renderResults(results);
  }
</script>

Pros:

  • No server required
  • Instant results (after index load)
  • Works offline

Cons:

  • Large index download (100KB+ for big books)
  • Slow initial load
  • JavaScript required
  • No auth filtering (all content in index)
  • Against HTMX philosophy
Option B: Server-Side Search with JSON Response

Server searches, returns JSON, client renders.

<input hx-get="/search" hx-trigger="input changed delay:300ms">

Server returns:

{"results": [{"title": "Chapter 1", "url": "/docs/ch1"}]}

Pros:

  • Index stays on server
  • Can filter by auth

Cons:

  • Requires client-side rendering
  • Not hypermedia (JSON, not HTML)
  • More JavaScript
Option C: Server-Side Search with HTML Response (Recommended)

Server searches, returns HTML fragment.

<input
  type="search"
  name="q"
  hx-get="/docs/search"
  hx-trigger="input changed delay:300ms"
  hx-target="#search-results"
  hx-swap="innerHTML">

<div id="search-results"></div>

Server returns:

<ul class="search-results">
  <li><a href="/docs/ch1">Chapter 1</a><p>...matching text...</p></li>
  <li><a href="/docs/ch2">Chapter 2</a><p>...matching text...</p></li>
</ul>

Pros:

  • True hypermedia (HTML response)
  • No client-side rendering logic
  • Authorization-aware (filter results)
  • Index never sent to client
  • Works with minimal JS (just HTMX)

Cons:

  • Requires server
  • Network latency per search

Decision

Generate server-side search index; output HTML search results template.

Index Format

Default: search-index.json

{
  "config": {
    "heading_split_level": 3
  },
  "documents": [
    {
      "path": "/docs/chapter-1",
      "title": "Getting Started",
      "body": "Full text content...",
      "headings": [
        {"level": 2, "text": "Installation", "anchor": "#installation"},
        {"level": 2, "text": "Configuration", "anchor": "#configuration"}
      ],
      "auth": {
        "access": "public"
      }
    }
  ]
}

Optional: search-index.sqlite for large books (using FTS5).

Search Partial Template
<!-- templates/partials/search-results.html -->
{% if results %}
<ul class="search-results">
  {% for result in results %}
  <li>
    <a href="{{ result.path }}" hx-boost="true">
      <strong>{{ result.title }}</strong>
    </a>
    <p class="excerpt">{{ result.excerpt | safe }}</p>
  </li>
  {% endfor %}
</ul>
{% else %}
<p class="no-results">No results found for "{{ query }}"</p>
{% endif %}
Server Implementation
// Cloudflare Worker example
import Fuse from 'fuse.js';
import searchIndex from './search-index.json';

const fuse = new Fuse(searchIndex.documents, {
  keys: ['title', 'body', 'headings.text'],
  includeMatches: true,
  threshold: 0.3,
});

app.get('/docs/search', async (c) => {
  const query = c.req.query('q');
  const user = await getUser(c.req);

  let results = fuse.search(query);

  // Filter by auth
  results = results.filter(r =>
    r.item.auth.access === 'public' ||
    user?.roles.includes(r.item.auth.roles)
  );

  return c.html(await render('partials/search-results.html', {
    query,
    results: results.slice(0, 10)
  }));
});

Consequences

Positive
  • True hypermedia pattern
  • Authorization-aware search
  • No large index download
  • Minimal client JavaScript
  • Consistent with HTMX philosophy
Negative
  • Requires server for search
  • Network latency per query
  • Server must implement search logic
Mitigation
  • Debounce search input (300ms)
  • Loading indicator during search
  • Provide reference implementations (Workers, Deno, Node)
  • Optional SQLite FTS5 for large books

Configuration

[output.htmx.search]
enabled = true
index-format = "json"           # "json" | "sqlite"
heading-split-level = 3         # Index headings up to H3
include-body = true             # Include full text
max-excerpt-length = 200        # Snippet length

Related ADRs

References

Ngôn ngữ chính
Rust
Star
0
Fork
1
Chỉ số merge pull request
Không có pull request nào được merge trong 30 ngày

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của aRustyDev/mdbook-htmx

Tất cả issue của aRustyDev/mdbook-htmx

Issue tương tự

Thêm issue về Rust

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.