-
Tutorials
+
Tutorials
{props.meta.title}
-
+
diff --git a/apps/docs/lib/mdx/plugins/remarkTabs.ts b/apps/docs/lib/mdx/plugins/remarkTabs.ts
new file mode 100644
index 00000000000..3562c2a6eca
--- /dev/null
+++ b/apps/docs/lib/mdx/plugins/remarkTabs.ts
@@ -0,0 +1,138 @@
+import { Content, Paragraph, Parent } from 'mdast'
+import { MdxJsxFlowElement } from 'mdast-util-mdx'
+import { Node } from 'unist'
+import { visit } from 'unist-util-visit'
+
+/**
+ * Transforms PyMdown Tabs to Supabase Tabs.
+ *
+ * https://facelessuser.github.io/pymdown-extensions/extensions/tabbed/
+ */
+const remarkPyMdownTabs = function () {
+ return function transformer(root: Parent) {
+ visit(root, 'paragraph', (node: Paragraph, nodeIndex: number, parent: Parent) => {
+ for (let i = 0; i < node.children.length; i++) {
+ const child = node.children[i]
+ if (child.type !== 'text') {
+ continue
+ }
+
+ // Look for 3 '=', followed by an optionally quoted title,
+ // followed by optional newlines of text
+ const match = child.value.match(/^\n*=== ("?)(.+)\1\n?((?:.|\n)*)/)
+
+ if (!match) {
+ continue
+ }
+
+ // Extract the tab title along with the remaining text
+ const [, , title, value] = match
+
+ // Rewrite the node's value to remove the tab syntax
+ child.value = value
+
+ // Extract sibling nodes that should be linked to this tab
+ const siblingsToNest = extractLinkedSiblings(parent, node, nodeIndex)
+
+ const children: any[] = [...node.children.slice(i), ...siblingsToNest]
+
+ const tabPanelElement: MdxJsxFlowElement = {
+ type: 'mdxJsxFlowElement',
+ name: 'TabPanel',
+ attributes: [
+ {
+ type: 'mdxJsxAttribute',
+ name: 'id',
+ value: title,
+ },
+ {
+ type: 'mdxJsxAttribute',
+ name: 'label',
+ value: title,
+ },
+ ],
+ children,
+ }
+
+ let nodesAdded = 0
+ const previousNode = parent.children[nodeIndex - 1]
+
+ if (previousNode?.type === 'mdxJsxFlowElement' && previousNode.name === 'Tabs') {
+ // Add TabPanel to existing Tabs component
+ previousNode.children.push(tabPanelElement)
+
+ // Remove this node
+ parent.children.splice(nodeIndex, 1)
+ nodesAdded--
+ } else {
+ // Create new Tabs components and add TabPanel
+ const tabsElement: MdxJsxFlowElement = {
+ type: 'mdxJsxFlowElement',
+ name: 'Tabs',
+ attributes: [
+ {
+ type: 'mdxJsxAttribute',
+ name: 'scrollable',
+ },
+ {
+ type: 'mdxJsxAttribute',
+ name: 'size',
+ value: 'small',
+ },
+ {
+ type: 'mdxJsxAttribute',
+ name: 'type',
+ value: 'underlined',
+ },
+ ],
+ children: [tabPanelElement],
+ }
+
+ // Overwrite this paragraph node with Tabs component
+ parent.children.splice(nodeIndex, 1, tabsElement)
+ }
+
+ // If this wasn't the first child of the paragraph, create
+ // a new paragraph before this with the previous text children
+ if (i > 0) {
+ const previousChildren = node.children.slice(0, i)
+ const paragraph: Paragraph = {
+ type: 'paragraph',
+ children: previousChildren,
+ }
+ parent.children.splice(nodeIndex, 0, paragraph)
+
+ nodesAdded++
+ }
+
+ // Return the correct index for the next visit, since
+ // we may have added or removed an element in the array
+ return nodeIndex + nodesAdded
+ }
+ })
+ }
+}
+
+/**
+ * Identifies sibling nodes that should be linked to this admonition
+ * based on their indent level (ie. 4 spaces).
+ *
+ * Iterates through proceeding siblings until one is found that is
+ * not indented relative to the original node.
+ *
+ * Splices the discovered siblings out of the original parent and returns them.
+ */
+function extractLinkedSiblings(parent: Parent, node: Node, index: number, indentAmount = 4) {
+ const { column } = node.position.start
+
+ let nextSibling: Content
+ let i = index
+
+ do {
+ nextSibling = parent.children[++i]
+ } while (nextSibling?.position && nextSibling.position.start.column === column + indentAmount)
+
+ return parent.children.splice(index + 1, i - index - 1)
+}
+
+export default remarkPyMdownTabs
diff --git a/apps/docs/package.json b/apps/docs/package.json
index 5d79d59dddd..e3710ea9ba6 100644
--- a/apps/docs/package.json
+++ b/apps/docs/package.json
@@ -97,6 +97,7 @@
"remark-admonitions": "^1.2.1",
"remark-gfm": "^3.0.1",
"remark-slug": "^7.0.1",
+ "remark-emoji": "^3.1.2",
"shiki": "^0.11.1",
"sse.js": "^0.6.1",
"ui": "*",
diff --git a/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx b/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx
index cbb1ae76649..0aaafb1f237 100644
--- a/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx
+++ b/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx
@@ -162,6 +162,8 @@ import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'
+export const dynamic = 'force-dynamic'
+
export async function GET(request) {
const requestUrl = new URL(request.url)
const code = requestUrl.searchParams.get('code')
@@ -190,6 +192,8 @@ import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import type { Database } from '@/lib/database.types'
+export const dynamic = 'force-dynamic'
+
export async function GET(request: NextRequest) {
const requestUrl = new URL(request.url)
const code = requestUrl.searchParams.get('code')
@@ -369,13 +373,9 @@ TypeScript types can be [generated with the Supabase CLI](/docs/reference/javasc
### Server-side
-The combination of [Server Components](https://nextjs.org/docs/getting-started/react-essentials#server-components) and [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions) can be used to trigger the authentication process from form submissions.
+The combination of [Server Components](https://nextjs.org/docs/getting-started/react-essentials#server-components) and [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers) can be used to trigger the authentication process from form submissions.
-
-
-Next.js [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions) are currently in Alpha and may change. For now we recommend [triggering the authentication flow client-side](/docs/guides/auth/auth-helpers/nextjs#client-side) for production applications.
-
-
+#### Sign Up Route
-```jsx app/login.js
-import { createServerActionClient } from '@supabase/auth-helpers-nextjs'
-import { revalidatePath } from 'next/cache'
+```jsx app/auth/sign-up.js
+import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
+import { NextResponse } from 'next/server'
-export default async function Login() {
- const handleSignUp = async (formData) => {
- 'use server'
- const email = formData.get('email')
- const password = formData.get('password')
+export const dynamic = 'force-dynamic'
- const supabase = createServerActionClient({ cookies })
- await supabase.auth.signUp({
- email,
- password,
- options: {
- emailRedirectTo: 'http://localhost:3000/auth/callback',
- },
- })
+export async function POST(request) {
+ const requestUrl = new URL(request.url)
+ const formData = await request.formData()
+ const email = formData.get('email')
+ const password = formData.get('password')
+ const supabase = createRouteHandlerClient({ cookies })
- revalidatePath('/')
- }
+ await supabase.auth.signUp({
+ email,
+ password,
+ options: {
+ emailRedirectTo: `${requestUrl.origin}/auth/callback`,
+ },
+ })
- const handleSignIn = async (formData) => {
- 'use server'
- const email = formData.get('email')
- const password = formData.get('password')
+ return NextResponse.redirect(requestUrl.origin, {
+ status: 301,
+ })
+}
+```
- const supabase = createServerActionClient({ cookies })
- await supabase.auth.signInWithPassword({
- email,
- password,
- })
+
- revalidatePath('/')
- }
+
- const handleSignOut = async () => {
- 'use server'
- const supabase = createServerActionClient({ cookies })
- await supabase.auth.signOut()
- revalidatePath('/')
- }
+```tsx app/auth/sign-up.ts
+import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
+import { cookies } from 'next/headers'
+import { NextResponse } from 'next/server'
+import type { Database } from '@/lib/database.types'
+
+export const dynamic = 'force-dynamic'
+
+export async function POST(request: Request) {
+ const requestUrl = new URL(request.url)
+ const formData = await request.formData()
+ const email = String(formData.get('email'))
+ const password = String(formData.get('password'))
+ const supabase = createRouteHandlerClient({ cookies })
+
+ await supabase.auth.signUp({
+ email,
+ password,
+ options: {
+ emailRedirectTo: `${requestUrl.origin}/auth/callback`,
+ },
+ })
+
+ return NextResponse.redirect(requestUrl.origin, {
+ status: 301,
+ })
+}
+```
+
+
+
+TypeScript types can be [generated with the Supabase CLI](/docs/reference/javascript/typescript-support) and passed to `createRouteHandlerClient` to add type support to the Supabase client.
+
+
+
+
+
+
+
+
+Returning a `301` status redirects from a POST to a GET route
+
+
+
+#### Login Route
+
+
+
+
+```jsx app/auth/login.js
+import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
+import { cookies } from 'next/headers'
+import { NextResponse } from 'next/server'
+
+export const dynamic = 'force-dynamic'
+
+export async function POST(request) {
+ const requestUrl = new URL(request.url)
+ const formData = await request.formData()
+ const email = formData.get('email')
+ const password = formData.get('password')
+ const supabase = createRouteHandlerClient({ cookies })
+
+ await supabase.auth.signInWithPassword({
+ email,
+ password,
+ })
+
+ return NextResponse.redirect(requestUrl.origin, {
+ status: 301,
+ })
+}
+```
+
+
+
+
+
+```tsx app/auth/login.ts
+import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
+import { cookies } from 'next/headers'
+import { NextResponse } from 'next/server'
+
+import type { Database } from '@/lib/database.types'
+
+export const dynamic = 'force-dynamic'
+
+export async function POST(request: Request) {
+ const requestUrl = new URL(request.url)
+ const formData = await request.formData()
+ const email = String(formData.get('email'))
+ const password = String(formData.get('password'))
+ const supabase = createRouteHandlerClient({ cookies })
+
+ await supabase.auth.signInWithPassword({
+ email,
+ password,
+ })
+
+ return NextResponse.redirect(requestUrl.origin, {
+ status: 301,
+ })
+}
+```
+
+
+
+TypeScript types can be [generated with the Supabase CLI](/docs/reference/javascript/typescript-support) and passed to `createRouteHandlerClient` to add type support to the Supabase client.
+
+
+
+
+
+
+
+
+Returning a `301` status redirects from a POST to a GET route
+
+
+
+#### Logout Route
+
+
+
+
+```jsx app/auth/logout.js
+import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
+import { cookies } from 'next/headers'
+import { NextResponse } from 'next/server'
+
+export const dynamic = 'force-dynamic'
+
+export async function POST(request) {
+ const requestUrl = new URL(request.url)
+ const supabase = createRouteHandlerClient({ cookies })
+
+ await supabase.auth.signOut()
+
+ return NextResponse.redirect(`${requestUrl.origin}/login`, {
+ status: 301,
+ })
+}
+```
+
+
+
+
+
+```tsx app/auth/logout.ts
+import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
+import { cookies } from 'next/headers'
+import { NextResponse } from 'next/server'
+
+import type { Database } from '@/lib/database.types'
+
+export const dynamic = 'force-dynamic'
+
+export async function POST(request: Request) {
+ const requestUrl = new URL(request.url)
+ const supabase = createRouteHandlerClient({ cookies })
+
+ await supabase.auth.signOut()
+
+ return NextResponse.redirect(`${requestUrl.origin}/login`, {
+ status: 301,
+ })
+}
+```
+
+
+
+TypeScript types can be [generated with the Supabase CLI](/docs/reference/javascript/typescript-support) and passed to `createRouteHandlerClient` to add type support to the Supabase client.
+
+
+
+
+
+
+#### Login Page
+
+
+
+
+```jsx app/login.jsx
+export default function Login() {
return (
-
)
}
@@ -445,70 +636,21 @@ export default async function Login() {
-```tsx app/login.ts
-import { createServerActionClient } from '@supabase/auth-helpers-nextjs'
-import { revalidatePath } from 'next/cache'
-import { cookies } from 'next/headers'
-
-import type { Database } from '@/lib/database.types'
-
-export default async function Login() {
- const handleSignUp = async (formData: FormData) => {
- 'use server'
- const email = String(formData.get('email'))
- const password = String(formData.get('password'))
-
- const supabase = createServerActionClient({ cookies })
- await supabase.auth.signUp({
- email,
- password,
- options: {
- emailRedirectTo: 'http://localhost:3000/auth/callback',
- },
- })
-
- revalidatePath('/')
- }
-
- const handleSignIn = async (formData: FormData) => {
- 'use server'
- const email = String(formData.get('email'))
- const password = String(formData.get('password'))
-
- const supabase = createServerActionClient({ cookies })
- await supabase.auth.signInWithPassword({
- email,
- password,
- })
-
- revalidatePath('/')
- }
-
- const handleSignOut = async () => {
- 'use server'
- const supabase = createServerActionClient({ cookies })
- await supabase.auth.signOut()
- revalidatePath('/')
- }
-
+```tsx app/login.tsx
+export default function Login() {
return (
-
)
}
```
-
-
-TypeScript types can be [generated with the Supabase CLI](/docs/reference/javascript/typescript-support) and passed to `createServerActionClient` to add type support to the Supabase client.
-
-
-
@@ -551,7 +693,7 @@ This allows for the Supabase client to be easily instantiated in the correct con
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'
import { useEffect, useState } from 'react'
-export default function Home() {
+export default function Page() {
const [todos, setTodos] = useState()
const supabase = createClientComponentClient()
@@ -582,7 +724,7 @@ import type { Database } from '@/lib/database.types'
type Todo = Database['public']['Tables']['todos']['Row']
-export default function Home() {
+export default function Page() {
const [todos, setTodos] = useState
(null)
const supabase = createClientComponentClient()
@@ -653,7 +795,9 @@ In order to use Supabase in Server Components, you need to have implemented the
import { cookies } from 'next/headers'
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs'
-export default async function Home() {
+export const dynamic = 'force-dynamic'
+
+export default async function Page() {
const supabase = createServerComponentClient({ cookies })
const { data } = await supabase.from('todos').select()
return {JSON.stringify(data, null, 2)}
@@ -670,6 +814,8 @@ import { createServerComponentClient } from '@supabase/auth-helpers-nextjs'
import type { Database } from '@/lib/database.types'
+export const dynamic = 'force-dynamic'
+
export default async function ServerComponent() {
const supabase = createServerComponentClient({ cookies })
const { data } = await supabase.from('todos').select()
@@ -724,6 +870,8 @@ import { cookies } from 'next/headers'
import { createServerActionClient } from '@supabase/auth-helpers-nextjs'
import { revalidatePath } from 'next/cache'
+export const dynamic = 'force-dynamic'
+
export default async function NewTodo() {
const addTodo = async (formData) => {
'use server'
@@ -753,6 +901,8 @@ import { revalidatePath } from 'next/cache'
import type { Database } from '@/lib/database.types'
+export const dynamic = 'force-dynamic'
+
export default async function NewTodo() {
const addTodo = async (formData: FormData) => {
'use server'
@@ -801,11 +951,13 @@ TypeScript types can be [generated with the Supabase CLI](/docs/reference/javasc
>
-```jsx app/api/todos/route.jsx
+```jsx app/api/todos/route.js
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'
import { cookies } from 'next/headers'
+export const dynamic = 'force-dynamic'
+
export async function POST(request) {
const { title } = await request.json()
const supabase = createRouteHandlerClient({ cookies })
@@ -818,13 +970,15 @@ export async function POST(request) {
-```tsx app/api/todos/route.tsx
+```tsx app/api/todos/route.ts
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'
import { cookies } from 'next/headers'
import type { Database } from '@/lib/database.types'
+export const dynamic = 'force-dynamic'
+
export async function POST(request: Request) {
const { title } = await request.json()
const supabase = createRouteHandlerClient({ cookies })
@@ -846,6 +1000,258 @@ TypeScript types can be [generated with the Supabase CLI](/docs/reference/javasc
See [refreshing session example](/docs/guides/auth/auth-helpers/nextjs#managing-session-with-middleware) above.
+### Edge Runtime
+
+The Next.js Edge Runtime allows you to host Server Components and Route Handlers from Edge nodes, serving the routes as close as possible to your user's location.
+
+A route can be configured to use the Edge Runtime by exporting a `runtime` variable set to `edge`. Additionally, the `cookies()` function must be called from the Edge route, before creating a Supabase Client.
+
+#### Server Components
+
+
+
+
+```jsx app/page.jsx
+import { cookies } from 'next/headers'
+import { createServerComponentClient } from '@supabase/auth-helpers-nextjs'
+
+export const runtime = 'edge'
+export const dynamic = 'force-dynamic'
+
+export default async function Page() {
+ const cookieStore = cookies()
+
+ const supabase = createServerComponentClient({
+ cookies: () => cookieStore,
+ })
+
+ const { data } = await supabase.from('todos').select()
+ return {JSON.stringify(data, null, 2)}
+}
+```
+
+
+
+
+
+```tsx app/page.tsx
+import { cookies } from 'next/headers'
+import { createServerComponentClient } from '@supabase/auth-helpers-nextjs'
+
+import type { Database } from '@/lib/database.types'
+
+export const runtime = 'edge'
+export const dynamic = 'force-dynamic'
+
+export default async function Page() {
+ const cookieStore = cookies()
+
+ const supabase = createServerComponentClient({
+ cookies: () => cookieStore,
+ })
+
+ const { data } = await supabase.from('todos').select()
+ return {JSON.stringify(data, null, 2)}
+}
+```
+
+
+
+TypeScript types can be [generated with the Supabase CLI](/docs/reference/javascript/typescript-support) and passed to `createServerComponentClient` to add type support to the Supabase client.
+
+
+
+
+
+
+#### Route Handlers
+
+
+
+
+```jsx app/api/todos/route.js
+import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
+import { NextResponse } from 'next/server'
+import { cookies } from 'next/headers'
+
+export const runtime = 'edge'
+export const dynamic = 'force-dynamic'
+
+export async function POST(request) {
+ const { title } = await request.json()
+ const cookieStore = cookies()
+
+ const supabase = createRouteHandlerClient({
+ cookies: () => cookieStore,
+ })
+
+ const { data } = await supabase.from('todos').insert({ title }).select()
+ return NextResponse.json(data)
+}
+```
+
+
+
+
+
+```tsx app/api/todos/route.ts
+import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
+import { NextResponse } from 'next/server'
+import { cookies } from 'next/headers'
+
+import type { Database } from '@/lib/database.types'
+
+export const runtime = 'edge'
+export const dynamic = 'force-dynamic'
+
+export async function POST(request: Request) {
+ const { title } = await request.json()
+ const cookieStore = cookies()
+
+ const supabase = createRouteHandlerClient({
+ cookies: () => cookieStore,
+ })
+
+ const { data } = await supabase.from('todos').insert({ title }).select()
+ return NextResponse.json(data)
+}
+```
+
+
+
+TypeScript types can be [generated with the Supabase CLI](/docs/reference/javascript/typescript-support) and passed to `createRouteHandlerClient` to add type support to the Supabase client.
+
+
+
+
+
+
+### Static Routes
+
+Server Components and Route Handlers are static by default - data is fetched once at build time and the value is cached. Since the request to Supabase now happens at build time, there is no user, session or cookie to pass along with the request to Supabase. Therefore, the `createClient` function from `supabase-js` can be used to fetch data for static routes.
+
+#### Server Components
+
+
+
+
+```jsx app/page.jsx
+import { createClient } from '@supabase/supabase-js'
+
+export default async function Page() {
+ const supabase = createClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
+ )
+
+ const { data } = await supabase.from('todos').select()
+ return {JSON.stringify(data, null, 2)}
+}
+```
+
+
+
+
+
+```tsx app/page.tsx
+import { createClient } from '@supabase/supabase-js'
+
+import type { Database } from '@/lib/database.types'
+
+export default async function Page() {
+ const supabase = createClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
+ )
+
+ const { data } = await supabase.from('todos').select()
+ return {JSON.stringify(data, null, 2)}
+}
+```
+
+
+
+TypeScript types can be [generated with the Supabase CLI](/docs/reference/javascript/typescript-support) and passed to `createClient` to add type support to the Supabase client.
+
+
+
+
+
+
+#### Route Handlers
+
+
+
+
+```jsx app/api/todos/route.js
+import { createClient } from '@supabase/supabase-js'
+import { NextResponse } from 'next/server'
+
+export async function POST(request) {
+ const { title } = await request.json()
+
+ const supabase = createClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
+ )
+
+ const { data } = await supabase.from('todos').insert({ title }).select()
+ return NextResponse.json(data)
+}
+```
+
+
+
+
+
+```tsx app/api/todos/route.ts
+import { createClient } from '@supabase/supabase-js'
+import { NextResponse } from 'next/server'
+
+import type { Database } from '@/lib/database.types'
+
+export async function POST(request: Request) {
+ const { title } = await request.json()
+
+ const supabase = createClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
+ )
+
+ const { data } = await supabase.from('todos').insert({ title }).select()
+ return NextResponse.json(data)
+}
+```
+
+
+
+TypeScript types can be [generated with the Supabase CLI](/docs/reference/javascript/typescript-support) and passed to `createClient` to add type support to the Supabase client.
+
+
+
+
+
+
## More examples
- [Build a Twitter Clone with the Next.js App Router and Supabase - free egghead course](https://egghead.io/courses/build-a-twitter-clone-with-the-next-js-app-router-and-supabase-19bebadb)
diff --git a/apps/docs/pages/guides/cli/github-action/[slug].tsx b/apps/docs/pages/guides/cli/github-action/[slug].tsx
index 181f3acb409..fe30a5f3f2a 100644
--- a/apps/docs/pages/guides/cli/github-action/[slug].tsx
+++ b/apps/docs/pages/guides/cli/github-action/[slug].tsx
@@ -11,6 +11,7 @@ import Layout from '~/layouts/DefaultGuideLayout'
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'
// We fetch these docs at build time from an external repo
const org = 'supabase'
@@ -31,6 +32,13 @@ const pageMap = [
},
remoteFile: 'generating-types.md',
},
+ {
+ slug: 'api',
+ meta: {
+ title: 'GraphQL API',
+ },
+ remoteFile: 'api.md',
+ },
]
interface ActionDocsProps {
@@ -116,6 +124,7 @@ export const getStaticProps: GetStaticProps = async ({ params }
remarkPlugins: [
remarkGfm,
remarkMkDocsAdmonition,
+ remarkPyMdownTabs,
[removeTitle, meta.title],
[remarkCodeHike, codeHikeOptions],
],
diff --git a/apps/docs/pages/guides/database/extensions/wrappers/[slug].tsx b/apps/docs/pages/guides/database/extensions/wrappers/[slug].tsx
new file mode 100644
index 00000000000..2363a4ce7d0
--- /dev/null
+++ b/apps/docs/pages/guides/database/extensions/wrappers/[slug].tsx
@@ -0,0 +1,181 @@
+import { CodeHikeConfig, remarkCodeHike } from '@code-hike/mdx'
+import { GetStaticPaths, GetStaticProps } from 'next'
+import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote'
+import { serialize } from 'next-mdx-remote/serialize'
+import { relative } from 'path'
+import rehypeSlug from 'rehype-slug'
+import remarkGfm from 'remark-gfm'
+import emoji from 'remark-emoji'
+import codeHikeTheme from 'config/code-hike.theme.json' assert { type: 'json' }
+import components from '~/components'
+import Layout from '~/layouts/DefaultGuideLayout'
+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'
+
+// We fetch these docs at build time from an external repo
+const org = 'supabase'
+const repo = 'wrappers'
+const branch = 'main'
+const docsDir = 'docs'
+const externalSite = 'https://supabase.github.io/wrappers'
+
+// Each external docs page is mapped to a local page
+const pageMap = [
+ {
+ slug: 'airtable',
+ meta: {
+ title: 'Airtable',
+ },
+ remoteFile: 'airtable.md',
+ },
+ {
+ slug: 's3',
+ meta: {
+ title: 'AWS S3',
+ },
+ remoteFile: 's3.md',
+ },
+ {
+ slug: 'bigquery',
+ meta: {
+ title: 'BigQuery',
+ },
+ remoteFile: 'bigquery.md',
+ },
+ {
+ slug: 'clickhouse',
+ meta: {
+ title: 'ClickHouse',
+ },
+ remoteFile: 'clickhouse.md',
+ },
+ {
+ slug: 'firebase',
+ meta: {
+ title: 'Firebase',
+ },
+ remoteFile: 'firebase.md',
+ },
+ {
+ slug: 'logflare',
+ meta: {
+ title: 'Logflare',
+ },
+ remoteFile: 'logflare.md',
+ },
+ {
+ slug: 'stripe',
+ meta: {
+ title: 'Stripe',
+ },
+ remoteFile: 'stripe.md',
+ },
+]
+
+interface WrappersDocsProps {
+ source: MDXRemoteSerializeResult
+ meta: {
+ title: string
+ description?: string
+ }
+}
+
+export default function WrappersDocs({ source, meta }: WrappersDocsProps) {
+ return (
+
+
+
+ )
+}
+
+/**
+ * Fetch markdown from external repo and transform links
+ */
+export const getStaticProps: GetStaticProps = async ({ params }) => {
+ const page = pageMap.find(({ slug }) => slug === params.slug)
+
+ if (!page) {
+ throw new Error(`No page mapping found for slug '${params.slug}'`)
+ }
+
+ const { remoteFile, meta } = page
+
+ const response = await fetch(
+ `https://raw.githubusercontent.com/${org}/${repo}/${branch}/${docsDir}/${remoteFile}`
+ )
+
+ const source = await response.text()
+
+ const urlTransform: UrlTransformFunction = (url) => {
+ try {
+ const externalSiteUrl = new URL(externalSite)
+
+ 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 relativePage = (
+ pathname.endsWith('.md')
+ ? pathname.replace(/\.md$/, '')
+ : relative(externalSiteUrl.pathname, pathname)
+ ).replace(/^\//, '')
+
+ const page = pageMap.find(({ remoteFile }) => `${relativePage}.md` === remoteFile)
+
+ // If we have a mapping for this page, use the mapped path
+ if (page) {
+ return page.slug + hash
+ }
+
+ // If we don't have this page in our docs, link to original docs
+ return `${externalSite}/${relativePage}${hash}`
+ } catch (err) {
+ console.error('Error transforming markdown URL', err)
+ return url
+ }
+ }
+
+ const codeHikeOptions: CodeHikeConfig = {
+ theme: codeHikeTheme,
+ lineNumbers: true,
+ showCopyButton: true,
+ skipLanguages: [],
+ autoImport: false,
+ }
+
+ const mdxSource = await serialize(source, {
+ scope: {
+ chCodeConfig: codeHikeOptions,
+ },
+ mdxOptions: {
+ remarkPlugins: [
+ remarkGfm,
+ remarkMkDocsAdmonition,
+ emoji,
+ remarkPyMdownTabs,
+ [removeTitle, meta.title],
+ [remarkCodeHike, codeHikeOptions],
+ ],
+ rehypePlugins: [[linkTransform, urlTransform], rehypeSlug],
+ },
+ })
+
+ return { props: { source: mdxSource, meta } }
+}
+
+export const getStaticPaths: GetStaticPaths = async () => {
+ return {
+ paths: pageMap.map(({ slug }) => ({
+ params: {
+ slug,
+ },
+ })),
+ fallback: false,
+ }
+}
diff --git a/apps/docs/pages/guides/database/extensions/wrappers.mdx b/apps/docs/pages/guides/database/extensions/wrappers/overview.mdx
similarity index 78%
rename from apps/docs/pages/guides/database/extensions/wrappers.mdx
rename to apps/docs/pages/guides/database/extensions/wrappers/overview.mdx
index 5d99f88ea08..16373b0a30e 100644
--- a/apps/docs/pages/guides/database/extensions/wrappers.mdx
+++ b/apps/docs/pages/guides/database/extensions/wrappers/overview.mdx
@@ -2,7 +2,7 @@ import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'wrappers',
- title: 'wrappers: 3rd Party Integrations',
+ title: 'Wrappers: 3rd Party Integrations',
description: '3rd party integrations for PostgreSQL ',
}
@@ -60,12 +60,13 @@ A web interface for connecting to external data is coming to Supabase Studio in
## Integrations
-- [Firebase](https://supabase.github.io/wrappers/firebase/)
-- [Stripe](https://supabase.github.io/wrappers/stripe/)
-- [BigQuery](https://supabase.github.io/wrappers/bigquery/)
-- [AWS S3](https://supabase.github.io/wrappers/s3/)
-- Airtable - Coming Soon
-- Clickhouse - Coming Soon
+- [Airtable](/docs/guides/database/extensions/wrappers/airtable)
+- [AWS S3](/docs/guides/database/extensions/wrappers/s3)
+- [BigQuery](/docs/guides/database/extensions/wrappers/bigquery)
+- [ClickHouse](/docs/guides/database/extensions/wrappers/clickhouse)
+- [Firebase](/docs/guides/database/extensions/wrappers/firebase)
+- [Logflare](/docs/guides/database/extensions/wrappers/logflare)
+- [Stripe](/docs/guides/database/extensions/wrappers/stripe)
## Resources
diff --git a/apps/docs/pages/guides/platform/custom-postgres-config.mdx b/apps/docs/pages/guides/platform/custom-postgres-config.mdx
index be25220006c..c5888bf6a6e 100644
--- a/apps/docs/pages/guides/platform/custom-postgres-config.mdx
+++ b/apps/docs/pages/guides/platform/custom-postgres-config.mdx
@@ -73,3 +73,15 @@ max_parallel_workers |3 |
export const Page = ({ children }) =>
export default Page
+
+## Pooler Config
+
+You can also [customize some parameters](https://supabase.com/dashboard/project/_/settings/database) for the Connection Pooler:
+
+1. [Pooling Mode](https://devcenter.heroku.com/articles/best-practices-pgbouncer-configuration#pgbouncer-s-connection-pooling-modes)
+1. [Default Pool Size](https://www.pgbouncer.org/config.html)
+1. [Max Client Connections](https://www.pgbouncer.org/config.html)
+
+The default pool size, and the maximum number of clients allowed to connect concurrently is automatically optimized based on the compute add-on being used. At the moment, the Dashboard only reflects any custom configuration being used, and does not include the default optimized numbers used for your project.
+
+Custom Pooler Config may also require manual updates to any relevant overrides when changing compute add-ons.
diff --git a/apps/docs/pages/guides/self-hosting/docker.mdx b/apps/docs/pages/guides/self-hosting/docker.mdx
index d3d326c5c11..3122ba067d3 100644
--- a/apps/docs/pages/guides/self-hosting/docker.mdx
+++ b/apps/docs/pages/guides/self-hosting/docker.mdx
@@ -48,7 +48,7 @@ We can use your JWT Secret to generate new `anon` and `service` API keys using t
### Update API Keys
-Replace the values the `.env` file:
+Replace the values in the `.env` file:
- `ANON_KEY` - replace with an `anon` key
- `SERVICE_ROLE_KEY` - replace with a `service` key
diff --git a/apps/docs/pages/index.mdx b/apps/docs/pages/index.mdx
index 8024c9629a4..68b70f43e4b 100644
--- a/apps/docs/pages/index.mdx
+++ b/apps/docs/pages/index.mdx
@@ -25,7 +25,7 @@ export const meta = {
## Products
-