diff --git a/press.config.tsx b/press.config.tsx index 73df00b06..9734315bd 100644 --- a/press.config.tsx +++ b/press.config.tsx @@ -1,6 +1,7 @@ import { defineMintlifyI18n, mintlifyPlugin, readMintlifyDocs } from "@fumapress/mintlify"; import { defineConfig } from "fumapress"; import { fumadocsMdx } from "fumapress/adapters/mdx"; +import { flexsearchPlugin } from "fumapress/plugins/flexsearch"; import { openapiPlugin } from "fumapress/plugins/openapi"; import { robotsPlugin } from "fumapress/plugins/robots"; import { update } from "fumadocs-core/source"; @@ -145,6 +146,38 @@ export default defineConfig({ // One registration is intentional: its adapter and loader plugin handle all // OpenAPI virtual pages, regardless of which content source generated them. .plugins( + flexsearchPlugin({ + async buildIndex(page) { + const title = page.data.title ?? page.path; + const description = page.data.description; + const sourceStructuredData = typeof page.data.structuredData === "function" + ? await page.data.structuredData() + : page.data.structuredData; + // FlexSearch's static serialization expands multilingual documents + // considerably. Index titles, descriptions, a few headings, and the + // opening content so search remains useful without exceeding Pages' + // hard 25 MiB per-file limit. OpenAPI endpoints intentionally omit the + // schema body, which is duplicated across every generated operation. + const isOpenAPI = page.url.includes("/api-reference/"); + const headings = isOpenAPI + ? [] + : sourceStructuredData.headings.slice(0, 4).map((heading) => ({ + ...heading, + content: heading.content.slice(0, 100), + })); + const openingContent = isOpenAPI + ? [] + : sourceStructuredData.contents.slice(0, 2).map((content) => content.content.slice(0, 300)); + const structuredData = { + headings, + contents: [{ + heading: undefined, + content: [title, description, ...openingContent, page.url].filter(Boolean).join(" "), + }], + }; + return { id: page.url, title, description, url: page.url, structuredData }; + }, + }), mintlifyPlugin({ features: { navbar: false } }), openapiPlugin({ server: enOpenAPI }), robotsPlugin({ diff --git a/tests/fumapress-static-build.test.mjs b/tests/fumapress-static-build.test.mjs index 393454fd4..4815b8da8 100644 --- a/tests/fumapress-static-build.test.mjs +++ b/tests/fumapress-static-build.test.mjs @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { access, readdir, readFile } from "node:fs/promises"; +import { access, readdir, readFile, stat } from "node:fs/promises"; import path from "node:path"; import test from "node:test"; import { collectMdxDocuments } from "../scripts/prepare-fumapress.mjs"; @@ -229,3 +229,9 @@ test("generated pages contain no internal .html route links", async () => { } assert.deepEqual(offenders, []); }); + + +test("static search stays within Cloudflare Pages' 25 MiB file limit", async () => { + const search = await stat(path.join(publicRoot, "api/search")); + assert.ok(search.size <= 25 * 1024 * 1024, `search artifact is ${search.size} bytes`); +});