Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ const config = defineConfig({
"Plane is open-source, modern project management software for planning, tracking, and shipping work.",
details:
"This documentation covers workspaces, projects, work items, cycles, modules, pages and wikis, integrations, importers, automations, and Plane AI.",
// Per-page .md versions are already emitted by buildEnd() for the
// `Accept: text/markdown` rewrite in vercel.json, so the plugin only
// owns llms.txt / llms-full.txt.
// Per-page .md versions are already emitted by buildEnd() and served on
// `Accept: text/markdown` by middleware.ts, so the plugin only owns
// llms.txt / llms-full.txt.
generateLLMFriendlyDocsForEachPage: false,
// Don't inject invisible LLM-hint markup into rendered pages.
injectLLMHint: false,
Expand All @@ -82,7 +82,8 @@ const config = defineConfig({
},

buildEnd(siteConfig) {
// Copy source .md files into dist/ for Accept: text/markdown negotiation.
// Copy source .md files into dist/ so middleware.ts can serve them on
// Accept: text/markdown negotiation.
const srcDir = siteConfig.srcDir;
const outDir = siteConfig.outDir;

Expand Down
47 changes: 47 additions & 0 deletions middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/** @format */

import { next, rewrite } from "@vercel/functions";

// Markdown content negotiation for agents.
//
// VitePress emits an HTML page for every doc, and buildEnd() in
// .vitepress/config.ts copies each source `.md` twin into dist/ alongside it
// (e.g. /core-concepts/issues/overview -> /core-concepts/issues/overview.md).
//
// A `vercel.json` rewrite can't serve those `.md` files on
// `Accept: text/markdown` because vercel.json rewrites run *after* the
// filesystem, and every clean URL already resolves to an existing `.html` file,
// so the rewrite is never reached. Routing Middleware runs *before* the
// filesystem, so it can transparently serve the `.md` twin at the same URL.
//
// HTML stays the default for browsers. Vercel's CDN already includes the
// `Accept` request header in its cache key, so the HTML and markdown variants
// of the same URL are cached as separate entries — no `Vary` header needed.
export const config = {
// Page routes only: skip built assets, fonts, and any path that already has a
// file extension (`.md`, `.txt`, `.xml`, `.js`, `.css`, images, …).
matcher: ["/", "/((?!assets/|fonts/|.*\\.).*)"],
};

export default function middleware(request: Request): Response {
const accept = request.headers.get("accept") || "";
if (!accept.includes("text/markdown")) {
return next();
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const url = new URL(request.url);
let pathname = url.pathname;

// Map the clean URL to its emitted `.md` twin.
if (pathname === "/" || pathname === "") {
pathname = "/index";
} else if (pathname.endsWith("/")) {
pathname = pathname.slice(0, -1);
}
if (!pathname.endsWith(".md")) {
pathname = `${pathname}.md`;
}

url.pathname = pathname;
return rewrite(url);
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.1",
"@vercel/functions": "^3.7.1",
"@voidzero-dev/vitepress-theme": "^4.8.4",
"lucide-vue-next": "^0.577.0",
"medium-zoom": "^1.1.0",
Expand Down
Loading
Loading