diff --git a/.eslintrc.js b/.eslintrc.js
index 59369755a0..b8e6aa686e 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -2,6 +2,7 @@
/* eslint quotes: ["error", "double"] */
module.exports = {
+ "ignorePatterns": ["docs/_site/**", "**/dist/**", "**/node_modules/**"],
"env": {
"node": true,
"es6": true,
diff --git a/.stylelintrc.js b/.stylelintrc.js
index f3276222e8..b883f26813 100644
--- a/.stylelintrc.js
+++ b/.stylelintrc.js
@@ -6,5 +6,16 @@ module.exports = {
// MarkBind generates some blank CSS files when initialising a site,
// which violates the no-empty-source rule
"no-empty-source": null
- }
+ },
+ "overrides": [
+ {
+ // pagefind uses BEM-style class names (e.g., .pagefind-ui__result) as default.
+ // Since we currently style pagefind's default UI classes, we need to ignore the kebab-case rule here.
+ // This override should be removed once we no longer rely on pagefind's default CSS classes.
+ "files": ["**/pagefindSearchBar/**"],
+ "rules": {
+ "selector-class-pattern": null
+ }
+ }
+ ]
};
diff --git a/docs/userGuide/makingTheSiteSearchable.md b/docs/userGuide/makingTheSiteSearchable.md
index 4e28680c49..0bc6baeba4 100644
--- a/docs/userGuide/makingTheSiteSearchable.md
+++ b/docs/userGuide/makingTheSiteSearchable.md
@@ -37,6 +37,114 @@ You can add a search bar component to your website to allow users to search the
+
+
+
+
+## Using Pagefind (Beta)
+
+
+MarkBind now supports [Pagefind](https://pagefind.app/), a static low-bandwidth search library, as a built-in feature. This provides full-text search capabilities without external services.
+
+
+This is a beta feature and will be refined in future updates. To use it, you must have enableSearch: true in your site.json (this is the default).
+
+
+
+The Pagefind index is currently only generated during a full site build (e.g., markbind build). It will not repeatedly update during live reload (markbind serve) when you modify pages. You must restart the server (re-run markbind serve) or rebuild to refresh the search index.
+
+
+To add the Pagefind search bar to your page, simply insert the following `div` where you want it to appear:
+
+```md
+
+```
+
+MarkBind will automatically inject the necessary scripts and styles to render the search UI.
+
+The following UI will be rendered, which is provided by Pagefind:
+
+
+
+
+
+### Ignoring Individual Elements from Pagefind Search
+
+You can exclude specific elements from the search index by adding the `data-pagefind-ignore` attribute to them:
+
+```html
+
+
This content will be in your search index.
+
+ This content and all its children will be excluded from search.
+
+
+```
+
+For more details, see the [Pagefind documentation on removing individual elements](https://pagefind.app/docs/indexing/#removing-individual-elements-from-the-index).
+
+### Using Pagefind Configuration
+
+You can customize Pagefind's indexing behavior by adding a `pagefind` configuration in your `site.json`. This allows you to control which content is indexed and how search works.
+
+#### Excluding Content from Search Index
+
+You can use the `exclude_selectors` option to exclude specific elements from the search index. This is useful if you are migrating from Algolia and want to reuse your existing CSS class selectors.
+
+In your `site.json`:
+
+```json
+{
+ "pagefind": {
+ "exclude_selectors": [".algolia-no-index", "[class*='algolia-no-index']"]
+ }
+}
+```
+
+This tells Pagefind to exclude any element with the `algolia-no-index` class (or containing it in a space-separated list) from the search index, similar to using `data-pagefind-ignore`.
+
+#### Limiting Which Pages Are Searchable
+
+You can use the `glob` option to limit which pages are indexed by Pagefind. This is useful when you want search results to only show pages from specific sections of your site.
+
+In your `site.json`:
+
+```json
+{
+ "pagefind": {
+ "glob": [
+ "devGuide",
+ "userGuide/*"
+ ]
+ }
+}
+```
+
+MarkBind supports glob patterns and will automatically append `.html` to your patterns if not specified. For example:
+- `"devGuide"` becomes `"devGuide/**/*.html"`
+- `"devGuide/*"` becomes `"devGuide/*.html"`
+- `"**/devGuide/**"` becomes `"**/devGuide/**/*.html"`
+- `"*.html"` remains `"*.html"` (no change needed)
+
+Only pages matching these glob patterns will appear in search results. This can be particularly useful for:
+- Multi-site setups where you want to search only specific sections
+- Including only certain directories from search results
+
+For more details on glob patterns, see the [Pagefind documentation](https://pagefind.app/docs/config-options/#glob).
+
+
+
+Additional Pagefind configuration options may be supported in future releases:
+
+- **`root_selector`**: Allows specifying a custom root element for indexing (default: `html`). Useful for sites with specific content containers.
+- **`force_language`**: Forces a specific language for indexing (e.g., `"en"`, `"pt"`). Improves search accuracy for multilingual sites.
+
+
+
+
+
+
+
## Using External Search Services
MarkBind sites can use Algolia Doc Search services easily via the Algolia plugin. Unlike the built-in search, Algolia provides full-text search. See the panel below for more info.
diff --git a/package-lock.json b/package-lock.json
index 7e016c8985..909f4c26a5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4646,6 +4646,84 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@pagefind/darwin-arm64": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.4.0.tgz",
+ "integrity": "sha512-2vMqkbv3lbx1Awea90gTaBsvpzgRs7MuSgKDxW0m9oV1GPZCZbZBJg/qL83GIUEN2BFlY46dtUZi54pwH+/pTQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@pagefind/darwin-x64": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.4.0.tgz",
+ "integrity": "sha512-e7JPIS6L9/cJfow+/IAqknsGqEPjJnVXGjpGm25bnq+NPdoD3c/7fAwr1OXkG4Ocjx6ZGSCijXEV4ryMcH2E3A==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@pagefind/freebsd-x64": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.4.0.tgz",
+ "integrity": "sha512-WcJVypXSZ+9HpiqZjFXMUobfFfZZ6NzIYtkhQ9eOhZrQpeY5uQFqNWLCk7w9RkMUwBv1HAMDW3YJQl/8OqsV0Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@pagefind/linux-arm64": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.4.0.tgz",
+ "integrity": "sha512-PIt8dkqt4W06KGmQjONw7EZbhDF+uXI7i0XtRLN1vjCUxM9vGPdtJc2mUyVPevjomrGz5M86M8bqTr6cgDp1Uw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@pagefind/linux-x64": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.4.0.tgz",
+ "integrity": "sha512-z4oddcWwQ0UHrTHR8psLnVlz6USGJ/eOlDPTDYZ4cI8TK8PgwRUPQZp9D2iJPNIPcS6Qx/E4TebjuGJOyK8Mmg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@pagefind/windows-x64": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.4.0.tgz",
+ "integrity": "sha512-NkT+YAdgS2FPCn8mIA9bQhiBs+xmniMGq1LFPDhcFn0+2yIUEiIG06t7bsZlhdjknEQRTSdT7YitP6fC5qwP0g==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"dev": true,
@@ -16563,6 +16641,23 @@
"node": ">=16 || 14 >=14.17"
}
},
+ "node_modules/pagefind": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.4.0.tgz",
+ "integrity": "sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g==",
+ "license": "MIT",
+ "bin": {
+ "pagefind": "lib/runner/bin.cjs"
+ },
+ "optionalDependencies": {
+ "@pagefind/darwin-arm64": "1.4.0",
+ "@pagefind/darwin-x64": "1.4.0",
+ "@pagefind/freebsd-x64": "1.4.0",
+ "@pagefind/linux-arm64": "1.4.0",
+ "@pagefind/linux-x64": "1.4.0",
+ "@pagefind/windows-x64": "1.4.0"
+ }
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"dev": true,
@@ -22044,6 +22139,7 @@
"material-icons": "^1.9.1",
"moment": "^2.29.4",
"nunjucks": "3.2.4",
+ "pagefind": "^1.4.0",
"path-is-inside": "^1.0.2",
"simple-git": "^3.22.0",
"url-parse": "^1.5.10",
diff --git a/packages/cli/test/functional/testUtil/compare.ts b/packages/cli/test/functional/testUtil/compare.ts
index d522f5a19c..0ec15e8fd5 100644
--- a/packages/cli/test/functional/testUtil/compare.ts
+++ b/packages/cli/test/functional/testUtil/compare.ts
@@ -13,6 +13,21 @@ const TEST_BLACKLIST = ignore().add([
'*.log',
'*.woff',
'*.woff2',
+ '*.pf_fragment',
+ '*.pf_index',
+ '*.pf_meta',
+ '*.wasm.pagefind',
+ 'wasm.unknown.pagefind',
+]);
+
+// File patterns to completely ignore (skip both content AND existence comparison)
+// These are files with non-deterministic content/hashes that differ across environments (e.g., PageFind)
+const TEST_BLACKLIST_EXISTENCE = ignore().add([
+ '*.pf_fragment',
+ '*.pf_index',
+ '*.pf_meta',
+ '*.wasm.pagefind',
+ 'wasm.unknown.pagefind',
]);
const CRLF_REGEX = /\r\n/g;
@@ -91,6 +106,11 @@ function compare(root: string, expectedSiteRelativePath = 'expected', siteRelati
actualPaths = actualPaths.filter(p => !ignoredPaths.includes(p));
expectedPaths = expectedPaths.filter(p => !ignoredPaths.includes(p));
+ // Filter out files with non-deterministic hashes (e.g., PageFind generated files)
+ // These files have content-dependent hashes that differ across environments
+ actualPaths = actualPaths.filter(p => !TEST_BLACKLIST_EXISTENCE.ignores(p));
+ expectedPaths = expectedPaths.filter(p => !TEST_BLACKLIST_EXISTENCE.ignores(p));
+
let error = false;
if (expectedPaths.length !== actualPaths.length) {
throw new Error('Unequal number of files! '
diff --git a/packages/cli/test/functional/test_site/expected/bugs/index.html b/packages/cli/test/functional/test_site/expected/bugs/index.html
index 7d3c9c86d9..32e743057d 100644
--- a/packages/cli/test/functional/test_site/expected/bugs/index.html
+++ b/packages/cli/test/functional/test_site/expected/bugs/index.html
@@ -16,6 +16,7 @@
+
@@ -359,5 +360,6 @@