diff --git a/src/routes/$libraryId/$version.docs.$.tsx b/src/routes/$libraryId/$version.docs.$.tsx index c980bc5a6..ea0ec0635 100644 --- a/src/routes/$libraryId/$version.docs.$.tsx +++ b/src/routes/$libraryId/$version.docs.$.tsx @@ -1,10 +1,37 @@ import { seo } from '~/utils/seo' import { Doc } from '~/components/Doc' -import { loadDocs } from '~/utils/docs' +import { + loadDocs, + prefersMarkdown, + createMarkdownResponse, +} from '~/utils/docs' import { findLibrary, getBranch, getLibrary } from '~/libraries' import { DocContainer } from '~/components/DocContainer' import { notFound } from '@tanstack/react-router' +export const ServerRoute = createServerFileRoute().methods({ + GET: async ({ request, params }) => { + const acceptHeader = request.headers.get('Accept') + + if (prefersMarkdown(acceptHeader)) { + const url = new URL(request.url) + const { libraryId, version, _splat: docsPath } = params + const library = getLibrary(libraryId) + const root = library.docsRoot || 'docs' + + const doc = await loadDocs({ + repo: library.repo, + branch: getBranch(library, version), + docsPath: `${root}/${docsPath}`, + currentPath: url.pathname, + redirectPath: `/${library.id}/${version}/docs/overview`, + }) + + return createMarkdownResponse(doc.title, doc.content) + } + }, +}) + export const Route = createFileRoute({ staleTime: 1000 * 60 * 5, loader: (ctx) => { @@ -43,6 +70,7 @@ export const Route = createFileRoute({ return { 'cache-control': 'public, max-age=0, must-revalidate', 'cdn-cache-control': 'max-age=300, stale-while-revalidate=300, durable', + 'vary': 'Accept', } }, }) diff --git a/src/routes/$libraryId/$version.docs.framework.$framework.$.tsx b/src/routes/$libraryId/$version.docs.framework.$framework.$.tsx index f246e3cdb..7c54b9b80 100644 --- a/src/routes/$libraryId/$version.docs.framework.$framework.$.tsx +++ b/src/routes/$libraryId/$version.docs.framework.$framework.$.tsx @@ -1,10 +1,37 @@ import { seo } from '~/utils/seo' import { Doc } from '~/components/Doc' -import { loadDocs } from '~/utils/docs' +import { + loadDocs, + prefersMarkdown, + createMarkdownResponse, +} from '~/utils/docs' import { getBranch, getLibrary } from '~/libraries' import { capitalize } from '~/utils/utils' import { DocContainer } from '~/components/DocContainer' +export const ServerRoute = createServerFileRoute().methods({ + GET: async ({ request, params }) => { + const acceptHeader = request.headers.get('Accept') + + if (prefersMarkdown(acceptHeader)) { + const url = new URL(request.url) + const { libraryId, version, framework, _splat: docsPath } = params + const library = getLibrary(libraryId) + const root = library.docsRoot || 'docs' + + const doc = await loadDocs({ + repo: library.repo, + branch: getBranch(library, version), + docsPath: `${root}/framework/${framework}/${docsPath}`, + currentPath: url.pathname, + redirectPath: `/${library.id}/${version}/docs/overview`, + }) + + return createMarkdownResponse(doc.title, doc.content) + } + }, +}) + export const Route = createFileRoute({ staleTime: 1000 * 60 * 5, loader: (ctx) => { @@ -36,6 +63,13 @@ export const Route = createFileRoute({ }), } }, + headers: (ctx) => { + return { + 'cache-control': 'public, max-age=0, must-revalidate', + 'cdn-cache-control': 'max-age=300, stale-while-revalidate=300, durable', + 'vary': 'Accept', + } + }, }) function Docs() { diff --git a/src/utils/docs.ts b/src/utils/docs.ts index 4af832d8e..d3ea262ff 100644 --- a/src/utils/docs.ts +++ b/src/utils/docs.ts @@ -216,3 +216,42 @@ export const fetchAllMaintainerStats = createServerFn({ } }) */ + +// Helper function to check if the Accept header prefers markdown +export function prefersMarkdown(acceptHeader: string | null): boolean { + if (!acceptHeader) return false + + const accepts = acceptHeader.split(',').map(type => { + const [mediaType, ...params] = type.trim().split(';') + const quality = params.find(p => p.trim().startsWith('q=')) + const q = quality ? parseFloat(quality.split('=')[1]) : 1.0 + return { mediaType: mediaType.toLowerCase(), q } + }) + + const markdownQ = accepts.find(a => + a.mediaType === 'text/markdown' || a.mediaType === 'text/plain' + )?.q || 0 + + const htmlQ = accepts.find(a => + a.mediaType === 'text/html' || a.mediaType === '*/*' + )?.q || 0 + + return markdownQ > 0 && markdownQ > htmlQ +} + +// Helper function to create markdown response +export function createMarkdownResponse( + title: string, + content: string +): Response { + const markdownContent = `# ${title}\n${content}` + + return new Response(markdownContent, { + headers: { + 'Content-Type': 'text/markdown; charset=utf-8', + 'Cache-Control': 'public, max-age=0, must-revalidate', + 'Cdn-Cache-Control': 'max-age=300, stale-while-revalidate=300, durable', + 'Vary': 'Accept', + }, + }) +}