Skip to content

feat(search): replace Elasticsearch search with Pagefind#1018

Merged
DeepDiver1975 merged 3 commits into
masterfrom
switch-search-to-pagefind
Jun 16, 2026
Merged

feat(search): replace Elasticsearch search with Pagefind#1018
DeepDiver1975 merged 3 commits into
masterfrom
switch-search-to-pagefind

Conversation

@DeepDiver1975

Copy link
Copy Markdown
Member

What

Replaces the docs UI's custom Elasticsearch-browser search with Pagefind.

The old search only activated when ELASTICSEARCH_NODE was set and required an external Elasticsearch cluster — provisioned, indexed, and reachable from the browser (host/index/read-auth exposed as data-* attributes). Pagefind indexes the static site at build time and serves search entirely from the generated bundle, so search works with no backend service.

Changes

  • Drop the elasticsearch-browser dependency and the bespoke src/js/vendor/elastic.js client
  • Load Pagefind's Default UI (pagefind-ui.js / pagefind-ui.css) from siteRootPath (where docs-server emits the index) and mount it on a plain #search div. The ELASTICSEARCH_NODE env gate is gone, so search is always present
  • Replace the hand-rolled .search .results CSS with Pagefind's --pagefind-ui-* variables mapped onto the ownCloud brand tokens (--oc-navy primary, brand text/border/tag colors, body font); keep the navbar width constraints and the high-z-index results drawer so the panel floats above page content

