diff --git a/.github/pr-assets/ai-guides-structured-outputs-error.png b/.github/pr-assets/ai-guides-structured-outputs-error.png new file mode 100644 index 000000000..1947a27c8 Binary files /dev/null and b/.github/pr-assets/ai-guides-structured-outputs-error.png differ diff --git a/src/routes/$libraryId/$version.docs.$.tsx b/src/routes/$libraryId/$version.docs.$.tsx index ee525cec5..5906dac26 100644 --- a/src/routes/$libraryId/$version.docs.$.tsx +++ b/src/routes/$libraryId/$version.docs.$.tsx @@ -1,5 +1,6 @@ import { seo } from '~/utils/seo' import { Doc } from '~/components/Doc' +import { isDocsNotFoundError } from '~/utils/docs-errors' import { loadDocsPage, resolveDocsRedirect } from '~/utils/docs' import { findLibrary, getBranch, getLibrary } from '~/libraries' import { DocContainer } from '~/components/DocContainer' @@ -36,6 +37,7 @@ export const Route = createFileRoute('/$libraryId/$version/docs/$')({ }) } catch (error) { const isNotFoundError = + isDocsNotFoundError(error) || isNotFound(error) || (error && typeof error === 'object' && 'isNotFound' in error) diff --git a/src/routes/$libraryId/$version.docs.framework.$framework.$.tsx b/src/routes/$libraryId/$version.docs.framework.$framework.$.tsx index b0eb34fb6..b810f961f 100644 --- a/src/routes/$libraryId/$version.docs.framework.$framework.$.tsx +++ b/src/routes/$libraryId/$version.docs.framework.$framework.$.tsx @@ -7,6 +7,7 @@ import { } from '@tanstack/react-router' import { seo } from '~/utils/seo' import { Doc } from '~/components/Doc' +import { isDocsNotFoundError } from '~/utils/docs-errors' import { loadDocsPage, resolveDocsRedirect } from '~/utils/docs' import { getBranch, getLibrary } from '~/libraries' import { capitalize } from '~/utils/utils' @@ -36,6 +37,7 @@ export const Route = createFileRoute( // This handles cases like switching frameworks where the same doc path doesn't exist // Check both isNotFound() and the serialized form from server functions const isNotFoundError = + isDocsNotFoundError(error) || isNotFound(error) || (error && typeof error === 'object' && 'isNotFound' in error) diff --git a/src/routes/$libraryId/$version.docs.framework.$framework.{$}[.]md.tsx b/src/routes/$libraryId/$version.docs.framework.$framework.{$}[.]md.tsx index fe302cbe8..c49d1a3f4 100644 --- a/src/routes/$libraryId/$version.docs.framework.$framework.{$}[.]md.tsx +++ b/src/routes/$libraryId/$version.docs.framework.$framework.{$}[.]md.tsx @@ -1,6 +1,7 @@ import { findLibrary, getBranch } from '~/libraries' -import { loadDocs } from '~/utils/docs' -import { notFound, createFileRoute } from '@tanstack/react-router' +import { isDocsNotFoundError } from '~/utils/docs-errors' +import { loadDocs, resolveDocsRedirect } from '~/utils/docs' +import { notFound, redirect, createFileRoute } from '@tanstack/react-router' import { filterFrameworkContent } from '~/utils/markdown/filterFrameworkContent' import { getPackageManager } from '~/utils/markdown/installCommand' @@ -34,12 +35,41 @@ export const Route = createFileRoute( const root = library.docsRoot || 'docs' - const doc = await loadDocs({ - repo: library.repo, - branch: getBranch(library, version), - docsRoot: root, - docsPath: `framework/${framework}/${docsPath}`, - }) + let doc + + try { + doc = await loadDocs({ + repo: library.repo, + branch: getBranch(library, version), + docsRoot: root, + docsPath: `framework/${framework}/${docsPath}`, + }) + } catch (error) { + if (isDocsNotFoundError(error)) { + const redirectPath = await resolveDocsRedirect({ + repo: library.repo, + branch: getBranch(library, version), + docsRoot: root, + docsPaths: docsPath + ? [ + `framework/${framework}/${docsPath}`, + `${framework}/${docsPath}`, + ] + : [], + }) + + if (redirectPath !== null) { + throw redirect({ + href: `/${libraryId}/${version}/docs/${redirectPath}.md`, + statusCode: 308, + }) + } + + throw notFound() + } + + throw error + } // Filter framework-specific content using framework from URL path const filteredContent = filterFrameworkContent(doc.content, { diff --git a/src/routes/$libraryId/$version.docs.{$}[.]md.tsx b/src/routes/$libraryId/$version.docs.{$}[.]md.tsx index 5ec537ade..a37af6db7 100644 --- a/src/routes/$libraryId/$version.docs.{$}[.]md.tsx +++ b/src/routes/$libraryId/$version.docs.{$}[.]md.tsx @@ -1,6 +1,7 @@ -import { createFileRoute } from '@tanstack/react-router' +import { createFileRoute, notFound, redirect } from '@tanstack/react-router' import { getBranch, getLibrary, type LibraryId } from '~/libraries' -import { loadDocs } from '~/utils/docs' +import { isDocsNotFoundError } from '~/utils/docs-errors' +import { loadDocs, resolveDocsRedirect } from '~/utils/docs' import { filterFrameworkContent } from '~/utils/markdown/filterFrameworkContent' import { getPackageManager } from '~/utils/markdown/installCommand' @@ -23,12 +24,36 @@ export const Route = createFileRoute('/$libraryId/$version/docs/{$}.md')({ const library = getLibrary(libraryId as LibraryId) const root = library.docsRoot || 'docs' - const doc = await loadDocs({ - repo: library.repo, - branch: getBranch(library, version), - docsRoot: root, - docsPath, - }) + let doc + + try { + doc = await loadDocs({ + repo: library.repo, + branch: getBranch(library, version), + docsRoot: root, + docsPath, + }) + } catch (error) { + if (isDocsNotFoundError(error)) { + const redirectPath = await resolveDocsRedirect({ + repo: library.repo, + branch: getBranch(library, version), + docsRoot: root, + docsPaths: [docsPath], + }) + + if (redirectPath !== null) { + throw redirect({ + href: `/${libraryId}/${version}/docs/${redirectPath}.md`, + statusCode: 308, + }) + } + + throw notFound() + } + + throw error + } // Filter framework-specific content only if framework is explicitly specified const filteredContent = framework diff --git a/src/utils/docs-errors.ts b/src/utils/docs-errors.ts new file mode 100644 index 000000000..12ef42853 --- /dev/null +++ b/src/utils/docs-errors.ts @@ -0,0 +1,25 @@ +type DocsNotFoundError = Error & { + data: { message: string } + isDocsNotFound: true +} + +export function createDocsNotFoundError(message = 'No doc was found here!') { + const error = new Error(message) as DocsNotFoundError + + error.name = 'DocsNotFoundError' + error.data = { message } + error.isDocsNotFound = true + + return error +} + +export function isDocsNotFoundError( + error: unknown, +): error is DocsNotFoundError { + return ( + typeof error === 'object' && + error !== null && + 'isDocsNotFound' in error && + error.isDocsNotFound === true + ) +} diff --git a/src/utils/docs.functions.ts b/src/utils/docs.functions.ts index dbfeea200..9f0e1309b 100644 --- a/src/utils/docs.functions.ts +++ b/src/utils/docs.functions.ts @@ -1,4 +1,3 @@ -import { notFound } from '@tanstack/react-router' import { createServerFn } from '@tanstack/react-start' import { setResponseHeader } from '@tanstack/react-start/server' import removeMarkdown from 'remove-markdown' @@ -9,6 +8,7 @@ import { fetchRepoFile, isRecoverableGitHubContentError, } from '~/utils/documents.server' +import { createDocsNotFoundError } from './docs-errors' import { renderMarkdownToRsc } from './markdown' import { getCachedDocsArtifact } from './github-content-cache.server' import { buildRedirectManifest, type RedirectManifestEntry } from './redirects' @@ -223,7 +223,7 @@ export const fetchDocs = createServerFn({ method: 'GET' }) const file = await readRepoFileOrFallback(repo, branch, filePath) if (!file) { - throw notFound() + throw createDocsNotFoundError() } const frontMatter = extractFrontMatter(file) @@ -267,7 +267,7 @@ export const fetchFile = createServerFn({ method: 'GET' }) const file = await readRepoFileOrFallback(repo, branch, filePath) if (!file) { - throw notFound() + throw createDocsNotFoundError() } setDocsCacheHeaders('max-age=3600, stale-while-revalidate=3600, durable') @@ -284,7 +284,7 @@ export const fetchRepoDirectoryContents = createServerFn({ const githubContents = await fetchApiContents(repo, branch, startingPath) if (!githubContents) { - throw notFound() + throw createDocsNotFoundError() } setDocsCacheHeaders('max-age=3600, stale-while-revalidate=3600, durable') diff --git a/src/utils/docs.ts b/src/utils/docs.ts index 204f37813..833ff7989 100644 --- a/src/utils/docs.ts +++ b/src/utils/docs.ts @@ -1,4 +1,3 @@ -import { notFound } from '@tanstack/react-router' import { fetchDocs, fetchDocsPage, @@ -7,6 +6,7 @@ import { fetchFile, fetchRepoDirectoryContents, } from './docs.functions' +import { createDocsNotFoundError } from './docs-errors' import { removeLeadingSlash } from './utils' export const loadDocs = async ({ @@ -21,11 +21,7 @@ export const loadDocs = async ({ docsPath: string }) => { if (!branch || !docsRoot || !docsPath) { - throw notFound({ - data: { - message: 'No doc was found here!', - }, - }) + throw createDocsNotFoundError() } return fetchDocs({ @@ -49,11 +45,7 @@ export const loadDocsPage = async ({ docsPath: string }) => { if (!branch || !docsRoot || !docsPath) { - throw notFound({ - data: { - message: 'No doc was found here!', - }, - }) + throw createDocsNotFoundError() } return fetchDocsPage({