Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

docs(mdbook-htmx): server side search

Aperta
#36 0 commenti 0 reazioni 1 assegnatario Vedi su GitHub

@aRustyDev ci sta già lavorando.

Dal 5/1/2026.

Valutazione

Questa issue non è ancora stata valutata.

Descrizione

documentation

Server-Side Search Implementation Guide

This document covers search implementation options for mdbook-htmx deployments, from simple in-memory solutions to enterprise-scale search engines.


Overview

mdbook-htmx generates search indexes at build time. When scopes are enabled, it generates separate indexes per scope to prevent information leakage.

Build Output
book/htmx/
├── search-index.json              # Full index (all pages)
├── search-index.developers.json   # Only developer-scoped pages
├── search-index.managers.json     # Only manager-scoped pages
└── search-index.sre.json          # Only SRE-scoped pages
Index Schema
{
  "config": {
    "heading_split_level": 3,
    "include_body": true
  },
  "scope": "developers", // null for full index
  "documents": [
    {
      "path": "/docs/chapter-1",
      "title": "Getting Started",
      "body": "Full text content...",
      "headings": [
        { "level": 2, "text": "Installation", "anchor": "#installation" }
      ],
      "auth": {
        "access": "roles",
        "roles": ["developer"]
      },
      "scopes": ["developers", "sre"]
    }
  ]
}

The server uses the appropriate scope index and applies authorization filtering.


Security: Scope-Aware & AuthZ-Aware Search

Why Separate Indexes?

A single index with runtime filtering still leaks information:

  • Page titles visible to unauthorized users
  • Content snippets may reveal sensitive data
  • Existence of pages is metadata leakage

Solution: Generate scope-specific indexes at build time. Each index only contains documents visible in that scope.

Defense in Depth: Runtime AuthZ Filtering

Even with scope-specific indexes, apply runtime authorization as defense in depth:

app.get("/docs/search", async (c) => {
  const query = c.req.query("q");
  const scope = c.req.query("scope") || "all";
  const user = c.get("user");

  // 1. Select scope-specific index
  const index = searchIndexes[scope] || searchIndexes.all;

  // 2. Search within scope
  let results = index.search(query);

  // 3. Runtime authorization filter (defense in depth)
  results = results.filter((result) => {
    const doc = result.item || result;

    // Public pages - always visible
    if (doc.auth?.access === "public") return true;

    // Authenticated pages - require login
    if (doc.auth?.access === "authenticated") return !!user;

    // Role-based pages - check roles
    if (doc.auth?.access === "roles") {
      return doc.auth.roles?.some((role) => user?.roles?.includes(role));
    }

    return false;
  });

  return c.html(renderSearchResults(results));
});
Anonymous User Search

Anonymous users:

  • Can only search the all scope (or public scope if configured)
  • Only see results from access: public pages
  • Cannot switch to other scopes
// Enforce public-only for anonymous users
if (!user) {
  scope = "all"; // Force to public scope
}

Solution Comparison

Solution Type Latency Index Size Limit Self-Hosted Free Tier Best For
Fuse.js In-memory <5ms ~50MB Yes Open source Small-medium books
MiniSearch In-memory <2ms ~50MB Yes Open source Small-medium, faster
FlexSearch In-memory <1ms ~100MB Yes Open source Speed-critical
Pagefind Static+WASM ~20ms Unlimited Yes Open source Large static sites
Meilisearch External ~10ms Unlimited Yes Cloud free tier Medium-large, self-hosted
Typesense External ~5ms Unlimited Yes Cloud free tier Performance-critical
Algolia SaaS ~10ms Unlimited No 10k searches/mo Enterprise, managed
Elasticsearch External ~50ms Unlimited Yes Open source Complex queries, analytics

1. In-Memory Solutions (Cloudflare Workers Compatible)

These solutions load the entire index into memory. Best for books with <1000 pages.

1.1 Fuse.js (Recommended for Simplicity)

Pros: Well-documented, fuzzy matching, configurable scoring
Cons: Slower on large datasets, ~50KB bundle

// worker.ts
import Fuse from "fuse.js";
import searchIndex from "./search-index.json";

