mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
docs: Set path on 404 errors (#46848)
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * 404 handling now returns path-aware not-found pages for missing docs and guides, improving accurate user-facing 404 responses. * Improved file-missing errors for guides so missing content cases surface clearer diagnostic info. * **Chores** * Enhanced 404 telemetry so missing-path information is recorded for better monitoring and quicker troubleshooting. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
ebcd052018
commit
954c861b11
11 files changed
+55
-36
No files matched your search
@@ -6,8 +6,8 @@ import {
|
||||
wrapInMarkdownCodeBlock,
|
||||
} from '~/app/guides/getting-started/ai-prompts/[slug]/AiPrompts.utils'
|
||||
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
|
||||
import { notFoundWithPathname } from '~/features/docs/notFound.utils'
|
||||
import { source } from 'common-tags'
|
||||
import { notFound } from 'next/navigation'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
@@ -18,7 +18,7 @@ export default async function AiPromptsPage(props: { params: Promise<{ slug: str
|
||||
|
||||
const prompt = await getAiPrompt(slug)
|
||||
if (!prompt) {
|
||||
notFound()
|
||||
notFoundWithPathname(`/guides/ai-tools/ai-prompts/${slug}`)
|
||||
}
|
||||
|
||||
let { heading, content } = prompt
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { notFound } from 'next/navigation'
|
||||
import { relative } from 'path'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
|
||||
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
|
||||
import { genGuideMeta, removeRedundantH1 } from '~/features/docs/GuidesMdx.utils'
|
||||
import { getGitHubFileContents } from '~/lib/octokit'
|
||||
import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLinkTransform'
|
||||
import { notFoundWithPathname } from '~/features/docs/notFound.utils'
|
||||
import { linkTransform, UrlTransformFunction } from '~/lib/mdx/plugins/rehypeLinkTransform'
|
||||
import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition'
|
||||
import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle'
|
||||
import { getGitHubFileContents } from '~/lib/octokit'
|
||||
import { SerializeOptions } from '~/types/next-mdx-remote-serialize'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
@@ -76,7 +75,7 @@ const getContent = async ({ slug }: Params) => {
|
||||
const page = pageMap.find(({ slug: validSlug }) => validSlug && validSlug === slug)
|
||||
|
||||
if (!page) {
|
||||
notFound()
|
||||
notFoundWithPathname(`/guides/ai/python/${slug}`)
|
||||
}
|
||||
|
||||
const { remoteFile, meta } = page
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
genGuidesStaticParams,
|
||||
removeRedundantH1,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { notFoundWithPathname } from '~/features/docs/notFound.utils'
|
||||
import { newEditLink } from '~/features/helpers.edit-link'
|
||||
import { Guide, GuideArticle, GuideFooter, GuideHeader, GuideMdxContent } from '~/features/ui/guide'
|
||||
// End of third-party imports
|
||||
@@ -20,7 +21,6 @@ import type { SerializeOptions } from '~/types/next-mdx-remote-serialize'
|
||||
import { isFeatureEnabled } from 'common'
|
||||
import matter from 'gray-matter'
|
||||
import Link from 'next/link'
|
||||
import { notFound } from 'next/navigation'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
import emoji from 'remark-emoji'
|
||||
import { Button } from 'ui'
|
||||
@@ -334,11 +334,14 @@ interface Params {
|
||||
}
|
||||
|
||||
const WrappersDocs = async (props: { params: Promise<Params> }) => {
|
||||
const params = await props.params
|
||||
|
||||
if (!isFeatureEnabled('docs:fdw')) {
|
||||
notFound()
|
||||
notFoundWithPathname(
|
||||
`/guides/database/extensions/wrappers${params.slug?.length ? `/${params.slug.join('/')}` : ''}`
|
||||
)
|
||||
}
|
||||
|
||||
const params = await props.params
|
||||
const { isExternal, meta, assetsBaseUrl, ...data } = await getContent(params)
|
||||
|
||||
// Create a combined URL transformer that handles both regular URLs and asset URLs
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { notFound } from 'next/navigation'
|
||||
import { relative } from 'node:path'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
|
||||
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
|
||||
import { genGuideMeta, removeRedundantH1 } from '~/features/docs/GuidesMdx.utils'
|
||||
import { getGitHubFileContents } from '~/lib/octokit'
|
||||
import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLinkTransform'
|
||||
import { notFoundWithPathname } from '~/features/docs/notFound.utils'
|
||||
import { linkTransform, UrlTransformFunction } from '~/lib/mdx/plugins/rehypeLinkTransform'
|
||||
import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition'
|
||||
import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle'
|
||||
import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs'
|
||||
import { getGitHubFileContents } from '~/lib/octokit'
|
||||
import { SerializeOptions } from '~/types/next-mdx-remote-serialize'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
@@ -75,7 +74,7 @@ const getContent = async ({ slug }: Params) => {
|
||||
const page = pageMap.find(({ slug: validSlug }) => validSlug && validSlug === slug)
|
||||
|
||||
if (!page) {
|
||||
notFound()
|
||||
notFoundWithPathname(`/guides/deployment/ci/${slug}`)
|
||||
}
|
||||
|
||||
const { remoteFile, meta } = page
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
|
||||
import { genGuideMeta, removeRedundantH1 } from '~/features/docs/GuidesMdx.utils'
|
||||
import { notFoundWithPathname } from '~/features/docs/notFound.utils'
|
||||
import { getEmptyArray } from '~/features/helpers.fn'
|
||||
import { IS_DEV } from '~/lib/constants'
|
||||
import { isValidGuideFrontmatter } from '~/lib/docs'
|
||||
@@ -10,7 +11,6 @@ import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs'
|
||||
import { getGitHubFileContents } from '~/lib/octokit'
|
||||
import { SerializeOptions } from '~/types/next-mdx-remote-serialize'
|
||||
import matter from 'gray-matter'
|
||||
import { notFound } from 'next/navigation'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
|
||||
import {
|
||||
@@ -106,7 +106,7 @@ const getContent = async ({ slug }: Params) => {
|
||||
const page = pageMap.find((page) => page.slug === requestedSlug)
|
||||
|
||||
if (!page) {
|
||||
notFound()
|
||||
notFoundWithPathname(`/guides/deployment/terraform${slug?.length ? `/${slug.join('/')}` : ''}`)
|
||||
}
|
||||
|
||||
const { meta, remoteFile, useRoot } = page
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { isAbsolute, relative } from 'path'
|
||||
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
|
||||
import { genGuideMeta } from '~/features/docs/GuidesMdx.utils'
|
||||
import { notFoundWithPathname } from '~/features/docs/notFound.utils'
|
||||
import { getEmptyArray } from '~/features/helpers.fn'
|
||||
import { IS_DEV } from '~/lib/constants'
|
||||
import { linkTransform, UrlTransformFunction } from '~/lib/mdx/plugins/rehypeLinkTransform'
|
||||
@@ -9,7 +10,6 @@ import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle'
|
||||
import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs'
|
||||
import { getGitHubFileContents } from '~/lib/octokit'
|
||||
import { SerializeOptions } from '~/types/next-mdx-remote-serialize'
|
||||
import { notFound } from 'next/navigation'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
|
||||
// We fetch these docs at build time from an external repo
|
||||
@@ -128,7 +128,7 @@ const getContent = async ({ slug }: Params) => {
|
||||
const page = pageMap.find((page) => page.slug === slug?.at(0))
|
||||
|
||||
if (!page) {
|
||||
notFound()
|
||||
notFoundWithPathname(`/guides/graphql${slug?.length ? `/${slug.join('/')}` : ''}`)
|
||||
}
|
||||
|
||||
const { remoteFile, meta } = page
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { notFound } from 'next/navigation'
|
||||
|
||||
import { notFoundWithPathname } from '~/features/docs/notFound.utils'
|
||||
import TroubleshootingPage from '~/features/docs/Troubleshooting.page'
|
||||
import { getAllTroubleshootingEntries, getArticleSlug } from '~/features/docs/Troubleshooting.utils'
|
||||
import { PROD_URL } from '~/lib/constants'
|
||||
@@ -20,7 +19,7 @@ export default async function TroubleshootingEntryPage(props: {
|
||||
const entry = allTroubleshootingEntries.find((entry) => getArticleSlug(entry) === slug)
|
||||
|
||||
if (!entry) {
|
||||
notFound()
|
||||
notFoundWithPathname(`/guides/troubleshooting/${slug}`)
|
||||
}
|
||||
|
||||
return <TroubleshootingPage entry={entry} />
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { notFound } from 'next/navigation'
|
||||
|
||||
import { REFERENCES } from '~/content/navigation.references'
|
||||
import { notFoundWithPathname } from '~/features/docs/notFound.utils'
|
||||
import { ApiReferencePage } from '~/features/docs/Reference.apiPage'
|
||||
import { CliReferencePage } from '~/features/docs/Reference.cliPage'
|
||||
import { ClientSdkReferencePage } from '~/features/docs/Reference.sdkPage'
|
||||
@@ -18,9 +17,10 @@ export default async function ReferencePage(props: { params: Promise<{ slug: Arr
|
||||
const params = await props.params
|
||||
|
||||
const { slug } = params
|
||||
const referencePath = `/reference/${slug.join('/')}`
|
||||
|
||||
if (!Object.keys(REFERENCES).includes(slug[0].replaceAll('-', '_'))) {
|
||||
notFound()
|
||||
notFoundWithPathname(referencePath)
|
||||
}
|
||||
|
||||
const parsedPath = parseReferencePath(slug)
|
||||
@@ -34,7 +34,7 @@ export default async function ReferencePage(props: { params: Promise<{ slug: Arr
|
||||
|
||||
const sdkData = REFERENCES[sdkId]
|
||||
if (sdkData.enabled === false) {
|
||||
notFound()
|
||||
notFoundWithPathname(referencePath)
|
||||
}
|
||||
|
||||
const latestVersion = sdkData.versions[0]
|
||||
@@ -52,7 +52,7 @@ export default async function ReferencePage(props: { params: Promise<{ slug: Arr
|
||||
<SelfHostingReferencePage service={parsedPath.service} servicePath={parsedPath.servicePath} />
|
||||
)
|
||||
} else {
|
||||
notFound()
|
||||
notFoundWithPathname(referencePath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@ import { fromMarkdown } from 'mdast-util-from-markdown'
|
||||
import { gfmFromMarkdown } from 'mdast-util-gfm'
|
||||
import { gfm } from 'micromark-extension-gfm'
|
||||
import { type Metadata, type ResolvingMetadata } from 'next'
|
||||
import { notFound } from 'next/navigation'
|
||||
|
||||
import { newEditLink } from './GuidesMdx.template'
|
||||
import { checkGuidePageEnabled } from './NavigationPageStatus.utils'
|
||||
import { notFoundWithPathname } from './notFound.utils'
|
||||
|
||||
const { metadataTitle } = getCustomContent(['metadata:title'])
|
||||
|
||||
@@ -56,7 +56,7 @@ const getGuidesMarkdownInternal = async (slug: string[]) => {
|
||||
!fullPath.startsWith(GUIDES_DIRECTORY) ||
|
||||
!PUBLISHED_SECTIONS.some((section) => relPath.startsWith(section))
|
||||
) {
|
||||
notFound()
|
||||
notFoundWithPathname(guidesPath)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,7 +65,7 @@ const getGuidesMarkdownInternal = async (slug: string[]) => {
|
||||
*/
|
||||
if (!checkGuidePageEnabled(guidesPath)) {
|
||||
console.log('Page is disabled: %s', guidesPath)
|
||||
notFound()
|
||||
notFoundWithPathname(guidesPath)
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -100,7 +100,7 @@ const getGuidesMarkdownInternal = async (slug: string[]) => {
|
||||
)
|
||||
Sentry.captureException(error)
|
||||
}
|
||||
notFound()
|
||||
notFoundWithPathname(guidesPath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import * as Sentry from '@sentry/nextjs'
|
||||
import { notFound } from 'next/navigation'
|
||||
|
||||
/**
|
||||
* Triggers a Next.js 404 while recording the requested path as a Sentry tag.
|
||||
*
|
||||
* `notFound()` itself does not create a Sentry event, but tagging the active
|
||||
* scope means any error captured during the same request (and any event the
|
||||
* Sentry SDK records for the render) carries the exact path under
|
||||
* `404.pathname`. That makes 404s filterable and groupable in Sentry/Discover
|
||||
* instead of only being inferable from the route's transaction name.
|
||||
*
|
||||
* @param pathname - The user-facing path that was not found, e.g.
|
||||
* `/guides/functions/runtimes/node-22`.
|
||||
*/
|
||||
export function notFoundWithPathname(pathname: string): never {
|
||||
Sentry.setTag('404.pathname', pathname)
|
||||
notFound()
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import matter from 'gray-matter'
|
||||
import { promises as fs } from 'node:fs'
|
||||
import { join, relative, resolve } from 'node:path'
|
||||
|
||||
import { extractMessageFromAnyError, FileNotFoundError, MultiError } from '~/app/api/utils'
|
||||
import { preprocessMdxWithDefaults } from '~/features/directives/utils'
|
||||
import { checkGuidePageEnabled } from '~/features/docs/NavigationPageStatus.utils'
|
||||
import { Both, Result } from '~/features/helpers.fn'
|
||||
import { GUIDES_DIRECTORY } from '~/lib/docs'
|
||||
import { processMdx } from '~/scripts/helpers.mdx'
|
||||
import matter from 'gray-matter'
|
||||
|
||||
import { GuideModel } from './guideModel'
|
||||
|
||||
/**
|
||||
@@ -140,7 +140,7 @@ export class GuideModelLoader {
|
||||
},
|
||||
(error) => {
|
||||
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
||||
throw new FileNotFoundError('', error)
|
||||
throw new FileNotFoundError(join(GUIDES_DIRECTORY, relPath), error)
|
||||
}
|
||||
throw new Error(
|
||||
`Failed to load guide from ${relPath}: ${extractMessageFromAnyError(error)}`,
|
||||
|
||||
Reference in new issue
Block a user