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))