const fuse = new Fuse(searchIndex.documents, {
  keys: [
    { name: "title", weight: 2.0 },
    { name: "headings.text", weight: 1.5 },
    { name: "body", weight: 1.0 },
  ],
  includeMatches: true,
  threshold: 0.3,
  ignoreLocation: true,
  minMatchCharLength: 2,
});

app.get("/docs/search", async (c) => {
  const query = c.req.query("q");
  if (!query || query.length < 2) {
    return c.html('<p class="search-hint">Type at least 2 characters</p>');
  }

  const results = fuse.search(query, { limit: 10 });

  return c.html(`
    <ul class="search-results">
      ${results
        .map(
          (r) => `
        <li>
          <a href="${r.item.path}" hx-boost="true">
            <strong>${highlightMatches(r.item.title, r.matches)}</strong>
          </a>
          <p class="excerpt">${getExcerpt(r.item.body, r.matches)}</p>
        </li>
      `,
        )
        .join("")}
    </ul>
  `);
});

function highlightMatches(
  text: string,
  matches: Fuse.FuseResultMatch[],
): string {
  // Highlight matching text with <mark> tags
  const titleMatch = matches?.find((m) => m.key === "title");
  if (!titleMatch) return text;

  let result = text;
  titleMatch.indices.reverse().forEach(([start, end]) => {
    result =
      result.slice(0, start) +
      "<mark>" +
      result.slice(start, end + 1) +
      "</mark>" +
      result.slice(end + 1);
  });
  return result;
}

Deployment Requirements:

  • Bundle fuse.js with worker (~50KB)
  • Include search-index.json in worker assets
  • Memory: ~2x index size
1.2 MiniSearch (Recommended for Speed)

Pros: Faster than Fuse.js, smaller bundle, prefix matching
Cons: Less fuzzy matching flexibility

import MiniSearch from "minisearch";
import searchIndex from "./search-index.json";

const miniSearch = new MiniSearch({
  fields: ["title", "body"],
  storeFields: ["title", "path"],
  searchOptions: {
    boost: { title: 2 },
    fuzzy: 0.2,
    prefix: true,
  },
});

miniSearch.addAll(searchIndex.documents.map((doc, id) => ({ id, ...doc })));

app.get("/docs/search", async (c) => {
  const query = c.req.query("q");
  const results = miniSearch.search(query, { limit: 10 });

  return c.html(`
    <ul class="search-results">
      ${results
        .map(
          (r) => `
        <li>
          <a href="${r.path}" hx-boost="true">${r.title}</a>
          <span class="score">${Math.round(r.score)}</span>
        </li>
      `,
        )
        .join("")}
    </ul>
  `);
});

Deployment Requirements:

  • Bundle minisearch (~8KB)
  • Include search-index.json
  • Memory: ~1.5x index size
1.3 FlexSearch (Maximum Speed)

Pros: Fastest in-memory search, language support
Cons: More complex API, larger memory footprint

import FlexSearch from "flexsearch";
import searchIndex from "./search-index.json";

const index = new FlexSearch.Document({
  document: {
    id: "path",
    index: ["title", "body"],
    store: ["title", "path"],
  },
  tokenize: "forward",
  language: "en",
});

searchIndex.documents.forEach((doc) => index.add(doc));

app.get("/docs/search", async (c) => {
  const query = c.req.query("q");
  const results = index.search(query, { limit: 10, enrich: true });

  // FlexSearch returns results per field, merge them
  const merged = results.flatMap((r) => r.result);

  return c.html(`
    <ul class="search-results">
      ${merged
        .map(
          (r) => `
        <li><a href="${r.doc.path}">${r.doc.title}</a></li>
      `,
        )
        .join("")}
    </ul>
  `);
});

2. Static Site Search: Pagefind

Pagefind generates a WASM-powered index at build time. Search happens client-side but uses minimal bandwidth.

Pros: No server needed, incremental loading, large site support
Cons: Client-side (not pure server), initial WASM load (~40KB)

Build Integration
# After mdbook-htmx build
npx pagefind --site book/htmx/pages --output-path book/htmx/pagefind
Configuration
# book.toml
[output.htmx.search]
engine = "pagefind"                    # Use Pagefind instead of JSON
exclude = ["partials/*", "404.html"]
Client Integration
<!-- In layout.html -->
<link href="/pagefind/pagefind-ui.css" rel="stylesheet" />
<script src="/pagefind/pagefind-ui.js"></script>