Builds on the corporate-identity theme (#1014), whose --oc-navy token the search styling reuses.

Follow-up needed on docs-server

This PR wires the UI to consume a Pagefind index at {siteRootPath}/pagefind/. docs-server must run the Pagefind indexer over the built site (and stop provisioning Elasticsearch) for search to function end to end.

Verification

⚠️ npm run lint / npm run bundle were not run — both currently fail on a pre-existing del@8 ESM-vs-gulp require() incompatibility on Node 18, unrelated to this change. Needs a bundle build + visual check of the search drawer (rendering + brand colors) before merge.

The docs UI shipped a custom Elasticsearch-browser search that only
activated when ELASTICSEARCH_NODE was set, requiring an external
Elasticsearch cluster to be provisioned, indexed, and reachable from the
browser (host/index/read-auth exposed as data-attributes). Pagefind
indexes the static site at build time and serves search entirely from
the generated bundle, so search works with no backend service.

- drop the elasticsearch-browser dependency and the bespoke
  src/js/vendor/elastic.js client
- load Pagefind's Default UI (pagefind-ui.js / pagefind-ui.css) from
  siteRootPath, where docs-server emits the index, and mount it on a
  plain #search div; the ELASTICSEARCH_NODE env gate is gone, so search
  is always present
- replace the hand-rolled .search .results CSS with Pagefind's
  --pagefind-ui-* variables mapped onto the ownCloud brand tokens
  (--oc-navy primary, brand text/border/tag colors, body font); keep the
  navbar width constraints and the z-index results drawer so the panel
  floats above page content

Not verified with npm run lint / bundle: both fail in this environment
on a pre-existing del@8 ESM-vs-gulp require() incompatibility on Node 18,
unrelated to this change.

Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com>
The initial Pagefind integration mounted the Default UI inside the ~150px
navbar search slot, reusing the old Elasticsearch-era results-drawer CSS.
The Default UI is built for a full-width page region, so in the navbar the
results drawer overflowed and overlapped the breadcrumb row and the input
looked cramped. Pagefind recommends the Component UI for new integrations.

Switch to the Component UI modal: a navbar trigger button opens a centered
modal overlay (also via the Ctrl/Cmd+K shortcut), keeping the navbar clean
and giving search results room to render.

- header-content.hbs: replace the `#search` mount with `pagefind-config`
  (points at the index under siteRootPath), `pagefind-modal-trigger` and
  `pagefind-modal`
- head-styles.hbs / footer-scripts.hbs: load pagefind-component-ui.css and
  pagefind-component-ui.js (ES module, self-registers the web components);
  drop the pagefind-ui.js include and the `new PagefindUI()` init
- owncloud.css: remove the dead Default-UI / Elasticsearch drawer CSS and
  theme the modal via its --pf-* custom properties on :root (the components
  apply `all: initial`, so site CSS cannot cascade in)

The pagefind-component-ui.{js,css} assets are already emitted by
`pagefind --site`, so no docs-server / playbook change is needed.

Verified on the full production build (Node 22): npm run bundle passes, and
on the served site the navbar shows the trigger button and Ctrl+K opens the
themed modal overlay.

Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com>
@DeepDiver1975

Copy link
Copy Markdown
Member Author

Updated the search UX based on testing the local build: the Pagefind Default UI rendered poorly inside the navbar (it's designed for a full-width region, so the results drawer overflowed and overlapped the breadcrumb row). Switched to the Pagefind Component UI modal — a navbar trigger button that opens a centered modal overlay (also via Ctrl/Cmd+K).

Commit 1921f94:

  • header-content.hbspagefind-config + pagefind-modal-trigger + pagefind-modal
  • head-styles.hbs / footer-scripts.hbs — load pagefind-component-ui.{css,js} (ES module, self-registers), drop the new PagefindUI() init
  • owncloud.css — remove the dead Default-UI drawer CSS; theme via --pf-* vars on :root

The pagefind-component-ui.* assets already ship from pagefind --site, so no playbook change is needed. Verified on the full production build (Node 22): npm run bundle passes; the navbar shows the trigger and Ctrl+K opens the themed modal.

@DeepDiver1975

Copy link
Copy Markdown
Member Author
image

@phil-davis phil-davis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks reasonable. The screenshot of the UI verifies that it renders OK.

@DeepDiver1975

Copy link
Copy Markdown
Member Author

Code review

Front-end half of the Elasticsearch → Pagefind migration. Pairs with owncloud/docs#5106both must land together (this expects a Pagefind index that only #5106 produces).

Strengths

  • Clean removal of the env-gated ES widget and the 90-line jQuery elastic.js client.
  • Component UI (modal) is the right choice over the Default UI for a navbar placement — well-commented, themed via the documented --pf-* variables on :root (correct, since the components apply all: initial and site CSS can't cascade in).
  • Uses {{{siteRootPath}}} consistently, so assets resolve at any page depth. Verified on a full production build: resolves to ./pagefind/ on the root page and ../../../../../pagefind/ on deep pages; the pagefind-component-ui.{js,css} assets are emitted by the pagefind CLI.

Issues & suggestions

🟡 Orphaned dependencies. elastic.js was the only consumer of jquery and handlebars (no other src/ references), but both remain in package.json:

"handlebars": "^4.7.9",
"jquery": "^4.0.0",

They're now dead weight. The repo ships depcheck — worth running to confirm and prune. (Sanity-check handlebars isn't relied on by the Antora build pipeline itself before removing.)

🟢 type="module" has no fallback. pagefind-component-ui.js loads as an ES module, so legacy browsers silently get no search. Acceptable given Pagefind's own browser matrix — noting it's a behavioral change from the old widget.

🟢 Cross-version duplicate results (tracked separately). Because Pagefind indexes the whole tree, each page is indexed once per published version (server/10.15 / /latest / /next) → duplicate hits. A canonical-URL-based fix is staged separately; flagging for completeness.

Verdict

Approve, with one pre-merge ask: prune the orphaned jquery/handlebars (or note why kept). Confirm this merges in lockstep with owncloud/docs#5106 — merging either alone breaks search.

jquery was only used by the Elasticsearch search client
(src/js/vendor/elastic.js), which was removed in the switch to Pagefind.
Nothing else in src/ or the build pipeline references it (confirmed with
depcheck and a full-tree grep), so it is now dead weight.

handlebars is intentionally kept: tasks/build-preview.js requires it to
compile the .hbs layouts/partials for `npm run preview`.

Verified: npm run bundle and gulp build:preview both pass with jquery
removed.

Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com>
@DeepDiver1975

Copy link
Copy Markdown
Member Author

Addressed the orphaned-dependency finding in 045f85a.

  • Removed jquery — it was used only by src/js/vendor/elastic.js (the Elasticsearch client this PR deletes). Confirmed orphaned via depcheck and a full-tree grep; nothing else in src/ or the build references it.

Correcting my earlier review: I'd flagged handlebars as also orphaned — that was wrong. tasks/build-preview.js requires it to compile the .hbs layouts/partials for npm run preview, and depcheck correctly does not flag it. Kept.

Scope note: depcheck reports a few other unused deps (npm-check-updates, standard, tslib, …), but those are pre-existing and unrelated to the Pagefind migration, so I left them out of this PR.

Verified: npm run bundle and gulp build:preview both pass with jquery removed.

@DeepDiver1975 DeepDiver1975 merged commit 48b250f into master Jun 16, 2026
2 checks passed
@DeepDiver1975 DeepDiver1975 deleted the switch-search-to-pagefind branch June 16, 2026 11:16

@DeepDiver1975 DeepDiver1975 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Automated review by Claude Code review agent.

Overview

This PR swaps the bespoke Elasticsearch-browser search for Pagefind's Component UI. Net effect is a strong simplification: it deletes the 90-line jQuery elastic.js client, drops the jquery and elasticsearch-browser runtime dependencies, removes the ELASTICSEARCH_NODE env gate (search is now always rendered), and replaces ~80 lines of hand-rolled .search .results CSS with --pf-* brand-token mapping. The three <pagefind-config> / <pagefind-modal-trigger> / <pagefind-modal> tags plus the module script and stylesheet are loaded from {siteRootPath}/pagefind/, where docs-server is expected to emit the Pagefind index. The diff is internally consistent and the integration matches Pagefind's documented Component UI API. The build/bundle implications are clean.

Code quality / style

  • Dependency removal is clean. jquery and elasticsearch-browser were used only by the deleted src/js/vendor/elastic.js. The build globs js/vendor/*.js and browserifies each file (tasks/build.js), so removing the single file that did require('jquery') / require('elasticsearch-browser') removes the only consumers — no dangling bundle references. depcheck should stay green.
  • Pagefind is loaded at runtime, not bundled. pagefind-component-ui.js is included via <script type="module" src="{{{siteRootPath}}}/pagefind/..."> and the matching CSS via <link>, so it deliberately bypasses the gulp/browserify pipeline. This is the correct approach for Pagefind (the bundle is generated by the indexer over the built site, not shipped in the UI bundle).
  • Brand tokens all resolve. Every token consumed in the new :root block — --body-font-family, --color-jet-50, --color-smoke-90, --color-smoke-50, --oc-link, --oc-teal — is defined in vars.css / owncloud-vars.css, both of which are @import-ed before owncloud.css in site.css. Ordering is correct.
  • siteRootPath is a legitimate Antora UI-model variable (already used for the logo link in this same partial), so the new references are sound.
  • Good inline documentation. The CSS and HBS comments explaining the all: initial style isolation and the bundle-path wiring are accurate and helpful for the next maintainer.

Specific suggestions

  1. The PR description is stale relative to the code. The body describes the Default UI (pagefind-ui.js / pagefind-ui.css, --pagefind-ui-* variables, a #search div). The actual diff ships the Component UI (pagefind-component-ui.js/.css, --pf-* variables, the <pagefind-*> web components). The code is the correct/consistent one — please update the PR description so reviewers and the docs-server follow-up aren't misled about which artifact to index/serve.
  2. CSS variable coverage is partial. Only 8 of the many --pf-* tokens are set. Notably unset: --pf-text-secondary / --pf-text-muted (result excerpts/metadata), --pf-modal-backdrop, --pf-modal-max-width, the focus-ring tokens (--pf-outline-focus), and --pf-dropdown-z-index. The old CSS deliberately used z-index: 999999 to float results above page content; if the Component UI's default modal z-index is lower than ownCloud's sticky navbar/toolbar, the results overlay could render behind them. Worth a visual check and possibly setting the modal/dropdown z-index explicitly.
  3. --oc-navy is referenced in the PR text but not in the diff. The body says the navy token is reused as the primary color, but the new :root block maps --pf-border-focus to --oc-link and --pf-mark to --oc-teal — no --oc-navy. Minor, but another sign the description drifted from the implementation.
  4. Loss of the min-length / debounce behavior is now Pagefind's concern. The old client only queried at ≥3 chars. Pagefind has its own debouncing, so this is fine, but worth confirming during the visual check that there's no noticeable request storm.

Potential issues / risks

  • End-to-end search is broken until docs-server ships the indexer. This is correctly called out as a follow-up, but it bears emphasizing: merging this alone removes working (Elasticsearch-gated) search and points the UI at {siteRootPath}/pagefind/, which won't exist until docs-server runs pagefind over the built site and stops provisioning Elasticsearch. If the /pagefind/ directory is absent, the module script and CSS will 404 and the search trigger will be non-functional. Coordinate the merge of both PRs, or the live docs site loses search in the interim.
  • CI may behave differently from the author's local run. The PR notes lint/bundle were not run because of a del@8 ESM-vs-gulp incompatibility "on Node 18." However .github/workflows/ci.yml runs on Node 22, where require(esm) is supported — so CI may well pass npm run lint / npm run bundle even though the local Node 18 build failed. Conversely, if it does fail in CI, the build/release job gates on it. Either way, rely on the CI result rather than the local-Node-18 caveat; please confirm the CI build is green before merge.
  • No automated coverage for the search UI (none existed before either), so the brand-color + drawer rendering really does need the manual visual check the author flagged.
  • Always-on search markup. Removing the {{#if env.ELASTICSEARCH_NODE}} gate means the <pagefind-*> elements always render. If a downstream consumer builds the UI without running the indexer, they get visible-but-dead search rather than no search. Acceptable trade-off given Pagefind's static model, just noting the behavior change.

Overall this is a clean, well-scoped change that meaningfully reduces complexity and removes an external-service dependency. Main asks before merge: refresh the PR description to match the Component UI implementation, confirm CI is green, do the visual/z-index check on the modal, and coordinate the docs-server indexer rollout.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants