Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
24 changes: 21 additions & 3 deletions docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,25 @@ const config = {
],
],

plugins:
process.env.NEXT_PUBLIC_POSTHOG_APIKEY &&
plugins: [
[
"@signalwire/docusaurus-plugin-llms-txt",
{
siteTitle: "Tigris Blog",
siteDescription:
"Blog for Tigris, a globally distributed, S3-compatible object storage service. Single endpoint: https://t3.storage.dev",
content: {
enableMarkdownFiles: true,
enableLlmsFullTxt: true,
includeDocs: false,
includeBlog: true,
includePages: false,
},
depth: 2,
logLevel: 1,
},
],
...(process.env.NEXT_PUBLIC_POSTHOG_APIKEY &&
process.env.NEXT_PUBLIC_POSTHOG_HOST
? [
[
Expand All @@ -86,7 +103,8 @@ const config = {
},
],
]
: [],
: []),
],

themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
Expand Down
83 changes: 83 additions & 0 deletions middleware.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { next } from "@vercel/edge";

// Skip middleware for static assets
const STATIC_EXTENSIONS =
/\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|json|xml|txt|md|map|webp|avif)$/i;

// Known AI agent and bot User-Agent patterns
const AGENT_UA_PATTERNS = [
/\bClaudeBot\b/i,
/\bChatGPT-User\b/i,
/\bGPTBot\b/i,
/\bGoogle-Extended\b/i,
/\bPerplexityBot\b/i,
/\bCohere-AI\b/i,
/\bAnthropic\b/i,
/\bClaude\b/i,
/\bOAI-SearchBot\b/i,
/\bYouBot\b/i,
/\bAI2Bot\b/i,
/\bApplebot-Extended\b/i,
/\bMeta-ExternalAgent\b/i,
/\bMeta-ExternalFetcher\b/i,
/\bFirecrawl\b/i,
/\bJinaBot\b/i,
];

export const config = {
matcher: "/blog/:path*",
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Middleware redirects non-post blog paths to likely nonexistent files

Medium Severity

The config.matcher of "/blog/:path*" causes the middleware to redirect AI agents on all blog sub-paths — including tag listing pages (/blog/tags/), specific tag pages (/blog/tags/foo/), and pagination pages (/blog/page/2/) — to .md equivalents like /blog/tags.md or /blog/page/2.md. The PR description states the plugin generates "per-post .md files", so these non-post paths likely have no corresponding .md file, resulting in AI agents receiving a 307 redirect followed by a 404.

Additional Locations (1)
Fix in Cursor Fix in Web


function isAgent(request) {
const accept = request.headers.get("accept") || "";
if (accept.includes("text/markdown")) {
return true;
}

const ua = request.headers.get("user-agent") || "";
return AGENT_UA_PATTERNS.some((pattern) => pattern.test(ua));
}

function rewriteToMarkdown(pathname, requestUrl) {
// Rewrite HTML URL to its .md equivalent:
// /blog/some-post/ -> /blog/some-post.md
// /blog/tags/foo/ -> /blog/tags/foo.md
// /blog/ -> /blog/index.md
const subpath = pathname.replace(/^\/blog\/?/, "").replace(/\/+$/, "");
let mdPath;
if (!subpath) {
mdPath = "/blog/index.md";
} else {
mdPath = `/blog/${subpath}.md`;
}

return new Response(null, {
status: 307,
headers: {
Location: new URL(mdPath, requestUrl).toString(),
Vary: "Accept, User-Agent",
},
});
}

export default function middleware(request) {
const url = new URL(request.url);
const pathname = url.pathname;

// Skip static assets
if (STATIC_EXTENSIONS.test(pathname)) {
return next();
}

if (isAgent(request)) {
return rewriteToMarkdown(pathname, request.url);
}

// For normal HTML requests, add Link header pointing to llms.txt for discovery
return next({
headers: {
Link: '</blog/llms.txt>; rel="alternate"; type="text/plain"',
Vary: "Accept, User-Agent",
},
});
}
Loading
Loading