<div id="search"></div>
<script>
  new PagefindUI({ element: "#search", showImages: false });
</script>
Hybrid: Pagefind with HTMX

For server-rendered results with Pagefind's index:

// Server loads Pagefind WASM
import { Pagefind } from "@nicco.io/pagefind-preact"; // Or custom loader

app.get("/docs/search", async (c) => {
  const query = c.req.query("q");
  const results = await pagefind.search(query);
  const data = await Promise.all(results.slice(0, 10).map((r) => r.data()));

  return c.html(`
    <ul class="search-results">
      ${data
        .map(
          (r) => `
        <li>
          <a href="${r.url}" hx-boost="true">${r.meta.title}</a>
          <p>${r.excerpt}</p>
        </li>
      `,
        )
        .join("")}
    </ul>
  `);
});

3. External Search Engines

For large books or advanced features (facets, typo tolerance, analytics).

3.1 Meilisearch (Recommended Self-Hosted)

Pros: Fast, typo-tolerant, easy to deploy, Rust-based
Cons: Requires separate service

Secure Self-Hosting via Cloudflare Tunnel (Recommended)

For production deployments, never expose Meilisearch directly to the internet. Instead, use Cloudflare Workers as a proxy layer with Cloudflare Tunnel for private access.

Browser (HTMX)
   ↓ POST /search
Cloudflare Worker (Policy Enforcement)
   ↓ CF-Access headers
Cloudflare Tunnel (cloudflared)
   ↓ localhost:7700
Private Meilisearch (LAN/VPC)

Benefits over direct exposure:

Risk (Direct Exposure) Mitigation (Worker Proxy)
Index enumeration Fixed index, no discovery
Filter abuse Whitelisted filters only
Query DoS Rate limiting, query constraints
API key leakage Keys server-side only

Worker acts as policy enforcement layer:

  • Validates authentication before searching
  • Sanitizes query input (length limits, character filtering)
  • Enforces scope/authorization filters
  • Returns HTML partials (not JSON) for HTMX
  • Caches popular queries in KV

See examples/meilisearch-cf-tunnel.md for complete implementation including:

  • Cloudflare Tunnel configuration
  • Worker TypeScript with Hono
  • Service Token authentication
  • Build-time indexing script
  • HTMX integration patterns
Docker Compose Deployment
# docker-compose.yml
version: "3.8"
services:
  docs:
    build: .
    ports:
      - "8080:8080"
    environment:
      - MEILI_URL=http://meilisearch:7700
      - MEILI_MASTER_KEY=${MEILI_MASTER_KEY}
    depends_on:
      - meilisearch

  meilisearch:
    image: getmeili/meilisearch:v1.6
    ports:
      - "7700:7700"
    environment:
      - MEILI_MASTER_KEY=${MEILI_MASTER_KEY}
    volumes:
      - meili_data:/meili_data

volumes:
  meili_data:
Kubernetes Deployment
# meilisearch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: meilisearch
spec:
  replicas: 1
  selector:
    matchLabels:
      app: meilisearch
  template:
    metadata:
      labels:
        app: meilisearch
    spec:
      containers:
        - name: meilisearch
          image: getmeili/meilisearch:v1.6
          ports:
            - containerPort: 7700
          env:
            - name: MEILI_MASTER_KEY
              valueFrom:
                secretKeyRef:
                  name: meilisearch-secrets
                  key: master-key
          volumeMounts:
            - name: data
              mountPath: /meili_data
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: meilisearch-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: meilisearch
spec:
  selector:
    app: meilisearch
  ports:
    - port: 7700
      targetPort: 7700
Index Population (Build Step)
// scripts/index-search.ts
import { MeiliSearch } from "meilisearch";
import searchIndex from "../book/htmx/search-index.json";

const client = new MeiliSearch({
  host: process.env.MEILI_URL,
  apiKey: process.env.MEILI_MASTER_KEY,
});

