diff --git a/content/shared/vibe-coding.mdx b/content/shared/vibe-coding.mdx
index 05ec04f7..c1db278a 100644
--- a/content/shared/vibe-coding.mdx
+++ b/content/shared/vibe-coding.mdx
@@ -13,12 +13,11 @@ We've built a few tools to help you Vibe Code using the knowledge of the Superwa
- [Editor AI](#editor-ai): Build and refine a paywall directly inside the visual editor using AI Chat or an external MCP-compatible agent.
And right here in the Superwall Docs:
+
- [Superwall AI](#superwall-ai)
- [Docs Links](#docs-links)
- [LLMs.txt](#llmstxt)
-
-
## Superwall Agents
[Superwall Agents](/agents) is the dedicated Superwall AI workspace. Use it when you want to analyze experiment results, use selected Superwall organization context, work with files available to the active hosted machine, create charts and reports, suggest new experiments, schedule recurring prompts, or connect webhook-driven workflows.
@@ -58,7 +57,6 @@ Use Editor AI when the task is about changing the design, layout, products, vari
Superwall AI is available in the bottom right 💬 and is a great place to start if you have a question or issue.
-
## Docs Links
At the top of each page of the Superwall Docs (including this one!):
@@ -70,24 +68,26 @@ Also in the **Open** dropdown menu, you can access these options:
- **View as Markdown**: to view the page in Markdown format
- **Open in ChatGPT**, **Open in Claude**: to open the page in the respective AI tool and add the page as context for your conversation
+You can also add `.md` to the end of any docs page URL to open that page as Markdown. For example, `https://superwall.com/docs/ios/quickstart/install.md`.
## LLMs.txt
+
The Superwall Docs website has `llms.txt` and `llms-full.txt` files, in total and for each SDK, that you can use to add context to your LLMs.
`llms.txt` is a summary of the docs with links to each page.
`llms-full.txt` is the full text of all of the docs.
-| SDK | Summary | Full Text |
-| -------- | ------- | --------- |
-| All | [`llms.txt`](https://superwall.com/docs/llms.txt) | [`llms-full.txt`](https://superwall.com/docs/llms-full.txt) |
-| Dashboard | [`llms-dashboard.txt`](https://superwall.com/docs/llms-dashboard.txt) | [`llms-full-dashboard.txt`](https://superwall.com/docs/llms-full-dashboard.txt) |
-| iOS | [`llms-ios.txt`](https://superwall.com/docs/llms-ios.txt) | [`llms-full-ios.txt`](https://superwall.com/docs/llms-full-ios.txt) |
-| Android | [`llms-android.txt`](https://superwall.com/docs/llms-android.txt) | [`llms-full-android.txt`](https://superwall.com/docs/llms-full-android.txt) |
-| Flutter | [`llms-flutter.txt`](https://superwall.com/docs/llms-flutter.txt) | [`llms-full-flutter.txt`](https://superwall.com/docs/llms-full-flutter.txt) |
-| Expo | [`llms-expo.txt`](https://superwall.com/docs/llms-expo.txt) | [`llms-full-expo.txt`](https://superwall.com/docs/llms-full-expo.txt) |
-| React Native (Deprecated) | [`llms-react-native.txt`](https://superwall.com/docs/llms-react-native.txt) | [`llms-full-react-native.txt`](https://superwall.com/docs/llms-full-react-native.txt) |
-| Integrations | [`llms-integrations.txt`](https://superwall.com/docs/llms-integrations.txt) | [`llms-full-integrations.txt`](https://superwall.com/docs/llms-full-integrations.txt) |
-| Web Checkout | [`llms-web-checkout.txt`](https://superwall.com/docs/llms-web-checkout.txt) | [`llms-full-web-checkout.txt`](https://superwall.com/docs/llms-full-web-checkout.txt) |
+| SDK | Summary | Full Text |
+| ------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
+| All | [`llms.txt`](https://superwall.com/docs/llms.txt) | [`llms-full.txt`](https://superwall.com/docs/llms-full.txt) |
+| Dashboard | [`dashboard/llms.txt`](https://superwall.com/docs/dashboard/llms.txt) | [`dashboard/llms-full.txt`](https://superwall.com/docs/dashboard/llms-full.txt) |
+| iOS | [`ios/llms.txt`](https://superwall.com/docs/ios/llms.txt) | [`ios/llms-full.txt`](https://superwall.com/docs/ios/llms-full.txt) |
+| Android | [`android/llms.txt`](https://superwall.com/docs/android/llms.txt) | [`android/llms-full.txt`](https://superwall.com/docs/android/llms-full.txt) |
+| Flutter | [`flutter/llms.txt`](https://superwall.com/docs/flutter/llms.txt) | [`flutter/llms-full.txt`](https://superwall.com/docs/flutter/llms-full.txt) |
+| Expo | [`expo/llms.txt`](https://superwall.com/docs/expo/llms.txt) | [`expo/llms-full.txt`](https://superwall.com/docs/expo/llms-full.txt) |
+| React Native (Deprecated) | [`react-native/llms.txt`](https://superwall.com/docs/react-native/llms.txt) | [`react-native/llms-full.txt`](https://superwall.com/docs/react-native/llms-full.txt) |
+| Integrations | [`integrations/llms.txt`](https://superwall.com/docs/integrations/llms.txt) | [`integrations/llms-full.txt`](https://superwall.com/docs/integrations/llms-full.txt) |
+| Web Checkout | [`web-checkout/llms.txt`](https://superwall.com/docs/web-checkout/llms.txt) | [`web-checkout/llms-full.txt`](https://superwall.com/docs/web-checkout/llms-full.txt) |
To minimize token use, we recommend using the files specific to your SDK.
diff --git a/plugins/remark-image-paths.ts b/plugins/remark-image-paths.ts
index c95743eb..0d5d9635 100644
--- a/plugins/remark-image-paths.ts
+++ b/plugins/remark-image-paths.ts
@@ -1,4 +1,7 @@
import { visit } from "unist-util-visit";
+import { normalizeDocsAssetPath } from "../src/lib/docs-asset-path";
+
+export { normalizeDocsAssetPath };
/*
Fixes the image paths
@@ -10,42 +13,6 @@ output:

*/
-const DOCS_PREFIX = "/docs";
-const SPECIAL_URL_PATTERN = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
-
-export function normalizeDocsAssetPath(url: string): string {
- const trimmed = url.trim();
- if (!trimmed) return trimmed;
-
- // Keep external/special schemes and fragment-only links untouched.
- if (trimmed.startsWith("#") || trimmed.startsWith("//") || SPECIAL_URL_PATTERN.test(trimmed)) {
- return trimmed;
- }
-
- const [, pathPart = "", suffix = ""] = trimmed.match(/^([^?#]*)([?#].*)?$/) ?? [];
- const segments = pathPart
- .replace(/^\/+/, "")
- .split("/")
- .filter(Boolean);
-
- const normalizedSegments: string[] = [];
- for (const segment of segments) {
- if (segment === "." || segment === "") continue;
- if (segment === "..") {
- normalizedSegments.pop();
- continue;
- }
- normalizedSegments.push(segment);
- }
-
- const normalizedPath = `/${normalizedSegments.join("/")}`;
- if (normalizedPath === DOCS_PREFIX || normalizedPath.startsWith(`${DOCS_PREFIX}/`)) {
- return `${normalizedPath}${suffix}`;
- }
-
- return `${DOCS_PREFIX}${normalizedPath === "/" ? "" : normalizedPath}${suffix}`;
-}
-
export default function remarkImagePaths() {
return (tree: any) => {
visit(tree, "image", (node) => {
diff --git a/plugins/remark-markdown-image-map.test.ts b/plugins/remark-markdown-image-map.test.ts
new file mode 100644
index 00000000..dd3daff6
--- /dev/null
+++ b/plugins/remark-markdown-image-map.test.ts
@@ -0,0 +1,57 @@
+import assert from "node:assert/strict";
+import { describe, test } from "node:test";
+import { unified } from "unified";
+import remarkMdx from "remark-mdx";
+import remarkParse from "remark-parse";
+import remarkMarkdownImageMap from "./remark-markdown-image-map";
+
+async function collectMarkdownImageMap(markdown: string) {
+ const processor = unified().use(remarkParse).use(remarkMdx).use(remarkMarkdownImageMap);
+ const file = { data: {} };
+ const tree = processor.parse(markdown);
+
+ await processor.run(tree, file as any);
+ return file.data as {
+ markdownImageMap?: Record;
+ "mdx-export"?: Array<{ name: string; value: unknown }>;
+ };
+}
+
+describe("remarkMarkdownImageMap", () => {
+ test("exports normalized image URLs keyed by Fumadocs placeholders", async () => {
+ const data = await collectMarkdownImageMap(`
+
+
+
+`);
+
+ assert.deepEqual(data.markdownImageMap, {
+ __img0: "/docs/images/first.png",
+ __img1: "/docs/images/second.png?size=large#preview",
+ });
+ assert.deepEqual(data["mdx-export"], [
+ {
+ name: "markdownImageMap",
+ value: data.markdownImageMap,
+ },
+ ]);
+ });
+
+ test("includes JSX image sources without shifting markdown image placeholders", async () => {
+ const data = await collectMarkdownImageMap(`
+
+
+
+
+
+
+
+`);
+
+ assert.deepEqual(data.markdownImageMap, {
+ __img0: "/docs/images/first.png",
+ __img1: "/docs/images/third.png",
+ __img2: "/docs/images/frame.png",
+ });
+ });
+});
diff --git a/plugins/remark-markdown-image-map.ts b/plugins/remark-markdown-image-map.ts
new file mode 100644
index 00000000..3111ce1f
--- /dev/null
+++ b/plugins/remark-markdown-image-map.ts
@@ -0,0 +1,62 @@
+import { visit } from "unist-util-visit";
+import { normalizeDocsAssetPath } from "../src/lib/docs-asset-path";
+
+const imagePlaceholderPattern = /^__img\d+$/;
+
+function getAttributeValue(node: any, name: string): string | undefined {
+ for (const attribute of node.attributes ?? []) {
+ if (attribute.name !== name) continue;
+
+ const value = attribute.value;
+ if (typeof value === "string") return value;
+ if (typeof value?.value === "string") return value.value;
+ }
+}
+
+function getImageUrl(node: any): string | undefined {
+ if (typeof node.url === "string") return normalizeDocsAssetPath(node.url);
+
+ const src = getAttributeValue(node, "src");
+ if (!src || imagePlaceholderPattern.test(src)) return undefined;
+
+ return normalizeDocsAssetPath(src);
+}
+
+export default function remarkMarkdownImageMap() {
+ return (tree: any, file: any) => {
+ const imageMap: Record = {};
+ let imageIndex = 0;
+ const jsxImageUrls: string[] = [];
+
+ visit(tree, "image", (node: any) => {
+ const url = getImageUrl(node);
+ if (!url) return;
+
+ imageMap[`__img${imageIndex}`] = url;
+ imageIndex += 1;
+ });
+
+ visit(tree, ["mdxJsxFlowElement", "mdxJsxTextElement"], (node: any) => {
+ if (node.name !== "img") return;
+
+ const url = getImageUrl(node);
+ if (!url) return;
+
+ jsxImageUrls.push(url);
+ });
+
+ for (const url of jsxImageUrls) {
+ imageMap[`__img${imageIndex}`] = url;
+ imageIndex += 1;
+ }
+
+ if (Object.keys(imageMap).length > 0) {
+ file.data.markdownImageMap = imageMap;
+ file.data["mdx-export"] ??= [];
+ file.data["mdx-export"].push({
+ name: "markdownImageMap",
+ value: imageMap,
+ });
+ }
+ };
+}
diff --git a/source.config.ts b/source.config.ts
index 0710dda9..12996ba0 100644
--- a/source.config.ts
+++ b/source.config.ts
@@ -8,20 +8,66 @@ import remarkFollowExport from "./plugins/remark-follow-export";
import remarkDirective from "remark-directive";
import { remarkInclude } from "fumadocs-mdx/config";
import remarkSdkFilter from "./plugins/remark-sdk-filter";
+import remarkMarkdownImageMap from "./plugins/remark-markdown-image-map";
+import type { LLMsOptions } from "fumadocs-core/mdx-plugins";
const isDevelopment = process.env.NODE_ENV === "development";
+const markdownHeadingHandler: NonNullable["heading"] = (
+ node,
+ _parent,
+ state,
+ info,
+) => {
+ const depth = Math.min(Math.max(node.depth, 1), 6);
+ return `${"#".repeat(depth)} ${state.containerPhrasing(node, info)}`;
+};
+
+const processedMarkdownOptions: LLMsOptions = {
+ handlers: {
+ heading: markdownHeadingHandler,
+ },
+ mdxAsPlaceholder: [
+ "Accordion",
+ "AccordionGroup",
+ "Callout",
+ "Card",
+ "CardGroup",
+ "CodeGroup",
+ "Frame",
+ "Info",
+ "Mermaid",
+ "Note",
+ "Step",
+ "Steps",
+ "Tab",
+ "Tabs",
+ "Tip",
+ "Warning",
+ ],
+};
+
+const markdownImageMapPassthroughPlugin = {
+ "index-file": {
+ serverOptions(options: any) {
+ options.doc ??= {};
+ options.doc.passthroughs ??= [];
+ options.doc.passthroughs.push("markdownImageMap");
+ },
+ },
+};
export const docs = defineDocs({
dir: "content/docs",
docs: {
async: isDevelopment,
postprocess: {
- includeProcessedMarkdown: true,
+ includeProcessedMarkdown: processedMarkdownOptions,
},
},
});
export default defineConfig({
+ plugins: [markdownImageMapPassthroughPlugin],
mdxOptions: {
// Shiki highlighting is one of the most expensive parts of the MDX pipeline.
// Keep it in builds, but skip it in local dev so first-page SSR doesn't have
@@ -29,6 +75,7 @@ export default defineConfig({
rehypeCodeOptions: isDevelopment ? false : undefined,
remarkPlugins: (existing) => [
remarkImagePaths,
+ remarkMarkdownImageMap,
remarkLinkPaths,
remarkFollowExport,
...existing,
diff --git a/src/lib/docs-asset-path.ts b/src/lib/docs-asset-path.ts
new file mode 100644
index 00000000..76a9fbbd
--- /dev/null
+++ b/src/lib/docs-asset-path.ts
@@ -0,0 +1,40 @@
+const DOCS_PREFIX = "/docs";
+const SPECIAL_URL_PATTERN = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
+
+export function normalizeDocsAssetPath(url: string): string {
+ const trimmed = url.trim();
+ if (!trimmed) return trimmed;
+
+ if (trimmed.startsWith("#") || trimmed.startsWith("//") || SPECIAL_URL_PATTERN.test(trimmed)) {
+ return trimmed;
+ }
+
+ const [, pathPart = "", suffix = ""] = trimmed.match(/^([^?#]*)([?#].*)?$/) ?? [];
+ const segments = pathPart.replace(/^\/+/, "").split("/").filter(Boolean);
+
+ const normalizedSegments: string[] = [];
+ for (const segment of segments) {
+ if (segment === "." || segment === "") continue;
+ if (segment === "..") {
+ normalizedSegments.pop();
+ continue;
+ }
+ normalizedSegments.push(segment);
+ }
+
+ const normalizedPath = `/${normalizedSegments.join("/")}`;
+ if (normalizedPath === DOCS_PREFIX || normalizedPath.startsWith(`${DOCS_PREFIX}/`)) {
+ return `${normalizedPath}${suffix}`;
+ }
+
+ return `${DOCS_PREFIX}${normalizedPath === "/" ? "" : normalizedPath}${suffix}`;
+}
+
+export function resolveDocsAssetUrl(url: string, baseUrl?: string) {
+ const normalized = normalizeDocsAssetPath(url);
+ if (!baseUrl || normalized.startsWith("#") || SPECIAL_URL_PATTERN.test(normalized)) {
+ return normalized;
+ }
+
+ return new URL(normalized, baseUrl).toString();
+}
diff --git a/src/lib/llms.ts b/src/lib/llms.ts
index 60907756..7db001f6 100644
--- a/src/lib/llms.ts
+++ b/src/lib/llms.ts
@@ -3,6 +3,10 @@ export const llmsSectionConfigs = {
label: "Dashboard",
urlPrefix: "/docs/dashboard",
},
+ agents: {
+ label: "Agents",
+ urlPrefix: "/docs/agents",
+ },
ios: {
label: "iOS",
urlPrefix: "/docs/ios",
@@ -51,7 +55,25 @@ export function getLLMSectionConfig(section?: string) {
return llmsSectionConfigs[section as LLMSection];
}
-export function getPagesForLLMSection(pages: LLMPageLike[], section?: string) {
+export function buildLLMSummaryPath(section: string) {
+ const config = getLLMSectionConfig(section);
+ return config ? `${config.urlPrefix}/llms.txt` : buildLegacyLLMSummaryPath(section);
+}
+
+export function buildLLMFullPath(section: string) {
+ const config = getLLMSectionConfig(section);
+ return config ? `${config.urlPrefix}/llms-full.txt` : buildLegacyLLMFullPath(section);
+}
+
+export function buildLegacyLLMSummaryPath(section: string) {
+ return `/docs/llms-${section}.txt`;
+}
+
+export function buildLegacyLLMFullPath(section: string) {
+ return `/docs/llms-full-${section}.txt`;
+}
+
+export function getPagesForLLMSection(pages: TPage[], section?: string) {
const config = getLLMSectionConfig(section);
if (!config) return pages;
@@ -69,6 +91,7 @@ function getExamplePathForSection(section: string): string {
unity: "/docs/unity/guides/game-controller-input.md",
"react-native": "/docs/react-native/guides/advanced/game-controller-support.md",
dashboard: "/docs/dashboard/dashboard-settings/overview-settings-apple-search-ads.md",
+ agents: "/docs/agents/create-an-agent.md",
integrations: "/docs/integrations/amplitude.md",
"web-checkout": "/docs/web-checkout/overview.md",
};
@@ -104,16 +127,22 @@ export function buildLLMSummaryTextFromPages(pages: LLMPageLike[], section?: str
// Top-level: list all platform-specific docs
lines.push("## Platform-specific docs", "");
for (const [key, val] of Object.entries(llmsSectionConfigs)) {
- lines.push(`- [${val.label} Documentation](/docs/llms-${key}.txt)`);
+ lines.push(`- [${val.label} Documentation](${buildLLMSummaryPath(key)}) ([full text](${buildLLMFullPath(key)}))`);
}
- lines.push("", "## All Pages", "");
+ lines.push(
+ "",
+ "Legacy root-level aliases such as `/docs/llms-ios.txt` and `/docs/llms-full-ios.txt` remain supported.",
+ "",
+ "## All Pages",
+ "",
+ );
} else {
// Section-specific: link back to top-level + list other sections
lines.push("## Other sections", "");
lines.push("- [All Documentation](/docs/llms.txt)");
for (const [key, val] of Object.entries(llmsSectionConfigs)) {
if (key !== section) {
- lines.push(`- [${val.label} Documentation](/docs/llms-${key}.txt)`);
+ lines.push(`- [${val.label} Documentation](${buildLLMSummaryPath(key)})`);
}
}
lines.push("");
@@ -133,6 +162,14 @@ export async function buildLLMSummaryText(section?: string) {
return buildLLMSummaryTextFromPages(source.getPages(), section);
}
+export async function buildLLMSummaryResponseForSection(section: string) {
+ if (!getLLMSectionConfig(section)) {
+ return new Response("LLMS section not found", { status: 404 });
+ }
+
+ return buildLLMResponse(await buildLLMSummaryText(section));
+}
+
export async function buildLLMFullText(section?: string) {
const config = getLLMSectionConfig(section);
const title = config ? `${config.label} Documentation` : "Superwall Documentation";
@@ -143,6 +180,14 @@ export async function buildLLMFullText(section?: string) {
return `# ${title}\n\n${scanned.join("\n\n")}`;
}
+export async function buildLLMFullResponseForSection(section: string) {
+ if (!getLLMSectionConfig(section)) {
+ return new Response("LLMS section not found", { status: 404 });
+ }
+
+ return buildLLMResponse(await buildLLMFullText(section));
+}
+
export function buildLLMResponse(body: string) {
return new Response(body, {
headers: {
diff --git a/src/lib/markdown-alternate.ts b/src/lib/markdown-alternate.ts
new file mode 100644
index 00000000..366d7192
--- /dev/null
+++ b/src/lib/markdown-alternate.ts
@@ -0,0 +1,40 @@
+import { DOCS_BASE } from "./url-base";
+
+const MARKDOWN_ROUTE_PREFIX = `${DOCS_BASE}/llms.mdx/docs/`;
+
+function normalizeDocsPath(pathname: string): string {
+ const normalized = pathname.replace(/\/+$/, "");
+ return normalized || DOCS_BASE;
+}
+
+export function buildMarkdownAlternatePath(pathname: string): string {
+ const normalized = normalizeDocsPath(pathname);
+ if (normalized === DOCS_BASE) return MARKDOWN_ROUTE_PREFIX;
+ return `${normalized}.md`;
+}
+
+export function buildHtmlPathFromMarkdownRoute(slugs: string[]): string {
+ if (slugs.length === 0) return DOCS_BASE;
+ return `${DOCS_BASE}/${slugs.join("/")}`;
+}
+
+export function appendHeaderValue(headers: Headers, name: string, value: string) {
+ const current = headers.get(name);
+ if (!current) {
+ headers.set(name, value);
+ return;
+ }
+
+ const values = current.split(",").map((entry) => entry.trim().toLowerCase());
+ if (!values.includes(value.toLowerCase())) {
+ headers.set(name, `${current}, ${value}`);
+ }
+}
+
+export function appendVaryAccept(headers: Headers) {
+ appendHeaderValue(headers, "Vary", "Accept");
+}
+
+export function buildMarkdownAlternateLinkHeader(pathname: string): string {
+ return `<${buildMarkdownAlternatePath(pathname)}>; rel="alternate"; type="text/markdown"`;
+}
diff --git a/src/lib/markdown-route.ts b/src/lib/markdown-route.ts
new file mode 100644
index 00000000..15d45297
--- /dev/null
+++ b/src/lib/markdown-route.ts
@@ -0,0 +1,33 @@
+import { buildHtmlPathFromMarkdownRoute } from "./markdown-alternate";
+import { buildLLMSummaryText } from "./llms";
+import { buildSdkSelectorMarkdown } from "./sdk-selector-markdown";
+import { getPageMarkdownText, source } from "./source";
+
+export function slugsFromMarkdownSplat(splat?: string) {
+ return (splat ?? "").replace(/\/+$/, "").split("/").filter(Boolean);
+}
+
+export async function getMarkdownForPageSlugs(slugs: string[], request: Request) {
+ if (slugs[0] === "sdk") return buildSdkSelectorMarkdown(slugs);
+
+ const page = source.getPage(slugs);
+ if (!page) return undefined;
+
+ return getPageMarkdownText(page, { baseUrl: request.url });
+}
+
+export async function getMarkdownRouteBody(slugs: string[], request: Request) {
+ if (slugs.length === 0) return buildLLMSummaryText();
+ return getMarkdownForPageSlugs(slugs, request);
+}
+
+export function buildMarkdownRouteResponse(body: BodyInit | null, slugs: string[]) {
+ return new Response(body, {
+ headers: {
+ "Content-Type": "text/markdown; charset=utf-8",
+ "Access-Control-Allow-Origin": "*",
+ Vary: "Accept",
+ Link: `<${buildHtmlPathFromMarkdownRoute(slugs)}>; rel="canonical"; type="text/html"`,
+ },
+ });
+}
diff --git a/src/lib/sdk-selector-markdown.test.ts b/src/lib/sdk-selector-markdown.test.ts
new file mode 100644
index 00000000..7559b2fc
--- /dev/null
+++ b/src/lib/sdk-selector-markdown.test.ts
@@ -0,0 +1,37 @@
+import assert from "node:assert/strict";
+import { describe, test } from "node:test";
+import { buildSdkSelectorMarkdown } from "./sdk-selector-markdown";
+
+describe("buildSdkSelectorMarkdown", () => {
+ test("renders sdk selector markdown with platform-specific markdown links", () => {
+ assert.equal(
+ buildSdkSelectorMarkdown(["sdk"]),
+ [
+ "# Choose your SDK",
+ "",
+ "SDK docs are shared across platforms. Pick your SDK to continue.",
+ "",
+ "- [iOS](/docs/ios.md)",
+ "- [Android](/docs/android.md)",
+ "- [Flutter](/docs/flutter.md)",
+ "- [Expo](/docs/expo.md)",
+ ].join("\n"),
+ );
+ });
+
+ test("renders sdk placeholder subpaths as concrete platform markdown links", () => {
+ assert.equal(
+ buildSdkSelectorMarkdown(["sdk", "guides", "advanced", "game-controller-support"]),
+ [
+ "# Choose your SDK",
+ "",
+ "SDK docs are shared across platforms. Pick your SDK to open this page for the platform you use.",
+ "",
+ "- [iOS](/docs/ios/guides/advanced/game-controller-support.md)",
+ "- [Android](/docs/android/guides/advanced/game-controller-support.md)",
+ "- [Flutter](/docs/flutter/guides/advanced/game-controller-support.md)",
+ "- [Expo](/docs/expo/guides/advanced/game-controller-support.md)",
+ ].join("\n"),
+ );
+ });
+});
diff --git a/src/lib/sdk-selector-markdown.ts b/src/lib/sdk-selector-markdown.ts
new file mode 100644
index 00000000..6d4c1049
--- /dev/null
+++ b/src/lib/sdk-selector-markdown.ts
@@ -0,0 +1,23 @@
+import { buildMarkdownAlternatePath } from "./markdown-alternate";
+import { replaceSdkPlaceholder, SDK_SELECTOR_SLUGS, type SdkSlug } from "./sdk-navigation";
+
+const sdkLabels: Record<(typeof SDK_SELECTOR_SLUGS)[number], string> = {
+ ios: "iOS",
+ android: "Android",
+ flutter: "Flutter",
+ expo: "Expo",
+};
+
+export function buildSdkSelectorMarkdown(slugs: string[]) {
+ const requestedPath = `/docs/${slugs.join("/")}`;
+ const description =
+ slugs.length === 1
+ ? "SDK docs are shared across platforms. Pick your SDK to continue."
+ : "SDK docs are shared across platforms. Pick your SDK to open this page for the platform you use.";
+ const links = SDK_SELECTOR_SLUGS.map((sdk) => {
+ const pagePath = replaceSdkPlaceholder(requestedPath, sdk as SdkSlug);
+ return `- [${sdkLabels[sdk]}](${buildMarkdownAlternatePath(pagePath)})`;
+ });
+
+ return ["# Choose your SDK", "", description, "", ...links].join("\n");
+}
diff --git a/src/lib/source.ts b/src/lib/source.ts
index a6ba994c..5e1cf08c 100644
--- a/src/lib/source.ts
+++ b/src/lib/source.ts
@@ -1,5 +1,10 @@
import { InferPageType, loader } from "fumadocs-core/source";
+import {
+ renderPlaceholder,
+ type PlaceholderData,
+} from "fumadocs-core/mdx-plugins/remark-llms.runtime";
import { docs } from "fumadocs-mdx:collections/server";
+import { resolveDocsAssetUrl } from "./docs-asset-path";
import { createElement, type SVGProps } from "react";
import {
AlertTriangle,
@@ -134,17 +139,134 @@ export const source = loader({
},
});
+function getStringAttribute(attributes: Record, key: string): string | undefined {
+ const value = attributes[key];
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
+}
+
+function joinMarkdownBlocks(blocks: Array) {
+ return blocks
+ .map((block) => block?.trim())
+ .filter(Boolean)
+ .join("\n\n");
+}
+
+function normalizeMarkdownWhitespace(markdown: string) {
+ return markdown.replace(/\n{3,}/g, "\n\n").trim();
+}
+
+function renderMarkdownBlock(blocks: Array) {
+ const block = joinMarkdownBlocks(blocks);
+ return block ? `${block}\n\n` : "";
+}
+
+function renderCallout(label: string, data: PlaceholderData) {
+ const title = getStringAttribute(data.attributes, "title") ?? label;
+ const children = data.children.trim();
+ if (!children) return renderMarkdownBlock([`> **${title}**`]);
+
+ return renderMarkdownBlock([`> **${title}:** ${children.replace(/\n/g, "\n> ")}`]);
+}
+
+const markdownPlaceholderRenderers: Record string> = {
+ Accordion: (data) =>
+ renderMarkdownBlock([
+ `## ${getStringAttribute(data.attributes, "title") ?? "Details"}`,
+ data.children,
+ ]),
+ AccordionGroup: (data) => data.children,
+ Callout: (data) => renderCallout("Callout", data),
+ Card: (data) =>
+ renderMarkdownBlock([
+ `## ${getStringAttribute(data.attributes, "title") ?? "Link"}`,
+ getStringAttribute(data.attributes, "href"),
+ data.children,
+ ]),
+ CardGroup: (data) => data.children,
+ CodeGroup: (data) => data.children,
+ Frame: (data) => data.children,
+ Info: (data) => renderCallout("Info", data),
+ Mermaid: (data) => `\`\`\`mermaid\n${data.children.trim()}\n\`\`\`\n\n`,
+ Note: (data) => renderCallout("Note", data),
+ Step: (data) =>
+ renderMarkdownBlock([
+ `## ${getStringAttribute(data.attributes, "title") ?? "Step"}`,
+ data.children,
+ ]),
+ Steps: (data) => data.children,
+ Tab: (data) =>
+ renderMarkdownBlock([
+ `## ${getStringAttribute(data.attributes, "title") ?? "Tab"}`,
+ data.children,
+ ]),
+ Tabs: (data) => data.children,
+ Tip: (data) => renderCallout("Tip", data),
+ Warning: (data) => renderCallout("Warning", data),
+};
+
+type MarkdownRenderOptions = {
+ baseUrl?: string;
+};
+
+const htmlImagePlaceholderPattern = /
]*?)\bsrc=["']__img(\d+)["']([^>]*)\/?>/g;
+const markdownImagePlaceholderPattern = /(!\[[^\]]*]\(\s*)?(\s*\))/g;
+const htmlAltAttributePattern = /\balt=["']([^"']*)["']/i;
+
+function restoreImagePlaceholders(markdown: string, imageImports: Map) {
+ const withHtmlImages = markdown.replace(
+ htmlImagePlaceholderPattern,
+ (match, before: string, index: string, after: string) => {
+ const url = imageImports.get(`__img${index}`);
+ if (!url) return match;
+
+ const alt = `${before} ${after}`.match(htmlAltAttributePattern)?.[1] ?? "";
+ return `\n\n`;
+ },
+ );
+
+ return withHtmlImages.replace(
+ markdownImagePlaceholderPattern,
+ (match, prefix: string, index: string, suffix: string) => {
+ const url = imageImports.get(`__img${index}`);
+ return url ? `${prefix}${url}${suffix}` : match;
+ },
+ );
+}
+
+type PageMarkdownData = {
+ load?: () => Promise;
+ markdownImageMap?: Record;
+ _exports?: { markdownImageMap?: Record };
+};
+
+async function getPageImageImports(page: InferPageType, baseUrl?: string) {
+ const pageData = page.data as PageMarkdownData;
+ const data = pageData.load ? await pageData.load() : pageData;
+ const imageMap = data.markdownImageMap ?? data._exports?.markdownImageMap ?? {};
+
+ return new Map(
+ Object.entries(imageMap)
+ .filter((entry): entry is [string, string] => typeof entry[1] === "string")
+ .map(([name, url]) => [name, resolveDocsAssetUrl(url, baseUrl)]),
+ );
+}
+
export async function getPageMarkdownText(
page: InferPageType,
- _type: "raw" | "processed" = "processed",
+ options: MarkdownRenderOptions = {},
) {
- return page.data.getText("processed");
+ const imageImports = await getPageImageImports(page, options.baseUrl);
+ const processed = await renderPlaceholder(
+ await page.data.getText("processed"),
+ markdownPlaceholderRenderers,
+ );
+ const withImages = restoreImagePlaceholders(processed, imageImports);
+
+ return normalizeMarkdownWhitespace(
+ joinMarkdownBlocks([`# ${page.data.title}`, page.data.description, withImages]),
+ );
}
export async function getLLMText(page: InferPageType) {
- const processed = await getPageMarkdownText(page);
-
- return `# ${page.data.title}
-
-${processed}`;
+ return getPageMarkdownText(page);
}
diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts
index f9050ae7..a8e45ada 100644
--- a/src/routeTree.gen.ts
+++ b/src/routeTree.gen.ts
@@ -9,7 +9,11 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root'
+import { Route as Char123Char125DotmdxRouteImport } from './routes/{$}[.]mdx'
+import { Route as Char123Char125DotmdRouteImport } from './routes/{$}[.]md'
import { Route as SitemapDotxmlRouteImport } from './routes/sitemap[.]xml'
+import { Route as SdkDotmdxRouteImport } from './routes/sdk[.]mdx'
+import { Route as SdkDotmdRouteImport } from './routes/sdk[.]md'
import { Route as RobotsDottxtRouteImport } from './routes/robots[.]txt'
import { Route as LlmsDottxtRouteImport } from './routes/llms[.]txt'
import { Route as LlmsChar123sectionChar125DottxtRouteImport } from './routes/llms-{$section}[.]txt'
@@ -18,17 +22,61 @@ import { Route as LlmsFullChar123sectionChar125DottxtRouteImport } from './route
import { Route as SplatRouteImport } from './routes/$'
import { Route as IndexRouteImport } from './routes/index'
import { Route as SdkIndexRouteImport } from './routes/sdk/index'
+import { Route as WebCheckoutLlmsDottxtRouteImport } from './routes/web-checkout/llms[.]txt'
+import { Route as WebCheckoutLlmsFullDottxtRouteImport } from './routes/web-checkout/llms-full[.]txt'
+import { Route as UnityLlmsDottxtRouteImport } from './routes/unity/llms[.]txt'
+import { Route as UnityLlmsFullDottxtRouteImport } from './routes/unity/llms-full[.]txt'
+import { Route as SdkChar123Char125DotmdxRouteImport } from './routes/sdk/{$}[.]mdx'
+import { Route as SdkChar123Char125DotmdRouteImport } from './routes/sdk/{$}[.]md'
import { Route as SdkSplatRouteImport } from './routes/sdk/$'
+import { Route as ReactNativeLlmsDottxtRouteImport } from './routes/react-native/llms[.]txt'
+import { Route as ReactNativeLlmsFullDottxtRouteImport } from './routes/react-native/llms-full[.]txt'
+import { Route as LlmsDotmdxDocsRouteImport } from './routes/llms[.]mdx.docs'
+import { Route as IosLlmsDottxtRouteImport } from './routes/ios/llms[.]txt'
+import { Route as IosLlmsFullDottxtRouteImport } from './routes/ios/llms-full[.]txt'
+import { Route as IntegrationsLlmsDottxtRouteImport } from './routes/integrations/llms[.]txt'
+import { Route as IntegrationsLlmsFullDottxtRouteImport } from './routes/integrations/llms-full[.]txt'
+import { Route as FlutterLlmsDottxtRouteImport } from './routes/flutter/llms[.]txt'
+import { Route as FlutterLlmsFullDottxtRouteImport } from './routes/flutter/llms-full[.]txt'
+import { Route as ExpoLlmsDottxtRouteImport } from './routes/expo/llms[.]txt'
+import { Route as ExpoLlmsFullDottxtRouteImport } from './routes/expo/llms-full[.]txt'
+import { Route as DashboardLlmsDottxtRouteImport } from './routes/dashboard/llms[.]txt'
+import { Route as DashboardLlmsFullDottxtRouteImport } from './routes/dashboard/llms-full[.]txt'
import { Route as ApiSearchRouteImport } from './routes/api/search'
import { Route as ApiFeedbackRouteImport } from './routes/api/feedback'
+import { Route as AndroidLlmsDottxtRouteImport } from './routes/android/llms[.]txt'
+import { Route as AndroidLlmsFullDottxtRouteImport } from './routes/android/llms-full[.]txt'
+import { Route as AgentsLlmsDottxtRouteImport } from './routes/agents/llms[.]txt'
+import { Route as AgentsLlmsFullDottxtRouteImport } from './routes/agents/llms-full[.]txt'
+import { Route as LlmsDotmdxDocsIndexRouteImport } from './routes/llms[.]mdx.docs.index'
import { Route as LlmsDotmdxDocsSplatRouteImport } from './routes/llms[.]mdx.docs.$'
import { Route as ApiRawSplatRouteImport } from './routes/api/raw/$'
+const Char123Char125DotmdxRoute = Char123Char125DotmdxRouteImport.update({
+ id: '/{$}.mdx',
+ path: '/{$}.mdx',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const Char123Char125DotmdRoute = Char123Char125DotmdRouteImport.update({
+ id: '/{$}.md',
+ path: '/{$}.md',
+ getParentRoute: () => rootRouteImport,
+} as any)
const SitemapDotxmlRoute = SitemapDotxmlRouteImport.update({
id: '/sitemap.xml',
path: '/sitemap.xml',
getParentRoute: () => rootRouteImport,
} as any)
+const SdkDotmdxRoute = SdkDotmdxRouteImport.update({
+ id: '/sdk.mdx',
+ path: '/sdk.mdx',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const SdkDotmdRoute = SdkDotmdRouteImport.update({
+ id: '/sdk.md',
+ path: '/sdk.md',
+ getParentRoute: () => rootRouteImport,
+} as any)
const RobotsDottxtRoute = RobotsDottxtRouteImport.update({
id: '/robots.txt',
path: '/robots.txt',
@@ -71,11 +119,109 @@ const SdkIndexRoute = SdkIndexRouteImport.update({
path: '/sdk/',
getParentRoute: () => rootRouteImport,
} as any)
+const WebCheckoutLlmsDottxtRoute = WebCheckoutLlmsDottxtRouteImport.update({
+ id: '/web-checkout/llms.txt',
+ path: '/web-checkout/llms.txt',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const WebCheckoutLlmsFullDottxtRoute =
+ WebCheckoutLlmsFullDottxtRouteImport.update({
+ id: '/web-checkout/llms-full.txt',
+ path: '/web-checkout/llms-full.txt',
+ getParentRoute: () => rootRouteImport,
+ } as any)
+const UnityLlmsDottxtRoute = UnityLlmsDottxtRouteImport.update({
+ id: '/unity/llms.txt',
+ path: '/unity/llms.txt',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const UnityLlmsFullDottxtRoute = UnityLlmsFullDottxtRouteImport.update({
+ id: '/unity/llms-full.txt',
+ path: '/unity/llms-full.txt',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const SdkChar123Char125DotmdxRoute = SdkChar123Char125DotmdxRouteImport.update({
+ id: '/sdk/{$}.mdx',
+ path: '/sdk/{$}.mdx',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const SdkChar123Char125DotmdRoute = SdkChar123Char125DotmdRouteImport.update({
+ id: '/sdk/{$}.md',
+ path: '/sdk/{$}.md',
+ getParentRoute: () => rootRouteImport,
+} as any)
const SdkSplatRoute = SdkSplatRouteImport.update({
id: '/sdk/$',
path: '/sdk/$',
getParentRoute: () => rootRouteImport,
} as any)
+const ReactNativeLlmsDottxtRoute = ReactNativeLlmsDottxtRouteImport.update({
+ id: '/react-native/llms.txt',
+ path: '/react-native/llms.txt',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const ReactNativeLlmsFullDottxtRoute =
+ ReactNativeLlmsFullDottxtRouteImport.update({
+ id: '/react-native/llms-full.txt',
+ path: '/react-native/llms-full.txt',
+ getParentRoute: () => rootRouteImport,
+ } as any)
+const LlmsDotmdxDocsRoute = LlmsDotmdxDocsRouteImport.update({
+ id: '/llms.mdx/docs',
+ path: '/llms.mdx/docs',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IosLlmsDottxtRoute = IosLlmsDottxtRouteImport.update({
+ id: '/ios/llms.txt',
+ path: '/ios/llms.txt',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IosLlmsFullDottxtRoute = IosLlmsFullDottxtRouteImport.update({
+ id: '/ios/llms-full.txt',
+ path: '/ios/llms-full.txt',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IntegrationsLlmsDottxtRoute = IntegrationsLlmsDottxtRouteImport.update({
+ id: '/integrations/llms.txt',
+ path: '/integrations/llms.txt',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IntegrationsLlmsFullDottxtRoute =
+ IntegrationsLlmsFullDottxtRouteImport.update({
+ id: '/integrations/llms-full.txt',
+ path: '/integrations/llms-full.txt',
+ getParentRoute: () => rootRouteImport,
+ } as any)
+const FlutterLlmsDottxtRoute = FlutterLlmsDottxtRouteImport.update({
+ id: '/flutter/llms.txt',
+ path: '/flutter/llms.txt',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const FlutterLlmsFullDottxtRoute = FlutterLlmsFullDottxtRouteImport.update({
+ id: '/flutter/llms-full.txt',
+ path: '/flutter/llms-full.txt',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const ExpoLlmsDottxtRoute = ExpoLlmsDottxtRouteImport.update({
+ id: '/expo/llms.txt',
+ path: '/expo/llms.txt',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const ExpoLlmsFullDottxtRoute = ExpoLlmsFullDottxtRouteImport.update({
+ id: '/expo/llms-full.txt',
+ path: '/expo/llms-full.txt',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const DashboardLlmsDottxtRoute = DashboardLlmsDottxtRouteImport.update({
+ id: '/dashboard/llms.txt',
+ path: '/dashboard/llms.txt',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const DashboardLlmsFullDottxtRoute = DashboardLlmsFullDottxtRouteImport.update({
+ id: '/dashboard/llms-full.txt',
+ path: '/dashboard/llms-full.txt',
+ getParentRoute: () => rootRouteImport,
+} as any)
const ApiSearchRoute = ApiSearchRouteImport.update({
id: '/api/search',
path: '/api/search',
@@ -86,11 +232,36 @@ const ApiFeedbackRoute = ApiFeedbackRouteImport.update({
path: '/api/feedback',
getParentRoute: () => rootRouteImport,
} as any)
-const LlmsDotmdxDocsSplatRoute = LlmsDotmdxDocsSplatRouteImport.update({
- id: '/llms.mdx/docs/$',
- path: '/llms.mdx/docs/$',
+const AndroidLlmsDottxtRoute = AndroidLlmsDottxtRouteImport.update({
+ id: '/android/llms.txt',
+ path: '/android/llms.txt',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const AndroidLlmsFullDottxtRoute = AndroidLlmsFullDottxtRouteImport.update({
+ id: '/android/llms-full.txt',
+ path: '/android/llms-full.txt',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const AgentsLlmsDottxtRoute = AgentsLlmsDottxtRouteImport.update({
+ id: '/agents/llms.txt',
+ path: '/agents/llms.txt',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const AgentsLlmsFullDottxtRoute = AgentsLlmsFullDottxtRouteImport.update({
+ id: '/agents/llms-full.txt',
+ path: '/agents/llms-full.txt',
getParentRoute: () => rootRouteImport,
} as any)
+const LlmsDotmdxDocsIndexRoute = LlmsDotmdxDocsIndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => LlmsDotmdxDocsRoute,
+} as any)
+const LlmsDotmdxDocsSplatRoute = LlmsDotmdxDocsSplatRouteImport.update({
+ id: '/$',
+ path: '/$',
+ getParentRoute: () => LlmsDotmdxDocsRoute,
+} as any)
const ApiRawSplatRoute = ApiRawSplatRouteImport.update({
id: '/api/raw/$',
path: '/api/raw/$',
@@ -105,13 +276,41 @@ export interface FileRoutesByFullPath {
'/llms-{$section}.txt': typeof LlmsChar123sectionChar125DottxtRoute
'/llms.txt': typeof LlmsDottxtRoute
'/robots.txt': typeof RobotsDottxtRoute
+ '/sdk.md': typeof SdkDotmdRoute
+ '/sdk.mdx': typeof SdkDotmdxRoute
'/sitemap.xml': typeof SitemapDotxmlRoute
+ '/{$}.md': typeof Char123Char125DotmdRoute
+ '/{$}.mdx': typeof Char123Char125DotmdxRoute
+ '/agents/llms-full.txt': typeof AgentsLlmsFullDottxtRoute
+ '/agents/llms.txt': typeof AgentsLlmsDottxtRoute
+ '/android/llms-full.txt': typeof AndroidLlmsFullDottxtRoute
+ '/android/llms.txt': typeof AndroidLlmsDottxtRoute
'/api/feedback': typeof ApiFeedbackRoute
'/api/search': typeof ApiSearchRoute
+ '/dashboard/llms-full.txt': typeof DashboardLlmsFullDottxtRoute
+ '/dashboard/llms.txt': typeof DashboardLlmsDottxtRoute
+ '/expo/llms-full.txt': typeof ExpoLlmsFullDottxtRoute
+ '/expo/llms.txt': typeof ExpoLlmsDottxtRoute
+ '/flutter/llms-full.txt': typeof FlutterLlmsFullDottxtRoute
+ '/flutter/llms.txt': typeof FlutterLlmsDottxtRoute
+ '/integrations/llms-full.txt': typeof IntegrationsLlmsFullDottxtRoute
+ '/integrations/llms.txt': typeof IntegrationsLlmsDottxtRoute
+ '/ios/llms-full.txt': typeof IosLlmsFullDottxtRoute
+ '/ios/llms.txt': typeof IosLlmsDottxtRoute
+ '/llms.mdx/docs': typeof LlmsDotmdxDocsRouteWithChildren
+ '/react-native/llms-full.txt': typeof ReactNativeLlmsFullDottxtRoute
+ '/react-native/llms.txt': typeof ReactNativeLlmsDottxtRoute
'/sdk/$': typeof SdkSplatRoute
+ '/sdk/{$}.md': typeof SdkChar123Char125DotmdRoute
+ '/sdk/{$}.mdx': typeof SdkChar123Char125DotmdxRoute
+ '/unity/llms-full.txt': typeof UnityLlmsFullDottxtRoute
+ '/unity/llms.txt': typeof UnityLlmsDottxtRoute
+ '/web-checkout/llms-full.txt': typeof WebCheckoutLlmsFullDottxtRoute
+ '/web-checkout/llms.txt': typeof WebCheckoutLlmsDottxtRoute
'/sdk/': typeof SdkIndexRoute
'/api/raw/$': typeof ApiRawSplatRoute
'/llms.mdx/docs/$': typeof LlmsDotmdxDocsSplatRoute
+ '/llms.mdx/docs/': typeof LlmsDotmdxDocsIndexRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
@@ -121,13 +320,40 @@ export interface FileRoutesByTo {
'/llms-{$section}.txt': typeof LlmsChar123sectionChar125DottxtRoute
'/llms.txt': typeof LlmsDottxtRoute
'/robots.txt': typeof RobotsDottxtRoute
+ '/sdk.md': typeof SdkDotmdRoute
+ '/sdk.mdx': typeof SdkDotmdxRoute
'/sitemap.xml': typeof SitemapDotxmlRoute
+ '/{$}.md': typeof Char123Char125DotmdRoute
+ '/{$}.mdx': typeof Char123Char125DotmdxRoute
+ '/agents/llms-full.txt': typeof AgentsLlmsFullDottxtRoute
+ '/agents/llms.txt': typeof AgentsLlmsDottxtRoute
+ '/android/llms-full.txt': typeof AndroidLlmsFullDottxtRoute
+ '/android/llms.txt': typeof AndroidLlmsDottxtRoute
'/api/feedback': typeof ApiFeedbackRoute
'/api/search': typeof ApiSearchRoute
+ '/dashboard/llms-full.txt': typeof DashboardLlmsFullDottxtRoute
+ '/dashboard/llms.txt': typeof DashboardLlmsDottxtRoute
+ '/expo/llms-full.txt': typeof ExpoLlmsFullDottxtRoute
+ '/expo/llms.txt': typeof ExpoLlmsDottxtRoute
+ '/flutter/llms-full.txt': typeof FlutterLlmsFullDottxtRoute
+ '/flutter/llms.txt': typeof FlutterLlmsDottxtRoute
+ '/integrations/llms-full.txt': typeof IntegrationsLlmsFullDottxtRoute
+ '/integrations/llms.txt': typeof IntegrationsLlmsDottxtRoute
+ '/ios/llms-full.txt': typeof IosLlmsFullDottxtRoute
+ '/ios/llms.txt': typeof IosLlmsDottxtRoute
+ '/react-native/llms-full.txt': typeof ReactNativeLlmsFullDottxtRoute
+ '/react-native/llms.txt': typeof ReactNativeLlmsDottxtRoute
'/sdk/$': typeof SdkSplatRoute
+ '/sdk/{$}.md': typeof SdkChar123Char125DotmdRoute
+ '/sdk/{$}.mdx': typeof SdkChar123Char125DotmdxRoute
+ '/unity/llms-full.txt': typeof UnityLlmsFullDottxtRoute
+ '/unity/llms.txt': typeof UnityLlmsDottxtRoute
+ '/web-checkout/llms-full.txt': typeof WebCheckoutLlmsFullDottxtRoute
+ '/web-checkout/llms.txt': typeof WebCheckoutLlmsDottxtRoute
'/sdk': typeof SdkIndexRoute
'/api/raw/$': typeof ApiRawSplatRoute
'/llms.mdx/docs/$': typeof LlmsDotmdxDocsSplatRoute
+ '/llms.mdx/docs': typeof LlmsDotmdxDocsIndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
@@ -138,13 +364,41 @@ export interface FileRoutesById {
'/llms-{$section}.txt': typeof LlmsChar123sectionChar125DottxtRoute
'/llms.txt': typeof LlmsDottxtRoute
'/robots.txt': typeof RobotsDottxtRoute
+ '/sdk.md': typeof SdkDotmdRoute
+ '/sdk.mdx': typeof SdkDotmdxRoute
'/sitemap.xml': typeof SitemapDotxmlRoute
+ '/{$}.md': typeof Char123Char125DotmdRoute
+ '/{$}.mdx': typeof Char123Char125DotmdxRoute
+ '/agents/llms-full.txt': typeof AgentsLlmsFullDottxtRoute
+ '/agents/llms.txt': typeof AgentsLlmsDottxtRoute
+ '/android/llms-full.txt': typeof AndroidLlmsFullDottxtRoute
+ '/android/llms.txt': typeof AndroidLlmsDottxtRoute
'/api/feedback': typeof ApiFeedbackRoute
'/api/search': typeof ApiSearchRoute
+ '/dashboard/llms-full.txt': typeof DashboardLlmsFullDottxtRoute
+ '/dashboard/llms.txt': typeof DashboardLlmsDottxtRoute
+ '/expo/llms-full.txt': typeof ExpoLlmsFullDottxtRoute
+ '/expo/llms.txt': typeof ExpoLlmsDottxtRoute
+ '/flutter/llms-full.txt': typeof FlutterLlmsFullDottxtRoute
+ '/flutter/llms.txt': typeof FlutterLlmsDottxtRoute
+ '/integrations/llms-full.txt': typeof IntegrationsLlmsFullDottxtRoute
+ '/integrations/llms.txt': typeof IntegrationsLlmsDottxtRoute
+ '/ios/llms-full.txt': typeof IosLlmsFullDottxtRoute
+ '/ios/llms.txt': typeof IosLlmsDottxtRoute
+ '/llms.mdx/docs': typeof LlmsDotmdxDocsRouteWithChildren
+ '/react-native/llms-full.txt': typeof ReactNativeLlmsFullDottxtRoute
+ '/react-native/llms.txt': typeof ReactNativeLlmsDottxtRoute
'/sdk/$': typeof SdkSplatRoute
+ '/sdk/{$}.md': typeof SdkChar123Char125DotmdRoute
+ '/sdk/{$}.mdx': typeof SdkChar123Char125DotmdxRoute
+ '/unity/llms-full.txt': typeof UnityLlmsFullDottxtRoute
+ '/unity/llms.txt': typeof UnityLlmsDottxtRoute
+ '/web-checkout/llms-full.txt': typeof WebCheckoutLlmsFullDottxtRoute
+ '/web-checkout/llms.txt': typeof WebCheckoutLlmsDottxtRoute
'/sdk/': typeof SdkIndexRoute
'/api/raw/$': typeof ApiRawSplatRoute
'/llms.mdx/docs/$': typeof LlmsDotmdxDocsSplatRoute
+ '/llms.mdx/docs/': typeof LlmsDotmdxDocsIndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
@@ -156,13 +410,41 @@ export interface FileRouteTypes {
| '/llms-{$section}.txt'
| '/llms.txt'
| '/robots.txt'
+ | '/sdk.md'
+ | '/sdk.mdx'
| '/sitemap.xml'
+ | '/{$}.md'
+ | '/{$}.mdx'
+ | '/agents/llms-full.txt'
+ | '/agents/llms.txt'
+ | '/android/llms-full.txt'
+ | '/android/llms.txt'
| '/api/feedback'
| '/api/search'
+ | '/dashboard/llms-full.txt'
+ | '/dashboard/llms.txt'
+ | '/expo/llms-full.txt'
+ | '/expo/llms.txt'
+ | '/flutter/llms-full.txt'
+ | '/flutter/llms.txt'
+ | '/integrations/llms-full.txt'
+ | '/integrations/llms.txt'
+ | '/ios/llms-full.txt'
+ | '/ios/llms.txt'
+ | '/llms.mdx/docs'
+ | '/react-native/llms-full.txt'
+ | '/react-native/llms.txt'
| '/sdk/$'
+ | '/sdk/{$}.md'
+ | '/sdk/{$}.mdx'
+ | '/unity/llms-full.txt'
+ | '/unity/llms.txt'
+ | '/web-checkout/llms-full.txt'
+ | '/web-checkout/llms.txt'
| '/sdk/'
| '/api/raw/$'
| '/llms.mdx/docs/$'
+ | '/llms.mdx/docs/'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
@@ -172,13 +454,40 @@ export interface FileRouteTypes {
| '/llms-{$section}.txt'
| '/llms.txt'
| '/robots.txt'
+ | '/sdk.md'
+ | '/sdk.mdx'
| '/sitemap.xml'
+ | '/{$}.md'
+ | '/{$}.mdx'
+ | '/agents/llms-full.txt'
+ | '/agents/llms.txt'
+ | '/android/llms-full.txt'
+ | '/android/llms.txt'
| '/api/feedback'
| '/api/search'
+ | '/dashboard/llms-full.txt'
+ | '/dashboard/llms.txt'
+ | '/expo/llms-full.txt'
+ | '/expo/llms.txt'
+ | '/flutter/llms-full.txt'
+ | '/flutter/llms.txt'
+ | '/integrations/llms-full.txt'
+ | '/integrations/llms.txt'
+ | '/ios/llms-full.txt'
+ | '/ios/llms.txt'
+ | '/react-native/llms-full.txt'
+ | '/react-native/llms.txt'
| '/sdk/$'
+ | '/sdk/{$}.md'
+ | '/sdk/{$}.mdx'
+ | '/unity/llms-full.txt'
+ | '/unity/llms.txt'
+ | '/web-checkout/llms-full.txt'
+ | '/web-checkout/llms.txt'
| '/sdk'
| '/api/raw/$'
| '/llms.mdx/docs/$'
+ | '/llms.mdx/docs'
id:
| '__root__'
| '/'
@@ -188,13 +497,41 @@ export interface FileRouteTypes {
| '/llms-{$section}.txt'
| '/llms.txt'
| '/robots.txt'
+ | '/sdk.md'
+ | '/sdk.mdx'
| '/sitemap.xml'
+ | '/{$}.md'
+ | '/{$}.mdx'
+ | '/agents/llms-full.txt'
+ | '/agents/llms.txt'
+ | '/android/llms-full.txt'
+ | '/android/llms.txt'
| '/api/feedback'
| '/api/search'
+ | '/dashboard/llms-full.txt'
+ | '/dashboard/llms.txt'
+ | '/expo/llms-full.txt'
+ | '/expo/llms.txt'
+ | '/flutter/llms-full.txt'
+ | '/flutter/llms.txt'
+ | '/integrations/llms-full.txt'
+ | '/integrations/llms.txt'
+ | '/ios/llms-full.txt'
+ | '/ios/llms.txt'
+ | '/llms.mdx/docs'
+ | '/react-native/llms-full.txt'
+ | '/react-native/llms.txt'
| '/sdk/$'
+ | '/sdk/{$}.md'
+ | '/sdk/{$}.mdx'
+ | '/unity/llms-full.txt'
+ | '/unity/llms.txt'
+ | '/web-checkout/llms-full.txt'
+ | '/web-checkout/llms.txt'
| '/sdk/'
| '/api/raw/$'
| '/llms.mdx/docs/$'
+ | '/llms.mdx/docs/'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
@@ -205,17 +542,57 @@ export interface RootRouteChildren {
LlmsChar123sectionChar125DottxtRoute: typeof LlmsChar123sectionChar125DottxtRoute
LlmsDottxtRoute: typeof LlmsDottxtRoute
RobotsDottxtRoute: typeof RobotsDottxtRoute
+ SdkDotmdRoute: typeof SdkDotmdRoute
+ SdkDotmdxRoute: typeof SdkDotmdxRoute
SitemapDotxmlRoute: typeof SitemapDotxmlRoute
+ Char123Char125DotmdRoute: typeof Char123Char125DotmdRoute
+ Char123Char125DotmdxRoute: typeof Char123Char125DotmdxRoute
+ AgentsLlmsFullDottxtRoute: typeof AgentsLlmsFullDottxtRoute
+ AgentsLlmsDottxtRoute: typeof AgentsLlmsDottxtRoute
+ AndroidLlmsFullDottxtRoute: typeof AndroidLlmsFullDottxtRoute
+ AndroidLlmsDottxtRoute: typeof AndroidLlmsDottxtRoute
ApiFeedbackRoute: typeof ApiFeedbackRoute
ApiSearchRoute: typeof ApiSearchRoute
+ DashboardLlmsFullDottxtRoute: typeof DashboardLlmsFullDottxtRoute
+ DashboardLlmsDottxtRoute: typeof DashboardLlmsDottxtRoute
+ ExpoLlmsFullDottxtRoute: typeof ExpoLlmsFullDottxtRoute
+ ExpoLlmsDottxtRoute: typeof ExpoLlmsDottxtRoute
+ FlutterLlmsFullDottxtRoute: typeof FlutterLlmsFullDottxtRoute
+ FlutterLlmsDottxtRoute: typeof FlutterLlmsDottxtRoute
+ IntegrationsLlmsFullDottxtRoute: typeof IntegrationsLlmsFullDottxtRoute
+ IntegrationsLlmsDottxtRoute: typeof IntegrationsLlmsDottxtRoute
+ IosLlmsFullDottxtRoute: typeof IosLlmsFullDottxtRoute
+ IosLlmsDottxtRoute: typeof IosLlmsDottxtRoute
+ LlmsDotmdxDocsRoute: typeof LlmsDotmdxDocsRouteWithChildren
+ ReactNativeLlmsFullDottxtRoute: typeof ReactNativeLlmsFullDottxtRoute
+ ReactNativeLlmsDottxtRoute: typeof ReactNativeLlmsDottxtRoute
SdkSplatRoute: typeof SdkSplatRoute
+ SdkChar123Char125DotmdRoute: typeof SdkChar123Char125DotmdRoute
+ SdkChar123Char125DotmdxRoute: typeof SdkChar123Char125DotmdxRoute
+ UnityLlmsFullDottxtRoute: typeof UnityLlmsFullDottxtRoute
+ UnityLlmsDottxtRoute: typeof UnityLlmsDottxtRoute
+ WebCheckoutLlmsFullDottxtRoute: typeof WebCheckoutLlmsFullDottxtRoute
+ WebCheckoutLlmsDottxtRoute: typeof WebCheckoutLlmsDottxtRoute
SdkIndexRoute: typeof SdkIndexRoute
ApiRawSplatRoute: typeof ApiRawSplatRoute
- LlmsDotmdxDocsSplatRoute: typeof LlmsDotmdxDocsSplatRoute
}
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
+ '/{$}.mdx': {
+ id: '/{$}.mdx'
+ path: '/{$}.mdx'
+ fullPath: '/{$}.mdx'
+ preLoaderRoute: typeof Char123Char125DotmdxRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/{$}.md': {
+ id: '/{$}.md'
+ path: '/{$}.md'
+ fullPath: '/{$}.md'
+ preLoaderRoute: typeof Char123Char125DotmdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
'/sitemap.xml': {
id: '/sitemap.xml'
path: '/sitemap.xml'
@@ -223,6 +600,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof SitemapDotxmlRouteImport
parentRoute: typeof rootRouteImport
}
+ '/sdk.mdx': {
+ id: '/sdk.mdx'
+ path: '/sdk.mdx'
+ fullPath: '/sdk.mdx'
+ preLoaderRoute: typeof SdkDotmdxRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/sdk.md': {
+ id: '/sdk.md'
+ path: '/sdk.md'
+ fullPath: '/sdk.md'
+ preLoaderRoute: typeof SdkDotmdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
'/robots.txt': {
id: '/robots.txt'
path: '/robots.txt'
@@ -279,6 +670,48 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof SdkIndexRouteImport
parentRoute: typeof rootRouteImport
}
+ '/web-checkout/llms.txt': {
+ id: '/web-checkout/llms.txt'
+ path: '/web-checkout/llms.txt'
+ fullPath: '/web-checkout/llms.txt'
+ preLoaderRoute: typeof WebCheckoutLlmsDottxtRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/web-checkout/llms-full.txt': {
+ id: '/web-checkout/llms-full.txt'
+ path: '/web-checkout/llms-full.txt'
+ fullPath: '/web-checkout/llms-full.txt'
+ preLoaderRoute: typeof WebCheckoutLlmsFullDottxtRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/unity/llms.txt': {
+ id: '/unity/llms.txt'
+ path: '/unity/llms.txt'
+ fullPath: '/unity/llms.txt'
+ preLoaderRoute: typeof UnityLlmsDottxtRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/unity/llms-full.txt': {
+ id: '/unity/llms-full.txt'
+ path: '/unity/llms-full.txt'
+ fullPath: '/unity/llms-full.txt'
+ preLoaderRoute: typeof UnityLlmsFullDottxtRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/sdk/{$}.mdx': {
+ id: '/sdk/{$}.mdx'
+ path: '/sdk/{$}.mdx'
+ fullPath: '/sdk/{$}.mdx'
+ preLoaderRoute: typeof SdkChar123Char125DotmdxRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/sdk/{$}.md': {
+ id: '/sdk/{$}.md'
+ path: '/sdk/{$}.md'
+ fullPath: '/sdk/{$}.md'
+ preLoaderRoute: typeof SdkChar123Char125DotmdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
'/sdk/$': {
id: '/sdk/$'
path: '/sdk/$'
@@ -286,6 +719,97 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof SdkSplatRouteImport
parentRoute: typeof rootRouteImport
}
+ '/react-native/llms.txt': {
+ id: '/react-native/llms.txt'
+ path: '/react-native/llms.txt'
+ fullPath: '/react-native/llms.txt'
+ preLoaderRoute: typeof ReactNativeLlmsDottxtRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/react-native/llms-full.txt': {
+ id: '/react-native/llms-full.txt'
+ path: '/react-native/llms-full.txt'
+ fullPath: '/react-native/llms-full.txt'
+ preLoaderRoute: typeof ReactNativeLlmsFullDottxtRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/llms.mdx/docs': {
+ id: '/llms.mdx/docs'
+ path: '/llms.mdx/docs'
+ fullPath: '/llms.mdx/docs'
+ preLoaderRoute: typeof LlmsDotmdxDocsRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/ios/llms.txt': {
+ id: '/ios/llms.txt'
+ path: '/ios/llms.txt'
+ fullPath: '/ios/llms.txt'
+ preLoaderRoute: typeof IosLlmsDottxtRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/ios/llms-full.txt': {
+ id: '/ios/llms-full.txt'
+ path: '/ios/llms-full.txt'
+ fullPath: '/ios/llms-full.txt'
+ preLoaderRoute: typeof IosLlmsFullDottxtRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/integrations/llms.txt': {
+ id: '/integrations/llms.txt'
+ path: '/integrations/llms.txt'
+ fullPath: '/integrations/llms.txt'
+ preLoaderRoute: typeof IntegrationsLlmsDottxtRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/integrations/llms-full.txt': {
+ id: '/integrations/llms-full.txt'
+ path: '/integrations/llms-full.txt'
+ fullPath: '/integrations/llms-full.txt'
+ preLoaderRoute: typeof IntegrationsLlmsFullDottxtRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/flutter/llms.txt': {
+ id: '/flutter/llms.txt'
+ path: '/flutter/llms.txt'
+ fullPath: '/flutter/llms.txt'
+ preLoaderRoute: typeof FlutterLlmsDottxtRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/flutter/llms-full.txt': {
+ id: '/flutter/llms-full.txt'
+ path: '/flutter/llms-full.txt'
+ fullPath: '/flutter/llms-full.txt'
+ preLoaderRoute: typeof FlutterLlmsFullDottxtRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/expo/llms.txt': {
+ id: '/expo/llms.txt'
+ path: '/expo/llms.txt'
+ fullPath: '/expo/llms.txt'
+ preLoaderRoute: typeof ExpoLlmsDottxtRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/expo/llms-full.txt': {
+ id: '/expo/llms-full.txt'
+ path: '/expo/llms-full.txt'
+ fullPath: '/expo/llms-full.txt'
+ preLoaderRoute: typeof ExpoLlmsFullDottxtRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/dashboard/llms.txt': {
+ id: '/dashboard/llms.txt'
+ path: '/dashboard/llms.txt'
+ fullPath: '/dashboard/llms.txt'
+ preLoaderRoute: typeof DashboardLlmsDottxtRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/dashboard/llms-full.txt': {
+ id: '/dashboard/llms-full.txt'
+ path: '/dashboard/llms-full.txt'
+ fullPath: '/dashboard/llms-full.txt'
+ preLoaderRoute: typeof DashboardLlmsFullDottxtRouteImport
+ parentRoute: typeof rootRouteImport
+ }
'/api/search': {
id: '/api/search'
path: '/api/search'
@@ -300,12 +824,47 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ApiFeedbackRouteImport
parentRoute: typeof rootRouteImport
}
+ '/android/llms.txt': {
+ id: '/android/llms.txt'
+ path: '/android/llms.txt'
+ fullPath: '/android/llms.txt'
+ preLoaderRoute: typeof AndroidLlmsDottxtRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/android/llms-full.txt': {
+ id: '/android/llms-full.txt'
+ path: '/android/llms-full.txt'
+ fullPath: '/android/llms-full.txt'
+ preLoaderRoute: typeof AndroidLlmsFullDottxtRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/agents/llms.txt': {
+ id: '/agents/llms.txt'
+ path: '/agents/llms.txt'
+ fullPath: '/agents/llms.txt'
+ preLoaderRoute: typeof AgentsLlmsDottxtRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/agents/llms-full.txt': {
+ id: '/agents/llms-full.txt'
+ path: '/agents/llms-full.txt'
+ fullPath: '/agents/llms-full.txt'
+ preLoaderRoute: typeof AgentsLlmsFullDottxtRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/llms.mdx/docs/': {
+ id: '/llms.mdx/docs/'
+ path: '/'
+ fullPath: '/llms.mdx/docs/'
+ preLoaderRoute: typeof LlmsDotmdxDocsIndexRouteImport
+ parentRoute: typeof LlmsDotmdxDocsRoute
+ }
'/llms.mdx/docs/$': {
id: '/llms.mdx/docs/$'
- path: '/llms.mdx/docs/$'
+ path: '/$'
fullPath: '/llms.mdx/docs/$'
preLoaderRoute: typeof LlmsDotmdxDocsSplatRouteImport
- parentRoute: typeof rootRouteImport
+ parentRoute: typeof LlmsDotmdxDocsRoute
}
'/api/raw/$': {
id: '/api/raw/$'
@@ -317,6 +876,20 @@ declare module '@tanstack/react-router' {
}
}
+interface LlmsDotmdxDocsRouteChildren {
+ LlmsDotmdxDocsSplatRoute: typeof LlmsDotmdxDocsSplatRoute
+ LlmsDotmdxDocsIndexRoute: typeof LlmsDotmdxDocsIndexRoute
+}
+
+const LlmsDotmdxDocsRouteChildren: LlmsDotmdxDocsRouteChildren = {
+ LlmsDotmdxDocsSplatRoute: LlmsDotmdxDocsSplatRoute,
+ LlmsDotmdxDocsIndexRoute: LlmsDotmdxDocsIndexRoute,
+}
+
+const LlmsDotmdxDocsRouteWithChildren = LlmsDotmdxDocsRoute._addFileChildren(
+ LlmsDotmdxDocsRouteChildren,
+)
+
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
SplatRoute: SplatRoute,
@@ -326,13 +899,39 @@ const rootRouteChildren: RootRouteChildren = {
LlmsChar123sectionChar125DottxtRoute: LlmsChar123sectionChar125DottxtRoute,
LlmsDottxtRoute: LlmsDottxtRoute,
RobotsDottxtRoute: RobotsDottxtRoute,
+ SdkDotmdRoute: SdkDotmdRoute,
+ SdkDotmdxRoute: SdkDotmdxRoute,
SitemapDotxmlRoute: SitemapDotxmlRoute,
+ Char123Char125DotmdRoute: Char123Char125DotmdRoute,
+ Char123Char125DotmdxRoute: Char123Char125DotmdxRoute,
+ AgentsLlmsFullDottxtRoute: AgentsLlmsFullDottxtRoute,
+ AgentsLlmsDottxtRoute: AgentsLlmsDottxtRoute,
+ AndroidLlmsFullDottxtRoute: AndroidLlmsFullDottxtRoute,
+ AndroidLlmsDottxtRoute: AndroidLlmsDottxtRoute,
ApiFeedbackRoute: ApiFeedbackRoute,
ApiSearchRoute: ApiSearchRoute,
+ DashboardLlmsFullDottxtRoute: DashboardLlmsFullDottxtRoute,
+ DashboardLlmsDottxtRoute: DashboardLlmsDottxtRoute,
+ ExpoLlmsFullDottxtRoute: ExpoLlmsFullDottxtRoute,
+ ExpoLlmsDottxtRoute: ExpoLlmsDottxtRoute,
+ FlutterLlmsFullDottxtRoute: FlutterLlmsFullDottxtRoute,
+ FlutterLlmsDottxtRoute: FlutterLlmsDottxtRoute,
+ IntegrationsLlmsFullDottxtRoute: IntegrationsLlmsFullDottxtRoute,
+ IntegrationsLlmsDottxtRoute: IntegrationsLlmsDottxtRoute,
+ IosLlmsFullDottxtRoute: IosLlmsFullDottxtRoute,
+ IosLlmsDottxtRoute: IosLlmsDottxtRoute,
+ LlmsDotmdxDocsRoute: LlmsDotmdxDocsRouteWithChildren,
+ ReactNativeLlmsFullDottxtRoute: ReactNativeLlmsFullDottxtRoute,
+ ReactNativeLlmsDottxtRoute: ReactNativeLlmsDottxtRoute,
SdkSplatRoute: SdkSplatRoute,
+ SdkChar123Char125DotmdRoute: SdkChar123Char125DotmdRoute,
+ SdkChar123Char125DotmdxRoute: SdkChar123Char125DotmdxRoute,
+ UnityLlmsFullDottxtRoute: UnityLlmsFullDottxtRoute,
+ UnityLlmsDottxtRoute: UnityLlmsDottxtRoute,
+ WebCheckoutLlmsFullDottxtRoute: WebCheckoutLlmsFullDottxtRoute,
+ WebCheckoutLlmsDottxtRoute: WebCheckoutLlmsDottxtRoute,
SdkIndexRoute: SdkIndexRoute,
ApiRawSplatRoute: ApiRawSplatRoute,
- LlmsDotmdxDocsSplatRoute: LlmsDotmdxDocsSplatRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
diff --git a/src/routes/$.tsx b/src/routes/$.tsx
index 2e5185e8..e9d5c0a8 100644
--- a/src/routes/$.tsx
+++ b/src/routes/$.tsx
@@ -25,6 +25,7 @@ import {
import { getSdkContextFromRouterPath, SDK_CONTEXT_SLUGS } from "@/lib/sdk-navigation";
import { buildDocsApiPath } from "@/lib/url-base";
import { Code } from "lucide-react";
+import { buildMarkdownAlternatePath } from "@/lib/markdown-alternate";
import { staticCacheMiddleware } from "@/lib/static-cache-middleware";
@@ -70,7 +71,18 @@ export const Route = createFileRoute("/$")({
{ name: "twitter:description", content: pageDescription },
{ name: "twitter:image", content: DEFAULT_SOCIAL_IMAGE_URL },
],
- links: [{ rel: "canonical", href: canonicalUrl }],
+ links: [
+ { rel: "canonical", href: canonicalUrl },
+ ...(loaderData
+ ? [
+ {
+ rel: "alternate",
+ type: "text/markdown",
+ href: buildMarkdownAlternatePath(loaderData.url),
+ },
+ ]
+ : []),
+ ],
};
},
});
@@ -140,8 +152,8 @@ const clientLoader = browserCollections.docs.createClientLoader({
{frontmatter.title}
{frontmatter.description}
-
-
+
+
diff --git a/src/routes/agents/llms-full[.]txt.ts b/src/routes/agents/llms-full[.]txt.ts
new file mode 100644
index 00000000..d3eda041
--- /dev/null
+++ b/src/routes/agents/llms-full[.]txt.ts
@@ -0,0 +1,10 @@
+import { buildLLMFullResponseForSection } from "@/lib/llms";
+import { createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/agents/llms-full.txt")({
+ server: {
+ handlers: {
+ GET: () => buildLLMFullResponseForSection("agents"),
+ },
+ },
+});
diff --git a/src/routes/agents/llms[.]txt.ts b/src/routes/agents/llms[.]txt.ts
new file mode 100644
index 00000000..dc34f91c
--- /dev/null
+++ b/src/routes/agents/llms[.]txt.ts
@@ -0,0 +1,10 @@
+import { buildLLMSummaryResponseForSection } from "@/lib/llms";
+import { createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/agents/llms.txt")({
+ server: {
+ handlers: {
+ GET: () => buildLLMSummaryResponseForSection("agents"),
+ },
+ },
+});
diff --git a/src/routes/android/llms-full[.]txt.ts b/src/routes/android/llms-full[.]txt.ts
new file mode 100644
index 00000000..5ecbba74
--- /dev/null
+++ b/src/routes/android/llms-full[.]txt.ts
@@ -0,0 +1,10 @@
+import { buildLLMFullResponseForSection } from "@/lib/llms";
+import { createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/android/llms-full.txt")({
+ server: {
+ handlers: {
+ GET: () => buildLLMFullResponseForSection("android"),
+ },
+ },
+});
diff --git a/src/routes/android/llms[.]txt.ts b/src/routes/android/llms[.]txt.ts
new file mode 100644
index 00000000..190f8757
--- /dev/null
+++ b/src/routes/android/llms[.]txt.ts
@@ -0,0 +1,10 @@
+import { buildLLMSummaryResponseForSection } from "@/lib/llms";
+import { createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/android/llms.txt")({
+ server: {
+ handlers: {
+ GET: () => buildLLMSummaryResponseForSection("android"),
+ },
+ },
+});
diff --git a/src/routes/dashboard/llms-full[.]txt.ts b/src/routes/dashboard/llms-full[.]txt.ts
new file mode 100644
index 00000000..c9c8f4d3
--- /dev/null
+++ b/src/routes/dashboard/llms-full[.]txt.ts
@@ -0,0 +1,10 @@
+import { buildLLMFullResponseForSection } from "@/lib/llms";
+import { createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/dashboard/llms-full.txt")({
+ server: {
+ handlers: {
+ GET: () => buildLLMFullResponseForSection("dashboard"),
+ },
+ },
+});
diff --git a/src/routes/dashboard/llms[.]txt.ts b/src/routes/dashboard/llms[.]txt.ts
new file mode 100644
index 00000000..c46cec2d
--- /dev/null
+++ b/src/routes/dashboard/llms[.]txt.ts
@@ -0,0 +1,10 @@
+import { buildLLMSummaryResponseForSection } from "@/lib/llms";
+import { createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/dashboard/llms.txt")({
+ server: {
+ handlers: {
+ GET: () => buildLLMSummaryResponseForSection("dashboard"),
+ },
+ },
+});
diff --git a/src/routes/expo/llms-full[.]txt.ts b/src/routes/expo/llms-full[.]txt.ts
new file mode 100644
index 00000000..6141482f
--- /dev/null
+++ b/src/routes/expo/llms-full[.]txt.ts
@@ -0,0 +1,10 @@
+import { buildLLMFullResponseForSection } from "@/lib/llms";
+import { createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/expo/llms-full.txt")({
+ server: {
+ handlers: {
+ GET: () => buildLLMFullResponseForSection("expo"),
+ },
+ },
+});
diff --git a/src/routes/expo/llms[.]txt.ts b/src/routes/expo/llms[.]txt.ts
new file mode 100644
index 00000000..708023d9
--- /dev/null
+++ b/src/routes/expo/llms[.]txt.ts
@@ -0,0 +1,10 @@
+import { buildLLMSummaryResponseForSection } from "@/lib/llms";
+import { createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/expo/llms.txt")({
+ server: {
+ handlers: {
+ GET: () => buildLLMSummaryResponseForSection("expo"),
+ },
+ },
+});
diff --git a/src/routes/flutter/llms-full[.]txt.ts b/src/routes/flutter/llms-full[.]txt.ts
new file mode 100644
index 00000000..a1278da2
--- /dev/null
+++ b/src/routes/flutter/llms-full[.]txt.ts
@@ -0,0 +1,10 @@
+import { buildLLMFullResponseForSection } from "@/lib/llms";
+import { createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/flutter/llms-full.txt")({
+ server: {
+ handlers: {
+ GET: () => buildLLMFullResponseForSection("flutter"),
+ },
+ },
+});
diff --git a/src/routes/flutter/llms[.]txt.ts b/src/routes/flutter/llms[.]txt.ts
new file mode 100644
index 00000000..af17df58
--- /dev/null
+++ b/src/routes/flutter/llms[.]txt.ts
@@ -0,0 +1,10 @@
+import { buildLLMSummaryResponseForSection } from "@/lib/llms";
+import { createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/flutter/llms.txt")({
+ server: {
+ handlers: {
+ GET: () => buildLLMSummaryResponseForSection("flutter"),
+ },
+ },
+});
diff --git a/src/routes/index.tsx b/src/routes/index.tsx
index a13a4164..071b35c1 100644
--- a/src/routes/index.tsx
+++ b/src/routes/index.tsx
@@ -23,6 +23,7 @@ import {
import { normalizeDocsInternalHref } from "@/lib/docs-url";
import { resolveSdkAwareDocsHref } from "@/lib/sdk-navigation";
import { buildDocsPath, toRouterPath } from "@/lib/url-base";
+import { buildMarkdownAlternatePath } from "@/lib/markdown-alternate";
export const Route = createFileRoute("/")({
component: DocsOverview,
@@ -48,7 +49,14 @@ export const Route = createFileRoute("/")({
{ name: "twitter:description", content: description },
{ name: "twitter:image", content: DEFAULT_SOCIAL_IMAGE_URL },
],
- links: [{ rel: "canonical", href: canonicalUrl }],
+ links: [
+ { rel: "canonical", href: canonicalUrl },
+ {
+ rel: "alternate",
+ type: "text/markdown",
+ href: buildMarkdownAlternatePath("/docs"),
+ },
+ ],
};
},
});
@@ -108,7 +116,8 @@ const docsCards: DocCard[] = [
},
{
title: "Superwall Agents",
- description: "Analyze experiments, automate reports, connect tools, and work with hosted machines.",
+ description:
+ "Analyze experiments, automate reports, connect tools, and work with hosted machines.",
href: buildDocsPath("agents"),
icon: ,
},
diff --git a/src/routes/integrations/llms-full[.]txt.ts b/src/routes/integrations/llms-full[.]txt.ts
new file mode 100644
index 00000000..0f7afea5
--- /dev/null
+++ b/src/routes/integrations/llms-full[.]txt.ts
@@ -0,0 +1,10 @@
+import { buildLLMFullResponseForSection } from "@/lib/llms";
+import { createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/integrations/llms-full.txt")({
+ server: {
+ handlers: {
+ GET: () => buildLLMFullResponseForSection("integrations"),
+ },
+ },
+});
diff --git a/src/routes/integrations/llms[.]txt.ts b/src/routes/integrations/llms[.]txt.ts
new file mode 100644
index 00000000..de2db6cc
--- /dev/null
+++ b/src/routes/integrations/llms[.]txt.ts
@@ -0,0 +1,10 @@
+import { buildLLMSummaryResponseForSection } from "@/lib/llms";
+import { createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/integrations/llms.txt")({
+ server: {
+ handlers: {
+ GET: () => buildLLMSummaryResponseForSection("integrations"),
+ },
+ },
+});
diff --git a/src/routes/ios/llms-full[.]txt.ts b/src/routes/ios/llms-full[.]txt.ts
new file mode 100644
index 00000000..2280d9a3
--- /dev/null
+++ b/src/routes/ios/llms-full[.]txt.ts
@@ -0,0 +1,10 @@
+import { buildLLMFullResponseForSection } from "@/lib/llms";
+import { createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/ios/llms-full.txt")({
+ server: {
+ handlers: {
+ GET: () => buildLLMFullResponseForSection("ios"),
+ },
+ },
+});
diff --git a/src/routes/ios/llms[.]txt.ts b/src/routes/ios/llms[.]txt.ts
new file mode 100644
index 00000000..207a84ca
--- /dev/null
+++ b/src/routes/ios/llms[.]txt.ts
@@ -0,0 +1,10 @@
+import { buildLLMSummaryResponseForSection } from "@/lib/llms";
+import { createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/ios/llms.txt")({
+ server: {
+ handlers: {
+ GET: () => buildLLMSummaryResponseForSection("ios"),
+ },
+ },
+});
diff --git a/src/routes/llms[.]mdx.docs.$.ts b/src/routes/llms[.]mdx.docs.$.ts
index 737e36f2..aadbf512 100644
--- a/src/routes/llms[.]mdx.docs.$.ts
+++ b/src/routes/llms[.]mdx.docs.$.ts
@@ -1,20 +1,19 @@
import { createFileRoute, notFound } from "@tanstack/react-router";
-import { getPageMarkdownText, source } from "@/lib/source";
+import {
+ buildMarkdownRouteResponse,
+ getMarkdownRouteBody,
+ slugsFromMarkdownSplat,
+} from "@/lib/markdown-route";
export const Route = createFileRoute("/llms.mdx/docs/$")({
server: {
handlers: {
- GET: async ({ params }) => {
- const slugs = params._splat?.split("/") ?? [];
- const page = source.getPage(slugs);
- if (!page) throw notFound();
+ GET: async ({ params, request }) => {
+ const slugs = slugsFromMarkdownSplat(params._splat);
+ const body = await getMarkdownRouteBody(slugs, request);
+ if (!body) throw notFound();
- return new Response(await getPageMarkdownText(page), {
- headers: {
- "Content-Type": "text/markdown",
- "Access-Control-Allow-Origin": "*",
- },
- });
+ return buildMarkdownRouteResponse(body, slugs);
},
},
},
diff --git a/src/routes/llms[.]mdx.docs.index.ts b/src/routes/llms[.]mdx.docs.index.ts
new file mode 100644
index 00000000..fe097808
--- /dev/null
+++ b/src/routes/llms[.]mdx.docs.index.ts
@@ -0,0 +1,12 @@
+import { createFileRoute } from "@tanstack/react-router";
+import { buildMarkdownRouteResponse, getMarkdownRouteBody } from "@/lib/markdown-route";
+
+export const Route = createFileRoute("/llms.mdx/docs/")({
+ server: {
+ handlers: {
+ GET: async ({ request }) => {
+ return buildMarkdownRouteResponse(await getMarkdownRouteBody([], request), []);
+ },
+ },
+ },
+});
diff --git a/src/routes/llms[.]mdx.docs.ts b/src/routes/llms[.]mdx.docs.ts
new file mode 100644
index 00000000..5472217c
--- /dev/null
+++ b/src/routes/llms[.]mdx.docs.ts
@@ -0,0 +1,3 @@
+import { createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/llms.mdx/docs")({});
diff --git a/src/routes/react-native/llms-full[.]txt.ts b/src/routes/react-native/llms-full[.]txt.ts
new file mode 100644
index 00000000..5d49710b
--- /dev/null
+++ b/src/routes/react-native/llms-full[.]txt.ts
@@ -0,0 +1,10 @@
+import { buildLLMFullResponseForSection } from "@/lib/llms";
+import { createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/react-native/llms-full.txt")({
+ server: {
+ handlers: {
+ GET: () => buildLLMFullResponseForSection("react-native"),
+ },
+ },
+});
diff --git a/src/routes/react-native/llms[.]txt.ts b/src/routes/react-native/llms[.]txt.ts
new file mode 100644
index 00000000..bff403f1
--- /dev/null
+++ b/src/routes/react-native/llms[.]txt.ts
@@ -0,0 +1,10 @@
+import { buildLLMSummaryResponseForSection } from "@/lib/llms";
+import { createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/react-native/llms.txt")({
+ server: {
+ handlers: {
+ GET: () => buildLLMSummaryResponseForSection("react-native"),
+ },
+ },
+});
diff --git a/src/routes/sdk/{$}[.]md.ts b/src/routes/sdk/{$}[.]md.ts
new file mode 100644
index 00000000..feb7e768
--- /dev/null
+++ b/src/routes/sdk/{$}[.]md.ts
@@ -0,0 +1,25 @@
+import { createFileRoute } from "@tanstack/react-router";
+import {
+ buildMarkdownRouteResponse,
+ getMarkdownForPageSlugs,
+ slugsFromMarkdownSplat,
+} from "@/lib/markdown-route";
+
+async function getSdkMarkdownResponse(
+ params: { _splat?: string },
+ request: Request,
+ includeBody = true,
+) {
+ const slugs = ["sdk", ...slugsFromMarkdownSplat(params._splat)];
+ const body = await getMarkdownForPageSlugs(slugs, request);
+ return buildMarkdownRouteResponse(includeBody ? body! : null, slugs);
+}
+
+export const Route = createFileRoute("/sdk/{$}.md")({
+ server: {
+ handlers: {
+ GET: async ({ params, request }) => getSdkMarkdownResponse(params, request),
+ HEAD: async ({ params, request }) => getSdkMarkdownResponse(params, request, false),
+ },
+ },
+});
diff --git a/src/routes/sdk/{$}[.]mdx.ts b/src/routes/sdk/{$}[.]mdx.ts
new file mode 100644
index 00000000..cc1d6302
--- /dev/null
+++ b/src/routes/sdk/{$}[.]mdx.ts
@@ -0,0 +1,25 @@
+import { createFileRoute } from "@tanstack/react-router";
+import {
+ buildMarkdownRouteResponse,
+ getMarkdownForPageSlugs,
+ slugsFromMarkdownSplat,
+} from "@/lib/markdown-route";
+
+async function getSdkMarkdownResponse(
+ params: { _splat?: string },
+ request: Request,
+ includeBody = true,
+) {
+ const slugs = ["sdk", ...slugsFromMarkdownSplat(params._splat)];
+ const body = await getMarkdownForPageSlugs(slugs, request);
+ return buildMarkdownRouteResponse(includeBody ? body! : null, slugs);
+}
+
+export const Route = createFileRoute("/sdk/{$}.mdx")({
+ server: {
+ handlers: {
+ GET: async ({ params, request }) => getSdkMarkdownResponse(params, request),
+ HEAD: async ({ params, request }) => getSdkMarkdownResponse(params, request, false),
+ },
+ },
+});
diff --git a/src/routes/sdk[.]md.ts b/src/routes/sdk[.]md.ts
new file mode 100644
index 00000000..6e6cd753
--- /dev/null
+++ b/src/routes/sdk[.]md.ts
@@ -0,0 +1,17 @@
+import { createFileRoute } from "@tanstack/react-router";
+import { buildMarkdownRouteResponse, getMarkdownForPageSlugs } from "@/lib/markdown-route";
+
+async function getSdkMarkdownResponse(request: Request, includeBody = true) {
+ const slugs = ["sdk"];
+ const body = await getMarkdownForPageSlugs(slugs, request);
+ return buildMarkdownRouteResponse(includeBody ? body! : null, slugs);
+}
+
+export const Route = createFileRoute("/sdk.md")({
+ server: {
+ handlers: {
+ GET: async ({ request }) => getSdkMarkdownResponse(request),
+ HEAD: async ({ request }) => getSdkMarkdownResponse(request, false),
+ },
+ },
+});
diff --git a/src/routes/sdk[.]mdx.ts b/src/routes/sdk[.]mdx.ts
new file mode 100644
index 00000000..3b1b7b2a
--- /dev/null
+++ b/src/routes/sdk[.]mdx.ts
@@ -0,0 +1,17 @@
+import { createFileRoute } from "@tanstack/react-router";
+import { buildMarkdownRouteResponse, getMarkdownForPageSlugs } from "@/lib/markdown-route";
+
+async function getSdkMarkdownResponse(request: Request, includeBody = true) {
+ const slugs = ["sdk"];
+ const body = await getMarkdownForPageSlugs(slugs, request);
+ return buildMarkdownRouteResponse(includeBody ? body! : null, slugs);
+}
+
+export const Route = createFileRoute("/sdk.mdx")({
+ server: {
+ handlers: {
+ GET: async ({ request }) => getSdkMarkdownResponse(request),
+ HEAD: async ({ request }) => getSdkMarkdownResponse(request, false),
+ },
+ },
+});
diff --git a/src/routes/unity/llms-full[.]txt.ts b/src/routes/unity/llms-full[.]txt.ts
new file mode 100644
index 00000000..031ea032
--- /dev/null
+++ b/src/routes/unity/llms-full[.]txt.ts
@@ -0,0 +1,10 @@
+import { buildLLMFullResponseForSection } from "@/lib/llms";
+import { createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/unity/llms-full.txt")({
+ server: {
+ handlers: {
+ GET: () => buildLLMFullResponseForSection("unity"),
+ },
+ },
+});
diff --git a/src/routes/unity/llms[.]txt.ts b/src/routes/unity/llms[.]txt.ts
new file mode 100644
index 00000000..430769f1
--- /dev/null
+++ b/src/routes/unity/llms[.]txt.ts
@@ -0,0 +1,10 @@
+import { buildLLMSummaryResponseForSection } from "@/lib/llms";
+import { createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/unity/llms.txt")({
+ server: {
+ handlers: {
+ GET: () => buildLLMSummaryResponseForSection("unity"),
+ },
+ },
+});
diff --git a/src/routes/web-checkout/llms-full[.]txt.ts b/src/routes/web-checkout/llms-full[.]txt.ts
new file mode 100644
index 00000000..e3320662
--- /dev/null
+++ b/src/routes/web-checkout/llms-full[.]txt.ts
@@ -0,0 +1,10 @@
+import { buildLLMFullResponseForSection } from "@/lib/llms";
+import { createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/web-checkout/llms-full.txt")({
+ server: {
+ handlers: {
+ GET: () => buildLLMFullResponseForSection("web-checkout"),
+ },
+ },
+});
diff --git a/src/routes/web-checkout/llms[.]txt.ts b/src/routes/web-checkout/llms[.]txt.ts
new file mode 100644
index 00000000..c4f0c73b
--- /dev/null
+++ b/src/routes/web-checkout/llms[.]txt.ts
@@ -0,0 +1,10 @@
+import { buildLLMSummaryResponseForSection } from "@/lib/llms";
+import { createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/web-checkout/llms.txt")({
+ server: {
+ handlers: {
+ GET: () => buildLLMSummaryResponseForSection("web-checkout"),
+ },
+ },
+});
diff --git a/src/routes/{$}[.]md.ts b/src/routes/{$}[.]md.ts
new file mode 100644
index 00000000..149b00af
--- /dev/null
+++ b/src/routes/{$}[.]md.ts
@@ -0,0 +1,27 @@
+import { createFileRoute, notFound } from "@tanstack/react-router";
+import {
+ buildMarkdownRouteResponse,
+ getMarkdownForPageSlugs,
+ slugsFromMarkdownSplat,
+} from "@/lib/markdown-route";
+
+async function getMarkdownResponse(
+ params: { _splat?: string },
+ request: Request,
+ includeBody = true,
+) {
+ const slugs = slugsFromMarkdownSplat(params._splat);
+ const body = await getMarkdownForPageSlugs(slugs, request);
+ if (!body) throw notFound();
+
+ return buildMarkdownRouteResponse(includeBody ? body : null, slugs);
+}
+
+export const Route = createFileRoute("/{$}.md")({
+ server: {
+ handlers: {
+ GET: async ({ params, request }) => getMarkdownResponse(params, request),
+ HEAD: async ({ params, request }) => getMarkdownResponse(params, request, false),
+ },
+ },
+});
diff --git a/src/routes/{$}[.]mdx.ts b/src/routes/{$}[.]mdx.ts
new file mode 100644
index 00000000..d1dde82c
--- /dev/null
+++ b/src/routes/{$}[.]mdx.ts
@@ -0,0 +1,27 @@
+import { createFileRoute, notFound } from "@tanstack/react-router";
+import {
+ buildMarkdownRouteResponse,
+ getMarkdownForPageSlugs,
+ slugsFromMarkdownSplat,
+} from "@/lib/markdown-route";
+
+async function getMarkdownResponse(
+ params: { _splat?: string },
+ request: Request,
+ includeBody = true,
+) {
+ const slugs = slugsFromMarkdownSplat(params._splat);
+ const body = await getMarkdownForPageSlugs(slugs, request);
+ if (!body) throw notFound();
+
+ return buildMarkdownRouteResponse(includeBody ? body : null, slugs);
+}
+
+export const Route = createFileRoute("/{$}.mdx")({
+ server: {
+ handlers: {
+ GET: async ({ params, request }) => getMarkdownResponse(params, request),
+ HEAD: async ({ params, request }) => getMarkdownResponse(params, request, false),
+ },
+ },
+});
diff --git a/src/start.test.ts b/src/start.test.ts
index 040c195d..bb24c939 100644
--- a/src/start.test.ts
+++ b/src/start.test.ts
@@ -1,6 +1,12 @@
import assert from "node:assert/strict";
import { describe, test } from "node:test";
-import { resolveLegacyRedirect } from "./start";
+import { negotiateDocsRepresentation, resolveLegacyRedirect, resolveLLMPath } from "./start";
+
+function docsRequest(accept?: string, path = "/docs/dashboard") {
+ return new Request(`https://example.com${path}`, {
+ headers: accept === undefined ? undefined : { Accept: accept },
+ });
+}
describe("resolveLegacyRedirect", () => {
test("does not redirect sdk placeholder routes", () => {
@@ -20,3 +26,52 @@ describe("resolveLegacyRedirect", () => {
);
});
});
+
+describe("negotiateDocsRepresentation", () => {
+ test("selects markdown when text/markdown is preferred", () => {
+ assert.equal(negotiateDocsRepresentation(docsRequest("text/markdown")), "text/markdown");
+ });
+
+ test("preserves legacy markdown media types", () => {
+ assert.equal(negotiateDocsRepresentation(docsRequest("text/plain")), "text/markdown");
+ assert.equal(negotiateDocsRepresentation(docsRequest("text/x-markdown")), "text/markdown");
+ });
+
+ test("selects html when html has a higher q-value", () => {
+ assert.equal(
+ negotiateDocsRepresentation(docsRequest("text/html;q=1,text/markdown;q=0.5")),
+ "text/html",
+ );
+ });
+
+ test("respects q=0 for markdown", () => {
+ assert.equal(
+ negotiateDocsRepresentation(docsRequest("text/markdown;q=0,text/html;q=1")),
+ "text/html",
+ );
+ });
+
+ test("defaults missing Accept and wildcards to html", () => {
+ assert.equal(negotiateDocsRepresentation(docsRequest()), "text/html");
+ assert.equal(negotiateDocsRepresentation(docsRequest("*/*")), "text/html");
+ });
+
+ test("returns undefined for unsupported explicit types", () => {
+ assert.equal(
+ negotiateDocsRepresentation(docsRequest("application/x-content-negotiation-probe")),
+ undefined,
+ );
+ });
+});
+
+describe("resolveLLMPath", () => {
+ test("rewrites canonical docs paths only when markdown wins negotiation", () => {
+ assert.equal(resolveLLMPath(docsRequest("text/markdown")), "/docs/dashboard.md");
+ assert.equal(resolveLLMPath(docsRequest("text/html;q=1,text/markdown;q=0.5")), undefined);
+ });
+
+ test("leaves direct markdown suffixes on their stable URL routes", () => {
+ assert.equal(resolveLLMPath(docsRequest("text/html", "/docs/dashboard.md")), undefined);
+ assert.equal(resolveLLMPath(docsRequest("text/html", "/docs/dashboard.mdx")), undefined);
+ });
+});
diff --git a/src/start.ts b/src/start.ts
index 9171d035..1e84cc19 100644
--- a/src/start.ts
+++ b/src/start.ts
@@ -1,21 +1,15 @@
import { createMiddleware, createStart } from "@tanstack/react-start";
-import { isMarkdownPreferred, rewritePath } from "fumadocs-core/negotiation";
+import { getNegotiator } from "fumadocs-core/negotiation";
import { redirect } from "@tanstack/react-router";
import { buildRedirectRouteRules } from "./lib/redirect-route-rules";
import { DOCS_BASE } from "./lib/url-base";
+import {
+ appendHeaderValue,
+ appendVaryAccept,
+ buildMarkdownAlternatePath,
+ buildMarkdownAlternateLinkHeader,
+} from "./lib/markdown-alternate";
-const { rewrite: rewriteLLMMarkdown } = rewritePath(
- `${DOCS_BASE}{/*path}.md`,
- `${DOCS_BASE}/llms.mdx/docs{/*path}`,
-);
-const { rewrite: rewriteLLMMdx } = rewritePath(
- `${DOCS_BASE}{/*path}.mdx`,
- `${DOCS_BASE}/llms.mdx/docs{/*path}`,
-);
-const { rewrite: rewriteLLMPreferred } = rewritePath(
- `${DOCS_BASE}{/*path}`,
- `${DOCS_BASE}/llms.mdx/docs{/*path}`,
-);
const legacyRedirectRules = buildRedirectRouteRules();
const redirectBypassPrefixes = [
`${DOCS_BASE}/_serverFn`,
@@ -23,6 +17,10 @@ const redirectBypassPrefixes = [
`${DOCS_BASE}/assets`,
];
const staticAssetPathPattern = /\.[a-z0-9]{2,8}$/i;
+const directMarkdownPathPattern = /\.mdx?$/i;
+const markdownMediaTypes = ["text/markdown", "text/plain", "text/x-markdown"] as const;
+const negotiatedMediaTypes = ["text/html", ...markdownMediaTypes] as const;
+type DocsRepresentation = "text/html" | "text/markdown";
function normalizePathname(pathname: string): string {
const normalized = pathname.replace(/\/+$/, "");
@@ -43,6 +41,15 @@ function withRequestSearch(requestUrl: URL, destination: string): URL {
return targetUrl;
}
+async function isKnownDocsRoutePath(pathname: string): Promise {
+ const normalizedPath = normalizePathname(pathname);
+ if (normalizedPath === DOCS_BASE || normalizedPath === `${DOCS_BASE}/sdk` || normalizedPath.startsWith(`${DOCS_BASE}/sdk/`)) return true;
+
+ const docsPath = normalizedPath.slice(DOCS_BASE.length).replace(/^\/+/, "");
+ const { source } = await import("./lib/source");
+ return Boolean(source.getPage(docsPath.split("/").filter(Boolean)));
+}
+
function shouldBypassLegacyRedirect(pathname: string): boolean {
if (!pathname.startsWith(DOCS_BASE)) return true;
if (
@@ -60,6 +67,7 @@ function shouldBypassLLMRewrite(pathname: string): boolean {
if (
pathname === `${DOCS_BASE}/llms.mdx` ||
pathname.startsWith(`${DOCS_BASE}/llms.mdx/`) ||
+ directMarkdownPathPattern.test(pathname) ||
redirectBypassPrefixes.some(
(prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`),
)
@@ -70,16 +78,56 @@ function shouldBypassLLMRewrite(pathname: string): boolean {
return /\.(?!mdx?$)[a-z0-9]{2,8}$/i.test(pathname);
}
+export function negotiateDocsRepresentation(request: Request): DocsRepresentation | undefined {
+ const mediaType = getNegotiator(request).mediaType([...negotiatedMediaTypes]);
+ if (!mediaType) return undefined;
+ if (mediaType === "text/html") return "text/html";
+ return "text/markdown";
+}
+
+function buildNotAcceptableResponse(): Response {
+ return new Response("Not Acceptable", {
+ status: 406,
+ headers: {
+ "Content-Type": "text/plain; charset=utf-8",
+ Vary: "Accept",
+ },
+ });
+}
+
+function buildNotFoundResponse(): Response {
+ return new Response("Not Found", {
+ status: 404,
+ headers: {
+ "Content-Type": "text/plain; charset=utf-8",
+ Vary: "Accept",
+ },
+ });
+}
+
+function withDocsNegotiationHeaders(response: Response, pathname: string): Response {
+ const headers = new Headers(response.headers);
+ appendVaryAccept(headers);
+
+ const contentType = headers.get("Content-Type");
+ if (response.ok && contentType?.toLowerCase().startsWith("text/html")) {
+ appendHeaderValue(headers, "Link", buildMarkdownAlternateLinkHeader(pathname));
+ }
+
+ return new Response(response.body, {
+ status: response.status,
+ statusText: response.statusText,
+ headers,
+ });
+}
+
export function resolveLLMPath(request: Request): string | undefined {
const url = new URL(request.url);
if (shouldBypassLLMRewrite(url.pathname)) return undefined;
- const directRewrite = rewriteLLMMarkdown(url.pathname) || rewriteLLMMdx(url.pathname);
- if (directRewrite) return directRewrite;
+ if (negotiateDocsRepresentation(request) !== "text/markdown") return undefined;
- if (!isMarkdownPreferred(request)) return undefined;
-
- return rewriteLLMPreferred(url.pathname) || undefined;
+ return buildMarkdownAlternatePath(url.pathname);
}
export function resolveLegacyRedirect(requestUrl: URL): string | undefined {
@@ -112,12 +160,14 @@ const legacyRedirectMiddleware = createMiddleware().server(({ next, request }) =
return next();
});
-const llmMiddleware = createMiddleware().server(({ next, request }) => {
+const llmMiddleware = createMiddleware().server(async ({ next, request }) => {
const url = new URL(request.url);
let destination: string | undefined;
+ let representation: DocsRepresentation | undefined;
try {
destination = resolveLLMPath(request);
+ representation = negotiateDocsRepresentation(request);
} catch {
return next();
}
@@ -126,10 +176,30 @@ const llmMiddleware = createMiddleware().server(({ next, request }) => {
throw redirect({
href: withRequestSearch(url, destination).toString(),
statusCode: 307,
+ headers: {
+ "Access-Control-Allow-Origin": "*",
+ Vary: "Accept",
+ },
});
}
- return next();
+ const bypassLLMRewrite = shouldBypassLLMRewrite(url.pathname);
+
+ if (!bypassLLMRewrite && !representation) {
+ if (!(await isKnownDocsRoutePath(url.pathname))) {
+ return buildNotFoundResponse();
+ }
+
+ return buildNotAcceptableResponse();
+ }
+
+ const result = await next();
+ return {
+ ...result,
+ response: !bypassLLMRewrite
+ ? withDocsNegotiationHeaders(result.response, url.pathname)
+ : result.response,
+ };
});
export const startInstance = createStart(() => {