docs: standardize quickstart guides (#48950)

## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Docs update

## What is the new behavior?

- All 19 guides follow one step order: create project → set up database
→ create app → AI tooling → add keys → create client → query data → run
it → go to production. Added _template.mdx with structure requirements;
it is not enforced with a lint check for now - this will be a separate
PR before adding new guides.
- 4 new partials replace copy-pasted blocks (AI tooling, connection
strings, mobile env vars, going to production).
- Error handling: return a message instead of a blank page when a query
fails.
- All guides verified and tested separately - all work as described.
What was fixed: wrong env var names in the Hono sample, a Next.js page
that redirected to login, missing database permissions in Refine and
Hono, and stale file paths and APIs in SvelteKit, Refine, and TanStack.
- Astro, Expo, Python, Laravel, and Rails were live but missing from the
quickstart grid or listing page. Added, with two new icons.

## Quick links for review

Base preview:
https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs

**Quickstart discovery**: new Astro/Expo/Python/Laravel/Rails entries
and icons

- [Docs homepage
grid](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs)
<img width="1998" height="882" alt="CleanShot 2026-08-12 at 12 06 31@2x"
src="https://github.com/user-attachments/assets/942eb7e2-1e85-4b20-a6a7-c2b127d31b2b"
/>


- [Getting started
overview](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started)
<img width="856" height="878" alt="CleanShot 2026-08-12 at 12 13 30@2x"
src="https://github.com/user-attachments/assets/d48091a9-7daf-4796-a521-14116b7479c9"
/>

### New shared files:


**[apps/docs/content/guides/getting-started/quickstarts/_template.mdx](https://github.com/supabase/supabase/blob/e311542913cf8da07f322a7586339d6f5de30c61/apps/docs/content/guides/getting-started/quickstarts/_template.mdx?plain=1)**
A reference contract the other 19 quickstart guides are checked against.
Documents the required frontmatter, the canonical 10-step section order,
every guide's deviation from that order (and why), the direct-Postgres
exception (Laravel/Rails/RedwoodJS/Spring Boot), and the
discovery-surface/icon requirements for adding a new guide. No lint rule
enforces it yet; that's a follow-up PR.


**[apps/docs/content/_partials/quickstart_ai_tooling.mdx](https://github.com/supabase/supabase/blob/e311542913cf8da07f322a7586339d6f5de30c61/apps/docs/content/_partials/quickstart_ai_tooling.mdx?plain=1)**
Example:
[Next.js](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/nextjs#4-set-up-ai-tooling-optional)
→ "Set up AI tooling" section
Shared by all 19 guides: astrojs, expo-react-native, flask, flutter,
hono, ios-swiftui, kotlin, laravel, nextjs, nuxtjs, reactjs, redwoodjs,
refine, ruby-on-rails, solidjs, spring-boot, sveltekit, tanstack, vue


**[apps/docs/content/_partials/quickstart_going_to_production.mdx](https://github.com/supabase/supabase/blob/e311542913cf8da07f322a7586339d6f5de30c61/apps/docs/content/_partials/quickstart_going_to_production.mdx?plain=1)**
Example:
[Next.js](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/nextjs#going-to-production)
→ "Going to production" section
Shared by all 19 guides: same full list as above


**[apps/docs/content/_partials/quickstart_connection_string.mdx](https://github.com/supabase/supabase/blob/e311542913cf8da07f322a7586339d6f5de30c61/apps/docs/content/_partials/quickstart_connection_string.mdx?plain=1)**
Example:
[Laravel](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/laravel#5-set-up-the-postgres-connection-details)
→ connection string setup step
Shared by 3 guides: laravel, ruby-on-rails, spring-boot – the
ORM/backend frameworks that connect directly to Postgres rather than
through the Data API


**[apps/docs/content/_partials/quickstart_mobile_env_note.mdx](https://github.com/supabase/supabase/blob/e311542913cf8da07f322a7586339d6f5de30c61/apps/docs/content/_partials/quickstart_mobile_env_note.mdx?plain=1)**
Example: [iOS
SwiftUI](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/ios-swiftui#get-api-details:~:text=This%20guide%20substitutes%20your%20project%20URL%20and%20key%20directly)
→ environment variables step
Shared by 3 guides: ios-swiftui, flutter, kotlin – note Expo React
Native is mobile too but doesn't use this partial, since it has its own
`EXPO_PUBLIC_` prefix convention inline instead.

## Per guide changes

**[Astro](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/astrojs#9-query-supabase-data-from-astro)**
Typed query error in the server client sample.

**[Expo React
Native](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/expo-react-native#8-query-data-from-the-app)**
Added an `error` state alongside instruments. Also removed the broken
[`--web` verification
path](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/expo-react-native#9-start-the-app):
expo-sqlite needs Metro wasm + COEP/COOP config the guide never had
(CodeRabbit finding).


**[Flask](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/flask#7-create-the-supabase-client)**
Split "Create the Supabase client" and ["Query
data"](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/flask#8-query-data-from-the-app)
into their own steps.


**[Flutter](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/flutter#9-setup-deep-links-optional)**
Reworded the deep-links section; keeps the framework-specific [Android
`INTERNET` permission
subsection](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/flutter#android)
under "Going to production."


**[Hono](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/hono#6-declare-supabase-environment-variables)**
Split into "Install dependencies," "Declare environment variables," "Set
up anonymous sign-ins," and "Query data" as separate steps. Fixes wrong
env var names from the previous sample.

**[iOS
SwiftUI](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/ios-swiftui#8-query-data-from-the-app)**
Added an `isLoading` state so the loading overlay doesn't hang forever
on a successful empty result (CodeRabbit fix).


**[Kotlin](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/kotlin#5-install-dependencies)**
Fixed the Compose compiler plugin declaration: `apply false` was missing
from the app module (CodeRabbit finding).


**[Laravel](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/laravel#5-set-up-the-postgres-connection-details)**
Now uses the shared `quickstart_connection_string.mdx` partial for the
session-pooler/SSL guidance instead of inline copy.


**[Next.js](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/nextjs#6-allow-public-access-to-the-instruments-page)**
New step fixing the page that previously redirected to login. Its
middleware path check is also now segment-aware so it doesn't over-match
paths like `/instruments-private` (CodeRabbit finding).


**[Nuxt](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/nuxtjs#7-create-the-supabase-client)**
"Create the Supabase client" and ["Query
data"](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/nuxtjs#8-query-data-from-the-app)
split out as their own steps.


**[React](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/reactjs#7-create-the-supabase-client)**
Same
client-creation/[query-data](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/reactjs#8-query-data-from-the-app)
split as the other Vite-based guides.


**[RedwoodJS](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/redwoodjs#2-gather-database-connection-strings)**
Expanded into explicit transaction-mode/session-mode connection strings,
Prisma schema, migration, seed, and scaffold steps; fixes stale file
paths and APIs from the previous version.


**[Refine](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/refine#8-allow-writes-to-the-instruments-table)**
New step fixing the missing RLS grants that made the scaffolded
create/edit pages fail.

**[Ruby on
Rails](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/ruby-on-rails#4-set-up-the-postgres-connection-details)**
Now uses `quickstart_connection_string.mdx`; added a [reminder to save
the database
password](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/ruby-on-rails#1-create-a-supabase-project)
before it's needed for the connection string.


**[SolidJS](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/solidjs#7-create-the-supabase-client)**
Same
client-creation/[query-data](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/solidjs#8-query-data-from-the-app)
split, adapted to Solid's `resource.error`.

**[Spring
Boot](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/spring-boot#4-set-up-the-postgres-connection-details)**
Connection-string section now uses the shared partial instead of a
duplicated inline caution.


**[SvelteKit](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/sveltekit#8-query-data-from-the-app)**
Updated `load` functions (both `+page.js` and `+page.server.ts`
variants) with explicit query-error typing; fixes stale file paths and
APIs from the previous version.


**[TanStack](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/tanstack#8-query-supabase-data-from-tanstack-start)**
`fetchInstruments` now returns and renders the query error instead of
silently returning an empty list (CodeRabbit finding); fixes stale file
paths and APIs from the previous version.


**[Vue](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/vue#7-create-the-supabase-client)**
Same
client-creation/[query-data](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/vue#8-query-data-from-the-app)
split as the other Vite-based guides.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added SolidJS, RedwoodJS, Refine, Laravel, and Ruby on Rails
quickstarts.
* Added framework discovery entries for Astro, Expo React Native,
Python, Laravel, and Rails.
* Added optional AI tooling, MCP setup, connection-string, mobile
configuration, and production-readiness guidance.
* Added a Hono authentication example with anonymous sign-in, user
details, and instrument data.

* **Documentation**
* Expanded setup, environment, authentication, RLS, migration, SSL, and
deployment guidance.

* **Bug Fixes**
  * Improved sample error handling for failed data requests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Miranda Limonczenko <miranda.limonczenko@supabase.io>
This commit is contained in:
Illia BasalaievandMiranda Limonczenko authored and GitHub committed 2026-08-14 15:03:37 +02:00
1 parent ebb8e2336e
commit ee1eb5dbca
34 files changed
+1089 -431

No files matched your search

+1
View File
@@ -15,6 +15,7 @@
"@hono/vite-build": "^1.1.0",
"@hono/vite-dev-server": "^0.17.0",
"@types/node": "^20.11.17",
"typescript": "^5.6.2",
"vite": "^5.4.2"
}
}
+109
View File
@@ -0,0 +1,109 @@
import type { AppType } from '.'
import { createBrowserClient } from '@supabase/ssr'
import { hc } from 'hono/client'
import { useEffect, useState } from 'hono/jsx'
import { render } from 'hono/jsx/dom'
const client = hc<AppType>('/')
const supabase = createBrowserClient(
import.meta.env.VITE_SUPABASE_URL!,
import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY!
)
function App() {
const [user, setUser] = useState<null | { id: string }>(null)
// Check client-side if user is logged in:
useEffect(() => {
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((event, session) => {
console.log('Auth event:', event)
if (event === 'SIGNED_OUT') {
setUser(null)
} else {
setUser(session?.user!)
}
})
return () => subscription.unsubscribe()
}, [])
return (
<>
<h1>Hono Supabase Auth Example!</h1>
<h2>Sign in</h2>
{!user ? (
<SignIn />
) : (
<form method="post" action="/signout">
<button type="submit">Sign out!</button>
</form>
)}
<h2>Example of API fetch()</h2>
<UserDetailsButton />
<h2>Example of database read</h2>
<p>Sign in anonymously, then open the instruments list.</p>
<a href="/instruments">Get instruments</a>
</>
)
}
function SignIn() {
return (
<>
<p>
Read about and enable{' '}
<a href="https://supabase.com/docs/guides/auth/auth-anonymous" target="_blank">
anonymous sign-ins here!
</a>
</p>
<button
type="button"
onClick={async () => {
const { data, error } = await supabase.auth.signInAnonymously()
if (error) return console.error('Error signing in:', error.message)
console.log('Signed in client-side!')
alert('Signed in anonymously! User id: ' + data?.user?.id)
}}
>
Anonymous sign in
</button>
</>
)
}
const UserDetailsButton = () => {
const [response, setResponse] = useState<string | null>(null)
const handleClick = async () => {
const response = await client.api.user.$get()
const data = await response.json()
const headers = Array.from(response.headers.entries()).reduce<Record<string, string>>(
(acc, [key, value]) => {
acc[key] = value
return acc
},
{}
)
const fullResponse = {
url: response.url,
status: response.status,
headers,
body: data,
}
setResponse(JSON.stringify(fullResponse, null, 2))
}
return (
<div>
<button type="button" onClick={handleClick}>
Get My User Details
</button>
{response && <pre>{response}</pre>}
</div>
)
}
const root = document.getElementById('root')!
render(<App />, root)
+38 -9
View File
@@ -1,16 +1,19 @@
import { Hono } from 'hono'
import { csrf } from 'hono/csrf'
import { getSupabase, supabaseMiddleware } from './middleware/auth.middleware'
const app = new Hono()
app.use('*', csrf())
app.use('*', supabaseMiddleware())
app.get('/api/user', async (c) => {
const routes = app.get('/api/user', async (c) => {
const supabase = getSupabase(c)
const { data, error } = await supabase.auth.getClaims()
if (error) console.log('error', error)
if (!data?.user) {
if (!data?.claims) {
return c.json({
message: 'You are not logged in.',
})
@@ -18,23 +21,49 @@ app.get('/api/user', async (c) => {
return c.json({
message: 'You are logged in!',
userId: data.user,
userId: data.claims.sub,
})
})
app.get('/signout', async (c) => {
app.post('/signout', async (c) => {
const supabase = getSupabase(c)
await supabase.auth.signOut()
console.log('Signed out server-side!')
return c.redirect('/')
return c.redirect('/', 303)
})
// Retrieve data with RLS enabled. The signed in user's auth token is automatically sent.
app.get('/countries', async (c) => {
app.get('/instruments', async (c) => {
const supabase = getSupabase(c)
const { data, error } = await supabase.from('countries').select('*')
if (error) console.log(error)
const { data, error } = await supabase.from('instruments').select('*')
if (error) {
console.error(error)
return c.json({ error: error.message }, 500)
}
return c.json(data)
})
export type AppType = typeof routes
app.get('/', (c) => {
return c.html(
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta content="width=device-width, initial-scale=1" name="viewport" />
<link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css" />
{import.meta.env.PROD ? (
<script type="module" src="/static/client.js" />
) : (
<script type="module" src="/src/client.tsx" />
)}
</head>
<body>
<div id="root" />
</body>
</html>
)
})
export default app
@@ -3,6 +3,7 @@ import { SupabaseClient } from '@supabase/supabase-js'
import type { Context, MiddlewareHandler } from 'hono'
import { env } from 'hono/adapter'
import { setCookie } from 'hono/cookie'
import type { CookieOptions } from 'hono/utils/cookie'
import type { Database } from '../database.types'
@@ -17,22 +18,23 @@ export const getSupabase = (c: Context) => {
}
type SupabaseEnv = {
SUPABASE_URL: string
SUPABASE_PUBLISHABLE_KEY: string
VITE_SUPABASE_URL: string
VITE_SUPABASE_PUBLISHABLE_KEY: string
}
export const supabaseMiddleware = (): MiddlewareHandler => {
return async (c, next) => {
const supabaseEnv = env<SupabaseEnv>(c)
const supabaseUrl = supabaseEnv.SUPABASE_URL
const supabasePublishableKey = supabaseEnv.SUPABASE_PUBLISHABLE_KEY
const supabaseUrl = supabaseEnv.VITE_SUPABASE_URL ?? import.meta.env.VITE_SUPABASE_URL
const supabasePublishableKey =
supabaseEnv.VITE_SUPABASE_PUBLISHABLE_KEY ?? import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY
if (!supabaseUrl) {
throw new Error('SUPABASE_URL missing!')
throw new Error('VITE_SUPABASE_URL missing!')
}
if (!supabasePublishableKey) {
throw new Error('SUPABASE_PUBLISHABLE_KEY missing!')
throw new Error('VITE_SUPABASE_PUBLISHABLE_KEY missing!')
}
const supabase = createServerClient(supabaseUrl, supabasePublishableKey, {
@@ -40,8 +42,11 @@ export const supabaseMiddleware = (): MiddlewareHandler => {
getAll() {
return parseCookieHeader(c.req.header('Cookie') ?? '')
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) => setCookie(c, name, value, options))
setAll(cookiesToSet, cacheHeaders) {
cookiesToSet.forEach(({ name, value, options }) =>
setCookie(c, name, value, options as CookieOptions)
)
Object.entries(cacheHeaders).forEach(([key, value]) => c.header(key, value))
},
},
})
+12
View File
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"types": ["vite/client"],
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
}
+33
View File
@@ -0,0 +1,33 @@
import devServer from '@hono/vite-dev-server'
import { defineConfig } from 'vite'
// Change the import to use your runtime specific build
import build from '@hono/vite-build/node'
export default defineConfig(({ mode }) => {
if (mode === 'client')
return {
esbuild: {
jsxImportSource: 'hono/jsx/dom', // Optimized for hono/jsx/dom
},
build: {
rollupOptions: {
input: './src/client.tsx',
output: {
entryFileNames: 'static/client.js',
},
},
},
}
return {
plugins: [
build({
entry: 'src/index.tsx',
}),
devServer({
entry: 'src/index.tsx',
}),
],
}
})