async function indexDocuments() {
  const index = client.index("docs");

  // Configure index
  await index.updateSettings({
    searchableAttributes: ["title", "body", "headings.text"],
    displayedAttributes: ["title", "path", "body"],
    rankingRules: [
      "words",
      "typo",
      "proximity",
      "attribute",
      "sort",
      "exactness",
    ],
  });

  // Add documents
  await index.addDocuments(
    searchIndex.documents.map((doc, id) => ({ id, ...doc })),
  );

  console.log("Indexed", searchIndex.documents.length, "documents");
}

indexDocuments();
Server Integration
import { MeiliSearch } from "meilisearch";

const client = new MeiliSearch({
  host: process.env.MEILI_URL,
  apiKey: process.env.MEILI_SEARCH_KEY, // Read-only key
});

app.get("/docs/search", async (c) => {
  const query = c.req.query("q");

  const results = await client.index("docs").search(query, {
    limit: 10,
    attributesToHighlight: ["title", "body"],
    highlightPreTag: "<mark>",
    highlightPostTag: "</mark>",
    attributesToCrop: ["body"],
    cropLength: 50,
  });

  return c.html(`
    <ul class="search-results">
      ${results.hits
        .map(
          (hit) => `
        <li>
          <a href="${hit.path}" hx-boost="true">
            ${hit._formatted.title}
          </a>
          <p class="excerpt">${hit._formatted.body}</p>
        </li>
      `,
        )
        .join("")}
    </ul>
  `);
});
3.2 Typesense

Pros: Fastest external engine, C++ based, great typo tolerance
Cons: Less cloud availability than Algolia

Docker Compose
services:
  typesense:
    image: typesense/typesense:0.25.2
    ports:
      - "8108:8108"
    environment:
      - TYPESENSE_API_KEY=${TYPESENSE_API_KEY}
      - TYPESENSE_DATA_DIR=/data
    volumes:
      - typesense_data:/data

volumes:
  typesense_data:
Server Integration
import Typesense from "typesense";

const client = new Typesense.Client({
  nodes: [{ host: "typesense", port: 8108, protocol: "http" }],
  apiKey: process.env.TYPESENSE_SEARCH_KEY,
});

app.get("/docs/search", async (c) => {
  const query = c.req.query("q");

  const results = await client.collections("docs").documents().search({
    q: query,
    query_by: "title,body",
    highlight_full_fields: "title,body",
    per_page: 10,
  });

  return c.html(`
    <ul class="search-results">
      ${results.hits
        .map(
          (hit) => `
        <li>
          <a href="${hit.document.path}">${hit.highlights[0]?.snippet || hit.document.title}</a>
        </li>
      `,
        )
        .join("")}
    </ul>
  `);
});
3.3 Algolia (Managed SaaS)

Pros: Zero ops, global CDN, excellent UI components
Cons: Pricing at scale, vendor lock-in

import algoliasearch from "algoliasearch";

const client = algoliasearch(
  process.env.ALGOLIA_APP_ID,
  process.env.ALGOLIA_SEARCH_KEY,
);
const index = client.initIndex("docs");

app.get("/docs/search", async (c) => {
  const query = c.req.query("q");

  const { hits } = await index.search(query, {
    hitsPerPage: 10,
    attributesToHighlight: ["title", "body"],
  });

  return c.html(`
    <ul class="search-results">
      ${hits
        .map(
          (hit) => `
        <li>
          <a href="${hit.path}">${hit._highlightResult.title.value}</a>
        </li>
      `,
        )
        .join("")}
    </ul>
  `);
});

4. Deployment Architecture

4.1 Cloudflare Workers (In-Memory, Simple Sites)
┌─────────────┐     ┌─────────────────────────────────┐
│   Browser   │────▶│     Cloudflare Worker           │
│             │     │  ┌───────────────────────────┐  │
│             │     │  │  Fuse.js + search-index   │  │
│             │     │  │  (in-memory, <50MB)       │  │
│             │◀────│  └───────────────────────────┘  │
└─────────────┘     └─────────────────────────────────┘

Limits:

  • Worker memory: 128MB
  • Index must fit in bundled assets
  • Best for <1000 pages
4.2 Cloudflare Workers + Tunnel (Recommended for Self-Hosted Search)

For larger sites or advanced search features, use Workers as a policy enforcement proxy to self-hosted Meilisearch via Cloudflare Tunnel.

