diff --git a/apps/docs/app/reference/[...slug]/page.tsx b/apps/docs/app/reference/[...slug]/page.tsx index 0ed5497704c..d5b295ac022 100644 --- a/apps/docs/app/reference/[...slug]/page.tsx +++ b/apps/docs/app/reference/[...slug]/page.tsx @@ -1,5 +1,3 @@ -import { notFound } from 'next/navigation' - import { REFERENCES } from '~/content/navigation.references' import { ApiReferencePage } from '~/features/docs/Reference.apiPage' import { CliReferencePage } from '~/features/docs/Reference.cliPage' @@ -11,6 +9,7 @@ import { parseReferencePath, redirectNonexistentReferenceSection, } from '~/features/docs/Reference.utils' +import { notFound } from 'next/navigation' export const dynamicParams = false @@ -46,7 +45,7 @@ export default async function ReferencePage(props: { params: Promise<{ slug: Arr } else if (isCliReference) { return } else if (isApiReference) { - return + return } else if (isSelfHostingReference) { return ( diff --git a/apps/docs/features/docs/Reference.apiPage.tsx b/apps/docs/features/docs/Reference.apiPage.tsx index 3e77558f3e6..94f2e69e27c 100644 --- a/apps/docs/features/docs/Reference.apiPage.tsx +++ b/apps/docs/features/docs/Reference.apiPage.tsx @@ -3,11 +3,13 @@ import { reference_api } from '~/components/Navigation/NavigationMenu/Navigation import { ClientLibIntroduction } from '~/features/docs/Reference.introduction' import { ReferenceNavigation } from '~/features/docs/Reference.navigation' import { ReferenceContentScrollHandler } from '~/features/docs/Reference.navigation.client' -import { RefSections } from '~/features/docs/Reference.sections' +import { RefSection } from '~/features/docs/Reference.sections' import { LayoutMainContent } from '~/layouts/DefaultLayout' import { SidebarSkeleton } from '~/layouts/MainSkeleton' -export async function ApiReferencePage() { +export async function ApiReferencePage({ sectionSlug }: { sectionSlug?: string }) { + const isIndexOrIntro = !sectionSlug || sectionSlug === 'introduction' + return ( } >
- - + {isIndexOrIntro ? ( + + ) : ( + + )}
diff --git a/apps/docs/features/docs/Reference.navigation.client.tsx b/apps/docs/features/docs/Reference.navigation.client.tsx index 4ddf294fd20..0211b33f8fd 100644 --- a/apps/docs/features/docs/Reference.navigation.client.tsx +++ b/apps/docs/features/docs/Reference.navigation.client.tsx @@ -239,11 +239,13 @@ export function RefLink({ section, skipChildren = false, className, + realNavigation, }: { basePath: string section: AbbrevApiReferenceSection skipChildren?: boolean className?: string + realNavigation?: boolean }) { const ref = useRef(null) @@ -272,16 +274,19 @@ export function RefLink({ return ( <> {isCompoundSection ? ( - + ) : ( {section.title} @@ -321,9 +326,11 @@ function useCompoundRefLinkActive(basePath: string, section: AbbrevApiReferenceS function CompoundRefLink({ basePath, section, + realNavigation, }: { basePath: string section: AbbrevApiReferenceSection + realNavigation?: boolean }) { const { open, setOpen, isActive } = useCompoundRefLinkActive(basePath, section) @@ -357,7 +364,7 @@ function CompoundRefLink({ {(section.items || []).map((item, idx) => { return (
  • - +
  • ) })} diff --git a/apps/docs/features/docs/Reference.navigation.tsx b/apps/docs/features/docs/Reference.navigation.tsx index 51e594b2874..5e9b27471e3 100644 --- a/apps/docs/features/docs/Reference.navigation.tsx +++ b/apps/docs/features/docs/Reference.navigation.tsx @@ -1,16 +1,14 @@ -import { isFeatureEnabled } from 'common' -import { type PropsWithChildren } from 'react' - -import { cn } from 'ui' - import MenuIconPicker from '~/components/Navigation/NavigationMenu/MenuIconPicker' import RefVersionDropdown from '~/components/RefVersionDropdown' import { getReferenceSections } from '~/features/docs/Reference.generated.singleton' import { - RefLink, ReferenceNavigationScrollHandler, + RefLink, } from '~/features/docs/Reference.navigation.client' import { type AbbrevApiReferenceSection } from '~/features/docs/Reference.utils' +import { isFeatureEnabled } from 'common' +import { type PropsWithChildren } from 'react' +import { cn } from 'ui' interface ReferenceNavigationProps { libraryId: string @@ -19,6 +17,7 @@ interface ReferenceNavigationProps { libPath: string version: string isLatestVersion: boolean + realNavigation?: boolean } export async function ReferenceNavigation({ @@ -28,6 +27,7 @@ export async function ReferenceNavigation({ libPath, version, isLatestVersion, + realNavigation, }: ReferenceNavigationProps) { const navSections = await getReferenceSections(libraryId, version) const filteredNavSections = navSections?.filter((section) => section.title !== 'Auth') @@ -46,11 +46,11 @@ export async function ReferenceNavigation({ {displayedNavSections?.map((section, index) => section.type === 'category' ? (
  • - +
  • ) : (
  • - +
  • ) )} @@ -64,9 +64,11 @@ const topLvlRefNavItemStyles = 'leading-5' function RefCategory({ basePath, section, + realNavigation, }: { basePath: string section: AbbrevApiReferenceSection + realNavigation?: boolean }) { if (!('items' in section && section.items && section.items.length > 0)) return null @@ -77,7 +79,7 @@ function RefCategory({
      {section.items?.map((item) => (
    • - +
    • ))}
    diff --git a/apps/docs/features/docs/Reference.sections.tsx b/apps/docs/features/docs/Reference.sections.tsx index cbced0f844c..226ff263ec2 100644 --- a/apps/docs/features/docs/Reference.sections.tsx +++ b/apps/docs/features/docs/Reference.sections.tsx @@ -5,6 +5,7 @@ import { getCliSpec, getFlattenedSections, getFunctionsList, + getSectionsBySlug, getSelfHostedApiEndpointById, getTypeSpec, } from '~/features/docs/Reference.generated.singleton' @@ -24,6 +25,7 @@ import type { AbbrevApiReferenceSection } from '~/features/docs/Reference.utils' import { normalizeMarkdown } from '~/features/docs/Reference.utils' import { CodeBlock } from '~/features/ui/CodeBlock/CodeBlock' import { isFeatureEnabled } from 'common' +import { notFound } from 'next/navigation' import { Fragment } from 'react' import ReactMarkdown from 'react-markdown' import { Badge, cn, Tabs, TabsContent, TabsList, TabsTrigger } from 'ui' @@ -64,6 +66,24 @@ async function RefSections({ libraryId, version }: RefSectionsProps) { ) } +type RefSectionProps = { + libraryId: string + version: string + slug: string +} + +async function RefSection({ libraryId, version, slug }: RefSectionProps) { + const sectionsBySlug = await getSectionsBySlug(libraryId, version) + const section = sectionsBySlug?.get(slug) + if (!section) notFound() + + return ( +
    + +
    + ) +} + function trimIntro(sections: Array) { const hasIntro = sections[0]?.type === 'markdown' && sections[0]?.slug === 'introduction' if (hasIntro) { @@ -573,4 +593,4 @@ async function FunctionSection({ ) } -export { RefSections } +export { RefSections, RefSection } diff --git a/apps/docs/features/docs/Reference.utils.ts b/apps/docs/features/docs/Reference.utils.ts index 5824f27901a..f3680553bbc 100644 --- a/apps/docs/features/docs/Reference.utils.ts +++ b/apps/docs/features/docs/Reference.utils.ts @@ -1,5 +1,8 @@ import { clientSdkIds, REFERENCES, selfHostingServices } from '~/content/navigation.references' -import { getFlattenedSections } from '~/features/docs/Reference.generated.singleton' +import { + getFlattenedSections, + getSectionsBySlug, +} from '~/features/docs/Reference.generated.singleton' import { generateOpenGraphImageMeta } from '~/features/seo/openGraph' import { BASE_PATH } from '~/lib/constants' import { getCustomContent } from '~/lib/custom-content/getCustomContent' @@ -113,11 +116,11 @@ export async function generateReferenceStaticParams() { }, ] - const apiPages = [ - { - slug: ['api'], - }, - ] + const apiFlattenedSections = await getFlattenedSections('api', 'latest') + const apiPages = (apiFlattenedSections ?? []) + .filter((section) => section.type !== 'category' && !!section.slug) + .map((section) => ({ slug: ['api', section.slug] })) + .concat([{ slug: ['api'] }]) const selfHostingPages = selfHostingServices.map((service) => ({ slug: [REFERENCES[service].libPath], @@ -178,9 +181,19 @@ export async function generateReferenceMetadata( description: 'CLI reference for the Supabase CLI', } } else if (isApiReference) { + const { path } = parsedPath + const sectionSlug = path[0] !== 'introduction' ? path[0] : undefined + + const sectionsBySlug = await getSectionsBySlug('api', 'latest') + const sectionTitle = sectionSlug ? sectionsBySlug?.get(sectionSlug)?.title : undefined + const url = [BASE_PATH, 'reference', 'api', sectionSlug].filter(Boolean).join('/') + return { - title: 'Management API Reference | Supabase Docs', + title: `Management API Reference${sectionTitle ? `: ${sectionTitle}` : ''} | Supabase Docs`, description: 'Management API reference for the Supabase API', + alternates: { + canonical: url, + }, } } else if (isSelfHostingReference) { return { diff --git a/apps/docs/internals/files/reference-lib.ts b/apps/docs/internals/files/reference-lib.ts index f1dbafbf5cc..c624d66c04b 100644 --- a/apps/docs/internals/files/reference-lib.ts +++ b/apps/docs/internals/files/reference-lib.ts @@ -29,12 +29,14 @@ export async function generateReferencePages() { .map(async ({ sdkId, version, libPath, isLatestVersion }) => { const flattenedSections = await getFlattenedSections(sdkId, version) return ( - flattenedSections?.map((section) => ({ - link: isLatestVersion - ? `reference/${libPath}/${section.slug}` - : `reference/${libPath}/${version}/${section.slug}`, - priority: 0.8, - })) ?? [] + flattenedSections + ?.filter((section) => !!section.slug) + .map((section) => ({ + link: isLatestVersion + ? `reference/${libPath}/${section.slug}` + : `reference/${libPath}/${version}/${section.slug}`, + priority: 0.8, + })) ?? [] ) }) ) diff --git a/apps/docs/middleware.test.ts b/apps/docs/middleware.test.ts index 87d171621d9..509579e088a 100644 --- a/apps/docs/middleware.test.ts +++ b/apps/docs/middleware.test.ts @@ -163,3 +163,34 @@ describe('docs middleware — /guides/* content negotiation', () => { expect(rewrite).not.toContain('/api/guides-md/') }) }) + +describe('docs middleware — /reference/api/* routing', () => { + // Regression test for INC-703: /reference/api/* used to be collapsed to a + // single /reference/api page for every endpoint, which grew unboundedly + // with the OpenAPI spec until it exceeded Vercel's ISR page-size cap. Each + // endpoint now gets its own statically-generated page, so these paths must + // pass through untouched. + it('does not rewrite /reference/api/ to the bare index', () => { + const req = makeRequest('/docs/reference/api/v1-list-all-projects') + expect(middleware(req).headers.get(REWRITE_HEADER)).toBeNull() + }) + + it('does not rewrite the bare /reference/api index', () => { + const req = makeRequest('/docs/reference/api') + expect(middleware(req).headers.get(REWRITE_HEADER)).toBeNull() + }) + + it('still rewrites /reference/cli/* to the single CLI page', () => { + const req = makeRequest('/docs/reference/cli/some-command') + expect(middleware(req).headers.get(REWRITE_HEADER)).toBe( + 'https://supabase.com/docs/reference/cli' + ) + }) + + it('still rewrites /reference/self-hosting-/* to its single service page', () => { + const req = makeRequest('/docs/reference/self-hosting-auth/some-endpoint') + expect(middleware(req).headers.get(REWRITE_HEADER)).toBe( + 'https://supabase.com/docs/reference/self-hosting-auth' + ) + }) +}) diff --git a/apps/docs/middleware.ts b/apps/docs/middleware.ts index fd885725c23..b9bbd611888 100644 --- a/apps/docs/middleware.ts +++ b/apps/docs/middleware.ts @@ -63,11 +63,6 @@ export function middleware(request: NextRequest) { return NextResponse.rewrite(new URL(rewritePath, request.url)) } - if (lib === 'api') { - const rewritePath = [REFERENCE_PATH, 'api'].join('/') - return NextResponse.rewrite(new URL(rewritePath, request.url)) - } - if (lib?.startsWith('self-hosting-')) { const rewritePath = [REFERENCE_PATH, lib].join('/') return NextResponse.rewrite(new URL(rewritePath, request.url))