refactor: split guide layouts by section (#32694)

Split guide layouts by section, so that nav data fetches can be pushed down to thesection where they're actually used. Limits blast radius of any data fetch problems, and also lets us push down client components in the future, since layouts now have context on the request path and we don't need a high-level usePathname.
This commit is contained in:
Charis authored and GitHub committed 2025-01-30 11:04:43 -05:00
1 parent 8a3d147afc
commit 44db2d3f16
60 files changed
+585 -755

No files matched your search

@@ -1,21 +0,0 @@
import {
getGuidesMarkdown,
genGuideMeta,
genGuidesStaticParams,
} from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
export const dynamicParams = false
type Params = { slug: string[] }
const GuidePage = async ({ params }: { params: Params }) => {
const data = await getGuidesMarkdown(params)
return <GuideTemplate {...data!} />
}
const generateStaticParams = genGuidesStaticParams()
const generateMetadata = genGuideMeta(getGuidesMarkdown)
export default GuidePage
export { generateStaticParams, generateMetadata }
@@ -1,61 +0,0 @@
import { cache, type PropsWithChildren } from 'react'
import { IS_PLATFORM } from 'common'
import { NavMenuSection } from '~/components/Navigation/Navigation.types'
import Layout from '~/layouts/guides'
import { supabaseMisc } from '~/lib/supabaseMisc'
import { getAiPrompts } from './getting-started/ai-prompts/[slug]/AiPrompts.utils'
// Revalidate occasionally to pick up changes to partners
// 60 seconds/minute * 60 minutes/hour * 24 hours/day
// export const revalidate = 86_400
const GuidesLayout = async ({ children }: PropsWithChildren) => {
const [partnerNavItems, promptNavItems] = await Promise.all([getPartners(), getPrompts()])
const additionalNavItems =
partnerNavItems.length > 0 || promptNavItems.length > 0
? { integrations: partnerNavItems, prompts: promptNavItems }
: undefined
return <Layout additionalNavItems={additionalNavItems}>{children}</Layout>
}
async function getPrompts() {
const prompts = await getAiPrompts()
return prompts.map(
(prompt) =>
({
name: prompt.heading,
url: `/guides/getting-started/ai-prompts/${prompt.filename}`,
}) as Partial<NavMenuSection>
)
}
const getPartners = cache(getPartnersImpl)
async function getPartnersImpl() {
if (!IS_PLATFORM) return []
const { data, error } = await supabaseMisc()
.from('partners')
.select('slug, title')
.eq('approved', true)
.eq('type', 'technology')
.order('title')
if (error) {
console.error(new Error('Error fetching partners', { cause: error }))
}
const partnerNavItems = (data ?? []).map(
(partner) =>
({
name: partner.title,
url: `https://supabase.com/partners/integrations/${partner.slug}` as `https://${string}`,
}) as Partial<NavMenuSection>
)
return partnerNavItems
}
export default GuidesLayout
@@ -1,106 +0,0 @@
import ReactMarkdown from 'react-markdown'
import { CodeBlock } from 'ui'
import { Heading } from 'ui/src/components/CustomHTMLElements'
import { type TOCHeader } from '~/components/GuidesTableOfContents'
import { genGuideMeta } from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
import type { Parameter } from '~/lib/refGenerator/refTypes'
import specFile from '~/spec/cli_v1_config.yaml' with { type: 'yml' }
const meta = {
title: 'Supabase CLI config',
}
const generateMetadata = genGuideMeta(() => ({
pathname: '/guides/cli/config',
meta,
}))
const tocList: TOCHeader[] = []
const content = specFile.info.tags.map((tag: { id: string; title: string }, id: number) => {
tocList.push({ id: `${id}`, text: tag.title, link: `${tag.id}-config`, level: 2 })
return (
<div>
<Heading tag="h2">{tag.title} Config</Heading>
{specFile.parameters
.filter((param: Parameter) => param.tags && param.tags[0] === tag.id)
.map((parameter: Parameter, id: number) => {
tocList.push({ id: `${id}`, text: parameter.id, link: `#${parameter.id}`, level: 3 })
return <Info parameter={parameter} />
})}
</div>
)
})
const Config = () => {
const editLink = newEditLink('supabase/supabase/blob/master/apps/docs/spec/cli_v1_config.yaml')
return (
<GuideTemplate meta={meta} editLink={editLink}>
<ReactMarkdown>{specFile.info.description}</ReactMarkdown>
<div>{content}</div>
</GuideTemplate>
)
}
function Info({ parameter }: { parameter: Parameter }) {
return (
<div className="mt-8">
<div>
<Heading tag="h3" parseAnchors={false} customAnchor={parameter.id}>
<code>{parameter.title}</code>
</Heading>
<div className="border-b pb-8" key={parameter.id}>
<div className=" mb-16">
<div>
<table className="table-auto">
<thead>
<tr>
<th className="text-left">Name</th>
<th className="text-left">Default</th>
<th className="text-left">Required</th>
</tr>
</thead>
<tbody>
<tr>
<td>{parameter.id}</td>
<td>{parameter.default ? parameter.default.toString() : 'None'}</td>
<td>{parameter.required !== undefined && parameter.required.toString()}</td>
</tr>
</tbody>
</table>
</div>
<div className="mb-4 scroll-mt-16 mt-0 ">
<p className="text-sm font-bold uppercase">Description</p>
<ReactMarkdown>{parameter.description}</ReactMarkdown>
</div>
{parameter.usage && (
<div className="mb-4 scroll-mt-16 mt-0 ">
<p className="text-sm font-bold uppercase">Usage</p>
<CodeBlock className="useless-code-block-class" language="py">
{parameter.usage}
</CodeBlock>
</div>
)}
{parameter.links && (
<div>
<p className="text-sm font-bold uppercase">See also</p>
<ul>
{parameter.links.map((link) => (
<li key={link.link}>
<a href={link.link}>{link.name}</a>
</li>
))}
</ul>
</div>
)}
</div>
</div>
</div>
</div>
)
}
export default Config
export { generateMetadata }
@@ -1,146 +0,0 @@
import matter from 'gray-matter'
import { type SerializeOptions } from 'next-mdx-remote/dist/types'
import { notFound } from 'next/navigation'
import rehypeSlug from 'rehype-slug'
import { genGuideMeta, removeRedundantH1 } from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
import { fetchRevalidatePerDay } from '~/features/helpers.fetch'
import { isValidGuideFrontmatter } from '~/lib/docs'
import { UrlTransformFunction, linkTransform } 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 {
terraformDocsBranch,
terraformDocsDocsDir,
terraformDocsOrg,
terraformDocsRepo,
} from '../terraformConstants'
// Each external docs page is mapped to a local page
const pageMap = [
{
remoteFile: 'README.md',
meta: {
title: 'Terraform Provider',
},
useRoot: true,
},
{
slug: 'tutorial',
remoteFile: 'tutorial.md',
meta: {
title: 'Using the Supabase Terraform Provider',
},
},
]
interface Params {
slug?: string[]
}
const TerraformDocs = async ({ params }: { params: Params }) => {
const { meta, ...data } = await getContent(params)
const options = {
mdxOptions: {
remarkPlugins: [remarkMkDocsAdmonition, remarkPyMdownTabs, [removeTitle, meta.title]],
rehypePlugins: [[linkTransform, urlTransform], rehypeSlug],
},
} as SerializeOptions
return <GuideTemplate mdxOptions={options} meta={meta} {...data} />
}
/**
* The GitHub repo uses relative links, which don't lead to the right locations
* in docs.
*
* @param url The original link, as written in the Markdown file
* @returns The rewritten link
*/
const urlTransform: UrlTransformFunction = (url: string) => {
try {
const placeholderHostname = 'placeholder'
const { hostname, pathname, hash } = new URL(url, `http://${placeholderHostname}`)
// Don't modify a url with a FQDN or a url that's only a hash
if (hostname !== placeholderHostname || pathname === '/') {
return url
}
const getBasename = (pathname: string) =>
pathname.endsWith('.md') ? pathname.replace(/\.md$/, '') : pathname
const stripLeadingPrefix = (pathname: string) => pathname.replace(/^\//, '')
const stripLeadingDocs = (pathname: string) => pathname.replace(/^docs\//, '')
const relativePath = stripLeadingPrefix(getBasename(pathname))
const page = pageMap.find(
({ remoteFile, useRoot }) =>
(useRoot && `${relativePath}.md` === remoteFile) ||
(!useRoot && `${stripLeadingDocs(relativePath)}.md` === remoteFile)
)
if (page) {
return 'terraform' + `/${page.slug}` + hash
}
// If we don't have this page in our docs, link to GitHub repo
return `https://github.com/${terraformDocsOrg}/${terraformDocsRepo}/blob/${terraformDocsBranch}${pathname}${hash}`
} catch (err) {
console.error('Error transforming markdown URL', err)
return url
}
}
/**
* Fetch markdown from external repo
*/
const getContent = async ({ slug }: Params) => {
const [requestedSlug] = slug ?? []
const page = pageMap.find((page) => page.slug === requestedSlug)
if (!page) {
notFound()
}
const { meta, remoteFile, useRoot } = page
const editLink = newEditLink(
`${terraformDocsOrg}/${terraformDocsRepo}/blob/${terraformDocsBranch}/${useRoot ? '' : `${terraformDocsDocsDir}/`}${remoteFile}`
)
let response = await fetchRevalidatePerDay(
`https://raw.githubusercontent.com/${terraformDocsOrg}/${terraformDocsRepo}/${terraformDocsBranch}/${useRoot ? '' : `${terraformDocsDocsDir}/`}${remoteFile}`
)
let rawContent = await response.text()
// Strip out HTML comments
rawContent = rawContent.replace(/<!--.*?-->/, '')
let { content, data } = matter(rawContent)
// Remove the title from the content so it isn't duplicated in the final display
content = removeRedundantH1(content)
Object.assign(meta, data)
if (!isValidGuideFrontmatter(meta)) {
throw Error('Guide frontmatter is invalid.')
}
return {
pathname:
`/guides/platform/terraform${slug?.length ? `/${slug.join('/')}` : ''}` satisfies `/${string}`,
meta,
content,
editLink,
}
}
const generateStaticParams = async () => pageMap.map(({ slug }) => ({ slug: slug ? [slug] : [] }))
const generateMetadata = genGuideMeta(getContent)
export default TerraformDocs
export { generateStaticParams, generateMetadata }
@@ -1,410 +0,0 @@
import { codeBlock } from 'common-tags'
import { Check, PlusCircle } from 'lucide-react'
import Link from 'next/link'
import ReactMarkdown from 'react-markdown'
import {
CodeBlock,
Heading,
PopoverContent_Shadcn_,
PopoverTrigger_Shadcn_,
Popover_Shadcn_,
} from 'ui'
import { genGuideMeta } from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
import { fetchRevalidatePerDay } from '~/features/helpers.fetch'
import { TabPanel, Tabs } from '~/features/ui/Tabs'
import {
terraformDocsBranch,
terraformDocsDocsDir,
terraformDocsOrg,
terraformDocsRepo,
} from '../terraformConstants'
const meta = {
title: 'Terraform Provider reference',
subtitle: 'Resources and data sources available through the Terraform Provider',
}
const generateMetadata = genGuideMeta(() => ({
pathname: '/guides/platform/terraform/reference',
meta,
}))
function ProviderSettings({ schema }: { schema: any }) {
const attributes = schema.block.attributes
const example = codeBlock`
provider "supabase" {
${Object.keys(attributes).map(
(attribute) =>
`${attribute} = ${attributes[attribute].type === 'string' ? `""` : '<value>'}`
)}
}
`
return (
<section aria-labelledby="provider-settings" className="prose max-w-none">
<Heading tag="h2">Provider settings</Heading>
<p>
Use these settings to configure your Supabase provider and authenticate to your Supabase
project.
</p>
<Heading tag="h3">Example usage</Heading>
<CodeBlock className="not-prose">{example}</CodeBlock>
<Heading tag="h3">Details</Heading>
{/* extra div because width restriction doesn't work on table itself */}
<div className="w-full overflow-auto">
<table>
<thead>
<tr>
<th>Attribute</th>
<th>Description</th>
<th>Type</th>
<th>Optional</th>
<th>Sensitive</th>
</tr>
</thead>
<tbody>
{Object.keys(schema.block.attributes).map((attribute) => (
<tr key={attribute}>
<td>{attribute}</td>
<td>
<ReactMarkdown>{attributes[attribute].description}</ReactMarkdown>
</td>
<td>{attributes[attribute].type}</td>
<td className="align-middle">
{attributes[attribute].optional && (
<>
<Check className="ml-[2.5ch]" />
<span className="sr-only">true</span>
</>
)}
</td>
<td className="align-middle">
{attributes[attribute].sensitive && (
<>
<Check className="ml-[2.5ch]" />
<span className="sr-only">true</span>
</>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
)
}
function Resources({ schema }: { schema: any }) {
return (
<section aria-labelledby="resources" className="prose max-w-none">
<Heading tag="h2">Resources</Heading>
<p>You can configure these resources using the Supabase Terraform provider:</p>
<Tabs>
{Object.keys(schema).map((resource) => (
<TabPanel id={resource} label={resource}>
<Heading tag="h4">Example usage</Heading>
<CodeBlock className="not-prose">{codeBlock`
resource "${resource}" "<label>" {
${Object.keys(schema[resource].block.attributes)
.filter((attribute) => !schema[resource].block.attributes[attribute].computed)
.map(
(attribute) =>
`${attribute} = ${
schema[resource].block.attributes[attribute].type === 'string'
? `""`
: '<value>'
}`
)}
}
`}</CodeBlock>
<Heading tag="h4">Details</Heading>
<table>
<thead>
<tr>
<th>Attribute</th>
<th>Description</th>
<th>Type</th>
<th>Required</th>
<th>Optional</th>
<th>Read-only</th>
</tr>
</thead>
<tbody>
{Object.keys(schema[resource].block.attributes).map((attribute) => (
<tr key={attribute}>
<td>
<code>{attribute}</code>
</td>
<td>
<ReactMarkdown>
{schema[resource].block.attributes[attribute].description}
</ReactMarkdown>
</td>
<td>
{schema[resource].block.attributes[attribute].type ?? (
<Popover_Shadcn_>
<PopoverTrigger_Shadcn_ asChild>
<button className="flex items-center justify-between gap-2">
Nested type
<PlusCircle size={14} className="shrink-0" />
</button>
</PopoverTrigger_Shadcn_>
<PopoverContent_Shadcn_ className="max-h-[50vh] overflow-auto">
<ul>
{Object.keys(
schema[resource].block.attributes[attribute].nested_type.attributes
).map((nestedAttribute) => (
<li key={nestedAttribute}>
{nestedAttribute}
<ul>
<li>
<ReactMarkdown className="*:!m-0">
{
schema[resource].block.attributes[attribute].nested_type
.attributes[nestedAttribute].description
}
</ReactMarkdown>
</li>
<li>
{schema[resource].block.attributes[attribute].nested_type
.attributes[nestedAttribute].type ?? 'nested type'}
</li>
{schema[resource].block.attributes[attribute].nested_type
.attributes[nestedAttribute].required && <li>Required</li>}
{schema[resource].block.attributes[attribute].nested_type
.attributes[nestedAttribute].optional && <li>Optional</li>}
{schema[resource].block.attributes[attribute].nested_type
.attributes[nestedAttribute].computed && <li>Read-only</li>}
</ul>
</li>
))}
</ul>
</PopoverContent_Shadcn_>
</Popover_Shadcn_>
)}
</td>
<td className="align-middle">
{schema[resource].block.attributes[attribute].required && (
<>
<Check className="ml-[2.5ch]" />
<span className="sr-only">true</span>
</>
)}
</td>
<td className="align-middle">
{schema[resource].block.attributes[attribute].optional && (
<>
<Check className="ml-[2.5ch]" />
<span className="sr-only">true</span>
</>
)}
</td>
<td className="align-middle">
{schema[resource].block.attributes[attribute].computed && (
<>
<Check className="ml-[2.5ch]" />
<span className="sr-only">true</span>
</>
)}
</td>
</tr>
))}
</tbody>
</table>
</TabPanel>
))}
</Tabs>
</section>
)
}
function DataSources({ schema }: { schema: any }) {
return (
<section aria-labelledby="data-sources" className="prose max-w-none">
<Heading tag="h2">Data sources</Heading>
<p>You can read these resources using the Supabase Terraform provider:</p>
<Tabs>
{Object.keys(schema).map((dataSource) => (
<TabPanel id={dataSource} label={dataSource}>
<Heading tag="h4">Example usage</Heading>
<CodeBlock className="not-prose">{codeBlock`
resource "${dataSource}" "all" {
${Object.keys(schema[dataSource].block.attributes)
.filter(
(attribute) => !schema[dataSource].block.attributes[attribute].computed
)
.map(
(attribute) =>
`${attribute} = ${
schema[dataSource].block.attributes[attribute].type === 'string'
? `""`
: '<value>'
}`
)}
}
`}</CodeBlock>
<Heading tag="h4">Details</Heading>
<table>
<thead>
<tr>
<th>Attribute</th>
<th>Description</th>
<th>Type</th>
<th>Required</th>
<th>Optional</th>
<th>Read-only</th>
</tr>
</thead>
<tbody>
{Object.keys(schema[dataSource].block.attributes).map((attribute) => (
<tr key={attribute}>
<td>
<code>{attribute}</code>
</td>
<td>
<ReactMarkdown>
{schema[dataSource].block.attributes[attribute].description}
</ReactMarkdown>
</td>
<td>
{schema[dataSource].block.attributes[attribute].type ?? (
<Popover_Shadcn_>
<PopoverTrigger_Shadcn_ asChild>
<button className="flex items-center justify-between gap-2">
Nested type
<PlusCircle size={14} />
</button>
</PopoverTrigger_Shadcn_>
<PopoverContent_Shadcn_ className="max-h-[50vh] overflow-auto">
{schema[dataSource].block.attributes[attribute].nested_type
.nesting_mode === 'set' && 'Array of:'}
<ul>
{Object.keys(
schema[dataSource].block.attributes[attribute].nested_type
.attributes
).map((nestedAttribute) => (
<li key={nestedAttribute}>
{nestedAttribute}
<ul>
<li>
<ReactMarkdown className="*:!m-0">
{
schema[dataSource].block.attributes[attribute].nested_type
.attributes[nestedAttribute].description
}
</ReactMarkdown>
</li>
<li>
{schema[dataSource].block.attributes[attribute].nested_type
.attributes[nestedAttribute].type ?? 'nested type'}
</li>
{schema[dataSource].block.attributes[attribute].nested_type
.attributes[nestedAttribute].required && <li>Required</li>}
{schema[dataSource].block.attributes[attribute].nested_type
.attributes[nestedAttribute].optional && <li>Optional</li>}
{schema[dataSource].block.attributes[attribute].nested_type
.attributes[nestedAttribute].computed && <li>Read-only</li>}
</ul>
</li>
))}
</ul>
</PopoverContent_Shadcn_>
</Popover_Shadcn_>
)}
</td>
<td className="align-middle">
{schema[dataSource].block.attributes[attribute].required && (
<>
<Check className="ml-[2.5ch]" />
<span className="sr-only">true</span>
</>
)}
</td>
<td className="align-middle">
{schema[dataSource].block.attributes[attribute].optional && (
<>
<Check className="ml-[2.5ch]" />
<span className="sr-only">true</span>
</>
)}
</td>
<td className="align-middle">
{schema[dataSource].block.attributes[attribute].computed && (
<>
<Check className="ml-[2.5ch]" />
<span className="sr-only">true</span>
</>
)}
</td>
</tr>
))}
</tbody>
</table>
</TabPanel>
))}
</Tabs>
</section>
)
}
const TerraformReferencePage = async () => {
const { schema } = await getSchema()
const editLink = newEditLink('supabase/terraform-provider-supabase')
return (
<GuideTemplate meta={meta} editLink={editLink}>
The Terraform Provider provices access to{' '}
<Link
href="https://developer.hashicorp.com/terraform/language/resources"
rel="noopener noreferrer"
>
resources
</Link>{' '}
and{' '}
<Link
href="https://developer.hashicorp.com/terraform/language/data-sources"
rel="noreferrer noopener"
>
data sources
</Link>
. Resources are infrastructure objects, such as a Supabase project, that you can declaratively
configure. Data sources are sources of information about your Supabase instances.
<ProviderSettings
schema={schema.provider_schemas['registry.terraform.io/supabase/supabase'].provider}
/>
<Resources
schema={schema.provider_schemas['registry.terraform.io/supabase/supabase'].resource_schemas}
/>
<DataSources
schema={
schema.provider_schemas['registry.terraform.io/supabase/supabase'].data_source_schemas
}
/>
</GuideTemplate>
)
}
/**
* Fetch JSON schema from external repo
*/
const getSchema = async () => {
let response = await fetchRevalidatePerDay(
`https://raw.githubusercontent.com/${terraformDocsOrg}/${terraformDocsRepo}/${terraformDocsBranch}/${terraformDocsDocsDir}/schema.json`
)
if (!response.ok) throw Error('Failed to fetch Terraform JSON schema from GitHub')
const schema = await response.json()
return {
schema,
}
}
export default TerraformReferencePage
export { generateMetadata }
@@ -1,9 +0,0 @@
/**
* Information on where to fetch docs content
*/
const terraformDocsOrg = 'supabase'
const terraformDocsRepo = 'terraform-provider-supabase'
const terraformDocsBranch = 'v1.1.3'
const terraformDocsDocsDir = 'docs'
export { terraformDocsOrg, terraformDocsRepo, terraformDocsBranch, terraformDocsDocsDir }
@@ -0,0 +1,25 @@
import {
getGuidesMarkdown,
genGuideMeta,
genGuidesStaticParams,
} from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
export const dynamicParams = false
type Params = { slug?: string[] }
const AiGuidePage = async ({ params }: { params: Params }) => {
const slug = ['ai', ...(params.slug ?? [])]
const data = await getGuidesMarkdown(slug)
return <GuideTemplate {...data!} />
}
const generateStaticParams = genGuidesStaticParams('ai')
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
getGuidesMarkdown(['ai', ...(params.slug ?? [])])
)
export default AiGuidePage
export { generateStaticParams, generateMetadata }
+5
View File
@@ -0,0 +1,5 @@
import Layout from '~/layouts/guides'
export default async function AiLayout({ children }: { children: React.ReactNode }) {
return <Layout>{children}</Layout>
}
@@ -10,6 +10,8 @@ import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLin
import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition'
import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle'
export const dynamicParams = false
// We fetch these docs at build time from an external repo
const org = 'supabase'
const repo = 'vecs'
@@ -0,0 +1,25 @@
import {
getGuidesMarkdown,
genGuideMeta,
genGuidesStaticParams,
} from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
export const dynamicParams = false
type Params = { slug?: string[] }
const ApiGuidePage = async ({ params }: { params: Params }) => {
const slug = ['api', ...(params.slug ?? [])]
const data = await getGuidesMarkdown(slug)
return <GuideTemplate {...data!} />
}
const generateStaticParams = genGuidesStaticParams('api')
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
getGuidesMarkdown(['api', ...(params.slug ?? [])])
)
export default ApiGuidePage
export { generateStaticParams, generateMetadata }
+5
View File
@@ -0,0 +1,5 @@
import Layout from '~/layouts/guides'
export default async function ApiLayout({ children }: { children: React.ReactNode }) {
return <Layout>{children}</Layout>
}
@@ -0,0 +1,25 @@
import {
getGuidesMarkdown,
genGuideMeta,
genGuidesStaticParams,
} from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
export const dynamicParams = false
type Params = { slug?: string[] }
const AuthGuidePage = async ({ params }: { params: Params }) => {
const slug = ['auth', ...(params.slug ?? [])]
const data = await getGuidesMarkdown(slug)
return <GuideTemplate {...data!} />
}
const generateStaticParams = genGuidesStaticParams('auth')
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
getGuidesMarkdown(['auth', ...(params.slug ?? [])])
)
export default AuthGuidePage
export { generateStaticParams, generateMetadata }
+5
View File
@@ -0,0 +1,5 @@
import Layout from '~/layouts/guides'
export default async function AuthLayout({ children }: { children: React.ReactNode }) {
return <Layout>{children}</Layout>
}
@@ -0,0 +1,25 @@
import {
getGuidesMarkdown,
genGuideMeta,
genGuidesStaticParams,
} from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
export const dynamicParams = false
type Params = { slug?: string[] }
const CronGuidePage = async ({ params }: { params: Params }) => {
const slug = ['cron', ...(params.slug ?? [])]
const data = await getGuidesMarkdown(slug)
return <GuideTemplate {...data!} />
}
const generateStaticParams = genGuidesStaticParams('cron')
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
getGuidesMarkdown(['cron', ...(params.slug ?? [])])
)
export default CronGuidePage
export { generateStaticParams, generateMetadata }
+5
View File
@@ -0,0 +1,5 @@
import Layout from '~/layouts/guides'
export default async function CronLayout({ children }: { children: React.ReactNode }) {
return <Layout>{children}</Layout>
}
@@ -0,0 +1,25 @@
import {
getGuidesMarkdown,
genGuideMeta,
genGuidesStaticParams,
} from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
export const dynamicParams = false
type Params = { slug?: string[] }
const DatabaseGuidePage = async ({ params }: { params: Params }) => {
const slug = ['database', ...(params.slug ?? [])]
const data = await getGuidesMarkdown(slug)
return <GuideTemplate {...data!} />
}
const generateStaticParams = genGuidesStaticParams('database')
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
getGuidesMarkdown(['database', ...(params.slug ?? [])])
)
export default DatabaseGuidePage
export { generateStaticParams, generateMetadata }
@@ -18,6 +18,8 @@ import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition'
import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle'
import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs'
export const dynamicParams = false
// We fetch these docs at build time from an external repo
const org = 'supabase'
const repo = 'wrappers'
+5
View File
@@ -0,0 +1,5 @@
import Layout from '~/layouts/guides'
export default async function DatabaseLayout({ children }: { children: React.ReactNode }) {
return <Layout>{children}</Layout>
}
@@ -0,0 +1,25 @@
import {
getGuidesMarkdown,
genGuideMeta,
genGuidesStaticParams,
} from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
export const dynamicParams = false
type Params = { slug?: string[] }
const DeploymentGuidePage = async ({ params }: { params: Params }) => {
const slug = ['deployment', ...(params.slug ?? [])]
const data = await getGuidesMarkdown(slug)
return <GuideTemplate {...data!} />
}
const generateStaticParams = genGuidesStaticParams('deployment')
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
getGuidesMarkdown(['deployment', ...(params.slug ?? [])])
)
export default DeploymentGuidePage
export { generateStaticParams, generateMetadata }
@@ -11,6 +11,8 @@ import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition'
import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle'
import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs'
export const dynamicParams = false
// We fetch these docs at build time from an external repo
const org = 'supabase'
const repo = 'setup-cli'
@@ -0,0 +1,5 @@
import Layout from '~/layouts/guides'
export default async function DeploymentLayout({ children }: { children: React.ReactNode }) {
return <Layout>{children}</Layout>
}
@@ -18,6 +18,8 @@ import {
terraformDocsRepo,
} from '../terraformConstants'
export const dynamicParams = false
// Each external docs page is mapped to a local page
const pageMap = [
{
@@ -0,0 +1,25 @@
import {
getGuidesMarkdown,
genGuideMeta,
genGuidesStaticParams,
} from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
export const dynamicParams = false
type Params = { slug?: string[] }
const FunctionsGuidePage = async ({ params }: { params: Params }) => {
const slug = ['functions', ...(params.slug ?? [])]
const data = await getGuidesMarkdown(slug)
return <GuideTemplate {...data!} />
}
const generateStaticParams = genGuidesStaticParams('functions')
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
getGuidesMarkdown(['functions', ...(params.slug ?? [])])
)
export default FunctionsGuidePage
export { generateStaticParams, generateMetadata }
@@ -0,0 +1,5 @@
import Layout from '~/layouts/guides'
export default async function FunctionsLayout({ children }: { children: React.ReactNode }) {
return <Layout>{children}</Layout>
}
@@ -0,0 +1,25 @@
import {
getGuidesMarkdown,
genGuideMeta,
genGuidesStaticParams,
} from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
export const dynamicParams = false
type Params = { slug?: string[] }
const GettingStartedGuidePage = async ({ params }: { params: Params }) => {
const slug = ['getting-started', ...(params.slug ?? [])]
const data = await getGuidesMarkdown(slug)
return <GuideTemplate {...data!} />
}
const generateStaticParams = genGuidesStaticParams('getting-started')
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
getGuidesMarkdown(['getting-started', ...(params.slug ?? [])])
)
export default GettingStartedGuidePage
export { generateStaticParams, generateMetadata }
@@ -0,0 +1,20 @@
import { type NavMenuSection } from '~/components/Navigation/Navigation.types'
import Layout from '~/layouts/guides'
import { getAiPrompts } from '../getting-started/ai-prompts/[slug]/AiPrompts.utils'
export default async function GettingStartedLayout({ children }: { children: React.ReactNode }) {
const additionalNavItems = { prompts: await getPrompts() }
return <Layout additionalNavItems={additionalNavItems}>{children}</Layout>
}
async function getPrompts() {
const prompts = await getAiPrompts()
return prompts.map(
(prompt) =>
({
name: prompt.heading,
url: `/guides/getting-started/ai-prompts/${prompt.filename}`,
}) as Partial<NavMenuSection>
)
}
@@ -11,6 +11,8 @@ import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition'
import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle'
import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs'
export const dynamicParams = false
// We fetch these docs at build time from an external repo
const org = 'supabase'
const repo = 'pg_graphql'
+5
View File
@@ -0,0 +1,5 @@
import Layout from '~/layouts/guides'
export default async function GraphQlLayout({ children }: { children: React.ReactNode }) {
return <Layout>{children}</Layout>
}
@@ -0,0 +1,25 @@
import {
getGuidesMarkdown,
genGuideMeta,
genGuidesStaticParams,
} from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
export const dynamicParams = false
type Params = { slug?: string[] }
const IntegrationsGuidePage = async ({ params }: { params: Params }) => {
const slug = ['integrations', ...(params.slug ?? [])]
const data = await getGuidesMarkdown(slug)
return <GuideTemplate {...data!} />
}
const generateStaticParams = genGuidesStaticParams('integrations')
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
getGuidesMarkdown(['integrations', ...(params.slug ?? [])])
)
export default IntegrationsGuidePage
export { generateStaticParams, generateMetadata }
@@ -0,0 +1,39 @@
import { IS_PLATFORM } from 'common'
import { unstable_cache } from 'next/cache'
import { type NavMenuSection } from '~/components/Navigation/Navigation.types'
import Layout from '~/layouts/guides'
import { supabaseMisc } from '~/lib/supabaseMisc'
export default async function IntegrationsLayout({ children }: { children: React.ReactNode }) {
const additionalNavItems = { integrations: await getPartners() }
return <Layout additionalNavItems={additionalNavItems}>{children}</Layout>
}
// Will need to turn on revalidation later, just turning it off for now so we
// can slowly turn things back on while monitoring usage
const getPartners = unstable_cache(getPartnersImpl, [], { revalidate: false })
async function getPartnersImpl() {
if (!IS_PLATFORM) return []
const { data, error } = await supabaseMisc()
.from('partners')
.select('slug, title')
.eq('approved', true)
.eq('type', 'technology')
.order('title')
if (error) {
console.error(new Error('Error fetching partners', { cause: error }))
}
const partnerNavItems = (data ?? []).map(
(partner) =>
({
name: partner.title,
url: `https://supabase.com/partners/integrations/${partner.slug}` as `https://${string}`,
}) as Partial<NavMenuSection>
)
return partnerNavItems
}
@@ -0,0 +1,25 @@
import {
getGuidesMarkdown,
genGuideMeta,
genGuidesStaticParams,
} from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
export const dynamicParams = false
type Params = { slug?: string[] }
const LocalDevelopmentGuidePage = async ({ params }: { params: Params }) => {
const slug = ['local-development', ...(params.slug ?? [])]
const data = await getGuidesMarkdown(slug)
return <GuideTemplate {...data!} />
}
const generateStaticParams = genGuidesStaticParams('local-development')
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
getGuidesMarkdown(['local-development', ...(params.slug ?? [])])
)
export default LocalDevelopmentGuidePage
export { generateStaticParams, generateMetadata }
@@ -0,0 +1,5 @@
import Layout from '~/layouts/guides'
export default async function LocalDevelopmentLayout({ children }: { children: React.ReactNode }) {
return <Layout>{children}</Layout>
}
@@ -0,0 +1,25 @@
import {
getGuidesMarkdown,
genGuideMeta,
genGuidesStaticParams,
} from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
export const dynamicParams = false
type Params = { slug?: string[] }
const MonitoringTroubleshootingGuidePage = async ({ params }: { params: Params }) => {
const slug = ['monitoring-troubleshooting', ...(params.slug ?? [])]
const data = await getGuidesMarkdown(slug)
return <GuideTemplate {...data!} />
}
const generateStaticParams = genGuidesStaticParams('monitoring-troubleshooting')
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
getGuidesMarkdown(['monitoring-troubleshooting', ...(params.slug ?? [])])
)
export default MonitoringTroubleshootingGuidePage
export { generateStaticParams, generateMetadata }
@@ -0,0 +1,9 @@
import Layout from '~/layouts/guides'
export default async function MonitoringTroubleshootingLayout({
children,
}: {
children: React.ReactNode
}) {
return <Layout>{children}</Layout>
}
@@ -0,0 +1,25 @@
import {
getGuidesMarkdown,
genGuideMeta,
genGuidesStaticParams,
} from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
export const dynamicParams = false
type Params = { slug?: string[] }
const PlatformGuidePage = async ({ params }: { params: Params }) => {
const slug = ['platform', ...(params.slug ?? [])]
const data = await getGuidesMarkdown(slug)
return <GuideTemplate {...data!} />
}
const generateStaticParams = genGuidesStaticParams('platform')
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
getGuidesMarkdown(['platform', ...(params.slug ?? [])])
)
export default PlatformGuidePage
export { generateStaticParams, generateMetadata }
+5
View File
@@ -0,0 +1,5 @@
import Layout from '~/layouts/guides'
export default async function PlatformLayout({ children }: { children: React.ReactNode }) {
return <Layout>{children}</Layout>
}
@@ -0,0 +1,25 @@
import {
getGuidesMarkdown,
genGuideMeta,
genGuidesStaticParams,
} from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
export const dynamicParams = false
type Params = { slug?: string[] }
const QueuesGuidePage = async ({ params }: { params: Params }) => {
const slug = ['queues', ...(params.slug ?? [])]
const data = await getGuidesMarkdown(slug)
return <GuideTemplate {...data!} />
}
const generateStaticParams = genGuidesStaticParams('queues')
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
getGuidesMarkdown(['queues', ...(params.slug ?? [])])
)
export default QueuesGuidePage
export { generateStaticParams, generateMetadata }
+5
View File
@@ -0,0 +1,5 @@
import Layout from '~/layouts/guides'
export default async function QueuesLayout({ children }: { children: React.ReactNode }) {
return <Layout>{children}</Layout>
}
@@ -0,0 +1,25 @@
import {
getGuidesMarkdown,
genGuideMeta,
genGuidesStaticParams,
} from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
export const dynamicParams = false
type Params = { slug?: string[] }
const RealtimeGuidePage = async ({ params }: { params: Params }) => {
const slug = ['realtime', ...(params.slug ?? [])]
const data = await getGuidesMarkdown(slug)
return <GuideTemplate {...data!} />
}
const generateStaticParams = genGuidesStaticParams('realtime')
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
getGuidesMarkdown(['realtime', ...(params.slug ?? [])])
)
export default RealtimeGuidePage
export { generateStaticParams, generateMetadata }
+5
View File
@@ -0,0 +1,5 @@
import Layout from '~/layouts/guides'
export default async function RealtimeLayout({ children }: { children: React.ReactNode }) {
return <Layout>{children}</Layout>
}
@@ -0,0 +1,25 @@
import {
getGuidesMarkdown,
genGuideMeta,
genGuidesStaticParams,
} from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
export const dynamicParams = false
type Params = { slug?: string[] }
const ResourcesGuidePage = async ({ params }: { params: Params }) => {
const slug = ['resources', ...(params.slug ?? [])]
const data = await getGuidesMarkdown(slug)
return <GuideTemplate {...data!} />
}
const generateStaticParams = genGuidesStaticParams('resources')
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
getGuidesMarkdown(['resources', ...(params.slug ?? [])])
)
export default ResourcesGuidePage
export { generateStaticParams, generateMetadata }
@@ -0,0 +1,5 @@
import Layout from '~/layouts/guides'
export default async function ResourcesLayout({ children }: { children: React.ReactNode }) {
return <Layout>{children}</Layout>
}
@@ -0,0 +1,25 @@
import {
getGuidesMarkdown,
genGuideMeta,
genGuidesStaticParams,
} from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
export const dynamicParams = false
type Params = { slug?: string[] }
const SelfHostingGuidePage = async ({ params }: { params: Params }) => {
const slug = ['self-hosting', ...(params.slug ?? [])]
const data = await getGuidesMarkdown(slug)
return <GuideTemplate {...data!} />
}
const generateStaticParams = genGuidesStaticParams('self-hosting')
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
getGuidesMarkdown(['self-hosting', ...(params.slug ?? [])])
)
export default SelfHostingGuidePage
export { generateStaticParams, generateMetadata }
@@ -0,0 +1,5 @@
import Layout from '~/layouts/guides'
export default async function SelfHostingLayout({ children }: { children: React.ReactNode }) {
return <Layout>{children}</Layout>
}
@@ -0,0 +1,25 @@
import {
getGuidesMarkdown,
genGuideMeta,
genGuidesStaticParams,
} from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
export const dynamicParams = false
type Params = { slug?: string[] }
const StorageGuidePage = async ({ params }: { params: Params }) => {
const slug = ['storage', ...(params.slug ?? [])]
const data = await getGuidesMarkdown(slug)
return <GuideTemplate {...data!} />
}
const generateStaticParams = genGuidesStaticParams('storage')
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
getGuidesMarkdown(['storage', ...(params.slug ?? [])])
)
export default StorageGuidePage
export { generateStaticParams, generateMetadata }
+5
View File
@@ -0,0 +1,5 @@
import Layout from '~/layouts/guides'
export default async function StorageLayout({ children }: { children: React.ReactNode }) {
return <Layout>{children}</Layout>
}
+5 -1
View File
@@ -42,7 +42,7 @@ const PUBLISHED_SECTIONS = [
'storage',
] as const
const getGuidesMarkdownInternal = async ({ slug }: { slug: string[] }) => {
const getGuidesMarkdownInternal = async (slug: string[]) => {
const relPath = slug.join(sep).replace(/\/$/, '')
const fullPath = join(GUIDES_DIRECTORY, relPath + '.mdx')
/**
@@ -60,6 +60,7 @@ const getGuidesMarkdownInternal = async ({ slug }: { slug: string[] }) => {
try {
mdx = await readFile(fullPath, 'utf-8')
} catch {
console.error('Error reading Markdown at path: %s', fullPath)
notFound()
}
@@ -96,6 +97,9 @@ const genGuidesStaticParams = (directory?: string) => async () => {
? (await readdir(join(GUIDES_DIRECTORY, directory), { recursive: true }))
.filter((file) => extname(file) === '.mdx' && !file.split(sep).at(-1).startsWith('_'))
.map((file) => ({ slug: file.replace(/\.mdx$/, '').split(sep) }))
.concat(
(await existsFile(join(GUIDES_DIRECTORY, `${directory}.mdx`))) ? [{ slug: [] }] : []
)
: PUBLISHED_SECTIONS.map(async (section) =>
(await readdir(join(GUIDES_DIRECTORY, section), { recursive: true }))
.filter((file) => extname(file) === '.mdx' && !file.split(sep).at(-1).startsWith('_'))
+1 -1
View File
@@ -7,7 +7,7 @@ import remarkGfm from 'remark-gfm'
import rehypeKatex from 'rehype-katex'
import remarkMath from 'remark-math'
import { AiPromptsIndex } from '~/app/guides/(with-sidebar)/getting-started/ai-prompts/[slug]/AiPromptsIndex'
import { AiPromptsIndex } from '~/app/guides/getting-started/ai-prompts/[slug]/AiPromptsIndex'
import { preprocessMdxWithDefaults } from '~/features/directives/utils'
import { components } from '~/features/docs/MdxBase.shared'
+1
View File
@@ -10,6 +10,7 @@ let octokitInstance: Octokit
function octokit() {
if (!octokitInstance) {
// https://github.com/gr2m/universal-github-app-jwt?tab=readme-ov-file#converting-pkcs1-to-pkcs8
const privateKeyPkcs8 = crypto
.createPrivateKey(process.env.DOCS_GITHUB_APP_PRIVATE_KEY)
.export({