┌─────────────┐     ┌─────────────────────────────────┐
│   Browser   │────▶│     Cloudflare Worker           │
│   (HTMX)    │     │  ┌───────────────────────────┐  │
│             │     │  │  Policy Enforcement       │  │
│             │     │  │  - Auth validation        │  │
│             │     │  │  - Query sanitization     │  │
│             │     │  │  - Rate limiting          │  │
│             │     │  └───────────┬───────────────┘  │
│             │     │              │                  │
│             │     └──────────────┼──────────────────┘
│             │                    │ CF-Access headers
│             │     ┌──────────────▼──────────────────┐
│             │     │     Cloudflare Tunnel           │
│             │     │     (cloudflared)               │
│             │     └──────────────┬──────────────────┘
│             │                    │
│             │     ┌──────────────▼──────────────────┐
│             │     │     Private Meilisearch         │
│             │◀────│     (localhost:7700)            │
└─────────────┘     └─────────────────────────────────┘

Benefits:

  • Meilisearch never internet-facing
  • Service Token authentication via CF Access
  • Worker returns HTML partials for HTMX
  • Cache layer in Worker KV
  • Best for 1000+ pages with advanced features

Performance: ~30-50ms E2E (5-20ms Worker→Tunnel, <10ms Meilisearch)

See examples/meilisearch-cf-tunnel.md for complete implementation

4.3 Docker Compose (Self-Hosted)
┌─────────────┐     ┌─────────────────────────────────┐
│   Browser   │────▶│        Reverse Proxy            │
│             │     │       (Caddy/Nginx)             │
│             │     └────────────┬────────────────────┘
│             │                  │
│             │     ┌────────────┴────────────────────┐
│             │     │                                 │
│             │     ▼                                 ▼
│             │ ┌──────────────┐          ┌───────────────────┐
│             │ │  Docs Server │──────────│   Meilisearch     │
│             │ │  (Hono/Deno) │          │   (Port 7700)     │
│             │ └──────────────┘          └───────────────────┘
└─────────────┘
# docker-compose.yml
version: "3.8"
services:
  caddy:
    image: caddy:2-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
    depends_on:
      - docs

  docs:
    build: .
    environment:
      - MEILI_URL=http://meilisearch:7700
      - MEILI_SEARCH_KEY=${MEILI_SEARCH_KEY}

  meilisearch:
    image: getmeili/meilisearch:v1.6
    environment:
      - MEILI_MASTER_KEY=${MEILI_MASTER_KEY}
    volumes:
      - meili_data:/meili_data

volumes:
  caddy_data:
  meili_data:
4.4 Kubernetes (Production)
┌─────────────┐     ┌─────────────────────────────────┐
│   Browser   │────▶│        Ingress Controller       │
│             │     │       (nginx-ingress)           │
│             │     └────────────┬────────────────────┘
│             │                  │
│             │     ┌────────────┴────────────────────┐
│             │     │        Service Mesh             │
│             │     │         (optional)              │
│             │     └────────────┬────────────────────┘
│             │                  │
│             │     ┌────────────┴────────────────────┐
│             │     │                                 │
│             │     ▼                                 ▼
│             │ ┌──────────────┐          ┌───────────────────┐
│             │ │  Docs        │──────────│   Meilisearch     │
│             │ │  Deployment  │          │   StatefulSet     │
│             │ │  (3 replicas)│          │   (1 replica)     │
│             │ └──────────────┘          └───────────────────┘
└─────────────┘                                    │
                                          ┌────────┴────────┐
                                          │  PVC (10Gi)     │
                                          └─────────────────┘

Full Kubernetes manifests:

# docs-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: docs
spec:
  replicas: 3
  selector:
    matchLabels:
      app: docs
  template:
    metadata:
      labels:
        app: docs
    spec:
      containers:
        - name: docs
          image: myregistry/docs-server:latest
          ports:
            - containerPort: 8080
          env:
            - name: MEILI_URL
              value: "http://meilisearch:7700"
            - name: MEILI_SEARCH_KEY
              valueFrom:
                secretKeyRef:
                  name: search-secrets
                  key: meili-search-key
          resources:
            requests:
              memory: "128Mi"
              cpu: "100m"
            limits:
              memory: "256Mi"
              cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
  name: docs
spec:
  selector:
    app: docs
  ports:
    - port: 80
      targetPort: 8080
---
# meilisearch-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: meilisearch
spec:
  serviceName: meilisearch
  replicas: 1
  selector:
    matchLabels:
      app: meilisearch
  template:
    metadata:
      labels:
        app: meilisearch
    spec:
      containers:
        - name: meilisearch
          image: getmeili/meilisearch:v1.6
          ports:
            - containerPort: 7700
          env:
            - name: MEILI_MASTER_KEY
              valueFrom:
                secretKeyRef:
                  name: search-secrets
                  key: meili-master-key
          volumeMounts:
            - name: data
              mountPath: /meili_data
          resources:
            requests:
              memory: "512Mi"
              cpu: "250m"
            limits:
              memory: "2Gi"
              cpu: "1000m"
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 10Gi
---
apiVersion: v1
kind: Service
metadata:
  name: meilisearch
spec:
  selector:
    app: meilisearch
  ports:
    - port: 7700
      targetPort: 7700
  clusterIP: None # Headless for StatefulSet

5. Decision Matrix

Choose Based on Book Size
Book Size Pages Recommended Alternative
Small <100 Fuse.js MiniSearch
Medium 100-500 MiniSearch Meilisearch
Large 500-5000 Meilisearch Typesense
Very Large >5000 Typesense Elasticsearch
Choose Based on Deployment
Deployment Recommended Notes
Cloudflare Workers Fuse.js/MiniSearch In-memory, bundled
Static hosting (Netlify, Vercel) Pagefind Client-side WASM
Docker Compose Meilisearch Easy setup
Kubernetes Meilisearch/Typesense StatefulSet pattern
Enterprise Algolia Managed, compliant
Choose Based on Features
Feature Fuse.js MiniSearch Pagefind Meilisearch Typesense Algolia
Typo tolerance ⚠️ ⚠️ ✅ ✅ ✅ ✅
Prefix matching ✅ ✅ ✅ ✅ ✅ ✅
Faceted search ❌ ❌ ❌ ✅ ✅ ✅
Highlighting ⚠️ ⚠️ ✅ ✅ ✅ ✅
Multi-language ❌ ❌ ✅ ✅ ✅ ✅
Authorization ✅ ✅ ❌ ✅ ✅ ✅
Analytics ❌ ❌ ❌ ⚠️ ⚠️ ✅

Legend: ✅ Built-in | ⚠️ Partial/Manual | ❌ Not supported


6. Authorization-Aware Search

For sites with access control, filter search results based on user permissions.

Manifest Integration
{
  "documents": [
    {
      "path": "/docs/public-page",
      "auth": { "access": "public" }
    },
    {
      "path": "/docs/admin-page",
      "auth": { "access": "roles", "roles": ["admin"] }
    }
  ]
}
Server-Side Filtering
app.get("/docs/search", async (c) => {
  const query = c.req.query("q");
  const user = c.get("user");

  let results = fuse.search(query);

  // Filter by authorization
  results = results.filter((r) => {
    const page = r.item;
    if (page.auth.access === "public") return true;
    if (page.auth.access === "authenticated") return !!user;
    if (page.auth.access === "roles") {
      return page.auth.roles.some((role) => user?.roles.includes(role));
    }
    return false;
  });

  return c.html(renderResults(results.slice(0, 10)));
});

7. Configuration Reference

[output.htmx.search]
enabled = true

# Index format: "json" | "sqlite" | "pagefind"
index-format = "json"

# What to index
heading-split-level = 3        # Index up to H3
include-body = true            # Full text search
max-body-length = 10000        # Truncate long documents

# Exclude patterns
exclude = ["drafts/*", "internal/*"]

# External engine (if using Meilisearch/Typesense/Algolia)
[output.htmx.search.external]
engine = "meilisearch"         # meilisearch | typesense | algolia
index-name = "docs"
# Credentials in environment variables:
# MEILI_URL, MEILI_MASTER_KEY, MEILI_SEARCH_KEY

References

Lingua principale
Rust
Stelle
0
Fork
1
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di aRustyDev/mdbook-htmx

Tutte le issue di aRustyDev/mdbook-htmx

Issue simili

Altre issue su Rust

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.