本文目标:在 30-60 分钟内,通过最小可运行示例,帮助你快速掌握 Supabase 与三种主流前端框架(Next.js, Astro, React)的集成方法,覆盖基础的数据库读写操作。
核心优势
- 零配置启动: 提供完整的代码片段与命令行指令,实现真正的“开箱即用”。
 - 框架全覆盖: 一篇文章同时满足 Next.js, Astro, React 开发者的入门需求。
 - 生产级实践: 虽然是快速入门,但代码结构与提示均考虑了后续扩展性。
 
1. 准备工作
在开始之前,请确保你已准备好以下环境与账号:
- Node.js: 版本 18 或更高。
 - 包管理器: pnpm, npm, 或 yarn。
 - Supabase 账号: 注册一个免费账号即可满足本教程所有需求。
 
2. Supabase 项目设置
创建项目
- 登录 Supabase Dashboard。
 - 点击 “New project” 创建一个新项目。
 - 在项目创建后,导航到 Settings -> API 页面。
 - 记录下你的 Project URL 和 anon (public) API key,后续步骤将频繁使用。
 
准备数据库表与 RLS 策略
为了演示数据读写,我们需要一张 messages 表,并为其配置行级安全 (Row Level Security, RLS) 策略。
安全提示以下 SQL 策略仅为演示目的,允许任何匿名用户读写
messages表。在生产环境中,你必须根据业务需求编写更严格的安全策略。
在 Supabase SQL Editor 中执行以下脚本:
-- 1) 创建用于演示的 "messages" 表create table if not exists public.messages (  id bigserial primary key,  content text not null,  created_at timestamp with time zone default now());
-- 2) 为 "messages" 表启用行级安全 (RLS)alter table public.messages enable row level security;
-- 3) 创建宽松的演示策略(生产环境请勿直接使用!)create policy "Allow anonymous read" on public.messages for select using (true);create policy "Allow anonymous insert" on public.messages for insert with check (true);配置环境变量
在你的本地前端项目根目录下,为不同框架创建对应的 .env.local 或 .env 文件,并填入之前记录的 Supabase 项目信息。
命名约定
不同前端框架对环境变量有不同的命名要求,请务必遵循:
- Next.js: 
NEXT_PUBLIC_ - Astro: 
PUBLIC_ - React (Vite): 
VITE_ 
Next.js (.env.local):
NEXT_PUBLIC_SUPABASE_URL=你的项目URLNEXT_PUBLIC_SUPABASE_ANON_KEY=你的anonKeyAstro (.env 或 .env.local):
PUBLIC_SUPABASE_URL=你的项目URLPUBLIC_SUPABASE_ANON_KEY=你的anonKeyReact (Vite) (.env.local):
VITE_SUPABASE_URL=你的项目URLVITE_SUPABASE_ANON_KEY=你的anonKey3. 前端框架集成示例
现在,我们将分别演示如何在 Next.js, Astro, 和 React (Vite) 中集成 Supabase。
Next.js 最小可运行示例
1) 初始化项目与依赖
pnpm dlx create-next-app@latest supa-next --ts --app --use-pnpmcd supa-nextpnpm add @supabase/supabase-js2) 创建 Supabase 客户端
新建 src/lib/supabaseClient.ts 文件:
import { createClient } from '@supabase/supabase-js'
export const supabase = createClient(  process.env.NEXT_PUBLIC_SUPABASE_URL!,  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!)3) 在页面中读写数据
修改 app/page.tsx 文件,实现消息的加载与新增:
'use client'
import { useEffect, useState, useCallback } from 'react'import { supabase } from '@/lib/supabaseClient'
type Message = {  id: number;  content: string;};
export default function Page() {  const [text, setText] = useState('')  const [rows, setRows] = useState<Message[]>([])  const [loading, setLoading] = useState(false)  const [error, setError] = useState<string | null>(null)
  const loadMessages = useCallback(async () => {    setLoading(true)    setError(null)    try {      const { data, error: dbError } = await supabase        .from('messages')        .select('id, content')        .order('id', { ascending: false })        .limit(20)
      if (dbError) throw dbError      setRows(data || [])    } catch (err: any) {      setError(err.message)    } finally {      setLoading(false)    }  }, [])
  const insertMessage = async () => {    if (!text.trim()) return    setLoading(true)    setError(null)    try {      const { error: dbError } = await supabase.from('messages').insert({ content: text.trim() })      if (dbError) throw dbError      setText('')      await loadMessages()    } catch (err: any) {      setError(err.message)    } finally {      setLoading(false)    }  }
  useEffect(() => {    void loadMessages()  }, [loadMessages])
  return (    <main style={{ padding: 24, maxWidth: '600px', margin: 'auto' }}>      <h1>Supabase × Next.js</h1>      <div style={{ display: 'flex', gap: 8, marginBottom: 16 }}>        <input          value={text}          onChange={(e) => setText(e.target.value)}          placeholder="输入消息"          style={{ flex: 1 }}        />        <button onClick={insertMessage} disabled={loading}>          {loading ? '提交中…' : '新增'}        </button>      </div>      {error && <p style={{ color: 'red' }}>Error: {error}</p>}      {loading && <p>Loading messages...</p>}      <ul>        {rows.map((r) => (          <li key={r.id}>#{r.id}: {r.content}</li>        ))}      </ul>    </main>  )}4) 运行
pnpm devAstro 最小可运行示例
Astro 示例将利用其组件化的特性,将客户端逻辑封装起来,使页面更整洁。
1) 初始化项目
pnpm create astro@latest supa-astro -- --template minimalcd supa-astropnpm add @supabase/supabase-js2) 创建 Supabase 客户端组件
新建 src/components/SupaMessages.astro 文件。这个组件将处理所有与 Supabase 的交互。
------<div id="messages-container">  <h1>Supabase × Astro</h1>  <div style={{ display: 'flex', gap: 8, marginBottom: 16 }}>    <input id="msg-input" placeholder="输入消息" style={{ flex: 1 }} />    <button id="add-btn">新增</button>  </div>  <div id="error-display" style={{ color: 'red' }}></div>  <p id="loading-display">Loading messages...</p>  <ul id="messages-list"></ul></div>
<script>  import { createClient } from "@supabase/supabase-js";
  const supabase = createClient(    import.meta.env.PUBLIC_SUPABASE_URL,    import.meta.env.PUBLIC_SUPABASE_ANON_KEY  );
  const container = document.getElementById('messages-container');  const listEl = container.querySelector('#messages-list');  const inputEl = container.querySelector('#msg-input') as HTMLInputElement;  const addBtn = container.querySelector('#add-btn');  const errorDisplay = container.querySelector('#error-display');  const loadingDisplay = container.querySelector('#loading-display');
  const loadMessages = async () => {    loadingDisplay.style.display = 'block';    errorDisplay.textContent = '';    try {      const { data, error } = await supabase        .from('messages')        .select('id, content')        .order('id', { ascending: false })        .limit(20);
      if (error) throw error;
      listEl.innerHTML = (data || [])        .map((r) => `<li>#${r.id}: ${r.content}</li>`)        .join('');    } catch (err: any) {      errorDisplay.textContent = `Error: ${err.message}`;    } finally {      loadingDisplay.style.display = 'none';    }  };
  addBtn.addEventListener('click', async () => {    const content = inputEl.value.trim();    if (!content) return;
    addBtn.setAttribute('disabled', 'true');    errorDisplay.textContent = '';
    try {      const { error } = await supabase.from('messages').insert({ content });      if (error) throw error;      inputEl.value = '';      await loadMessages();    } catch (err: any) {      errorDisplay.textContent = `Error: ${err.message}`;    } finally {      addBtn.removeAttribute('disabled');    }  });
  // Initial load  loadMessages();</script>3) 在页面中使用组件
修改 src/pages/index.astro:
---import SupaMessages from '../components/SupaMessages.astro';---
<html lang="zh-CN">  <head>    <meta charset="utf-8" />    <meta name="viewport" content="width=device-width, initial-scale=1" />    <title>Supabase × Astro</title>    <style>      body {        padding: 24px;        max-width: 600px;        margin: auto;      }    </style>  </head>  <body>    <SupaMessages />  </body></html>4) 运行
pnpm devReact(Vite)最小可运行示例
1) 初始化项目与依赖
pnpm create vite@latest supa-react -- --template react-tscd supa-reactpnpm add @supabase/supabase-js2) 创建 Supabase 客户端
新建 src/lib/supabase.ts:
import { createClient } from '@supabase/supabase-js'
export const supabase = createClient(  import.meta.env.VITE_SUPABASE_URL!,  import.meta.env.VITE_SUPABASE_ANON_KEY!)3) 在 App.tsx 中读写数据
修改 src/App.tsx:
import { useEffect, useState, useCallback } from 'react'import { supabase } from './lib/supabase'
type Message = {  id: number;  content: string;};
function App() {  const [text, setText] = useState('')  const [rows, setRows] = useState<Message[]>([])  const [loading, setLoading] = useState(false)  const [error, setError] = useState<string | null>(null)
  const loadMessages = useCallback(async () => {    setLoading(true)    setError(null)    try {      const { data, error: dbError } = await supabase        .from('messages')        .select('id, content')        .order('id', { ascending: false })        .limit(20)
      if (dbError) throw dbError      setRows(data || [])    } catch (err: any) {      setError(err.message)    } finally {      setLoading(false)    }  }, [])
  const insertMessage = async () => {    if (!text.trim()) return    setLoading(true)    setError(null)    try {      const { error: dbError } = await supabase.from('messages').insert({ content: text.trim() })      if (dbError) throw dbError      setText('')      await loadMessages()    } catch (err: any) {      setError(err.message)    } finally {      setLoading(false)    }  }
  useEffect(() => {    void loadMessages()  }, [loadMessages])
  return (    <div style={{ padding: 24, maxWidth: '600px', margin: 'auto' }}>      <h1>Supabase × React (Vite)</h1>      <div style={{ display: 'flex', gap: 8, marginBottom: 16 }}>        <input          value={text}          onChange={(e) => setText(e.target.value)}          placeholder="输入消息"          style={{ flex: 1 }}        />        <button onClick={insertMessage} disabled={loading}>          {loading ? '提交中…' : '新增'}        </button>      </div>      {error && <p style={{ color: 'red' }}>Error: {error}</p>}      {loading && <p>Loading messages...</p>}      <ul>        {rows.map((r) => <li key={r.id}>#{r.id}: {r.content}</li>)}      </ul>    </div>  )}
export default App4) 运行
pnpm dev4. 常见问题与排错
- 
403/401 权限错误:
- 检查 RLS 策略: 确保你的 RLS 策略允许当前操作(
select,insert等)。对于入门测试,可以使用教程中提供的宽松策略。 - 检查 API Key: 确认客户端使用的是 
anon(public) key,而不是service_role(secret) key。 
 - 检查 RLS 策略: 确保你的 RLS 策略允许当前操作(
 - 
连接失败:
- 核对 URL 和 Key: 仔细检查 
.env文件中的URL和anon key是否与 Supabase 项目设置中的完全一致,注意不要有空格或特殊字符。 
 - 核对 URL 和 Key: 仔细检查 
 - 
环境变量未加载:
- 遵循命名约定: 确保变量前缀符合框架要求 (
NEXT_PUBLIC_,VITE_,PUBLIC_)。 - 重启开发服务器: 修改 
.env文件后,需要重启开发服务器才能生效。 
 - 遵循命名约定: 确保变量前缀符合框架要求 (
 - 
CORS 跨域问题:
- Supabase 默认已为你的 API 启用 CORS。如果你通过自定义域名或代理访问,请检查浏览器控制台的错误信息,并在 Supabase Dashboard 的 API Settings -> CORS Configuration 中添加你的访问源。
 
 
5. 后续扩展方向
- 用户认证 (Auth): 探索 Supabase 强大的认证功能,如邮箱/密码登录、社交登录 (GitHub, Google)、魔法链接等。
 - Edge Functions: 将复杂的业务逻辑或需要安全环境的操作(如调用第三方服务、处理支付)封装在 Edge Functions 中。
 - 实时 (Realtime): 利用 Supabase 的实时订阅功能,监听数据库变更,构建聊天、协作等动态应用。
 - 存储 (Storage): 学习如何上传、下载和管理文件,如用户头像、文档等。
 
6. 附录:核心功能扩展
用户认证 (Auth)
Supabase 提供了强大且易于使用的认证解决方案。以下是如何在你的应用中集成基础的邮箱密码登录功能。
1) 调整 RLS 策略
为了让登录用户能够管理自己的消息,我们需要更新 messages 表的 RLS 策略。将之前的宽松策略替换为以下更安全的版本:
-- 删除旧的匿名策略DROP POLICY IF EXISTS "Allow anonymous read" ON public.messages;DROP POLICY IF EXISTS "Allow anonymous insert" ON public.messages;
-- 创建新的基于用户的策略-- 允许用户读取自己的消息CREATE POLICY "Allow individual read access" ON public.messages FOR SELECT USING (auth.uid() = user_id);-- 允许用户插入自己的消息CREATE POLICY "Allow individual insert access" ON public.messages FOR INSERT WITH CHECK (auth.uid() = user_id);
-- 为表添加 user_id 字段ALTER TABLE public.messages ADD COLUMN IF NOT EXISTS user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE;重要提示执行此操作前,请确保你的
messages表中已有一个user_id字段,用于关联auth.users表。如果还没有,请先添加该字段。
2) 构建认证组件
你可以创建一个简单的认证组件,处理注册、登录和登出逻辑。以下是一个 React 示例:
import { useState } from 'react';import { supabase } from '../lib/supabase'; // 假设你的 Supabase 客户端在这里
export default function Auth() {  const [email, setEmail] = useState('');  const [password, setPassword] = useState('');  const [loading, setLoading] = useState(false);
  const handleLogin = async (e) => {    e.preventDefault();    setLoading(true);    const { error } = await supabase.auth.signInWithPassword({ email, password });    if (error) alert(error.message);    setLoading(false);  };
  const handleSignup = async (e) => {    e.preventDefault();    setLoading(true);    const { error } = await supabase.auth.signUp({ email, password });    if (error) alert(error.message);    else alert('注册成功,请检查你的邮箱以激活账号!');    setLoading(false);  };
  return (    <div>      <form onSubmit={handleLogin}>        <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="邮箱" />        <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="密码" />        <button disabled={loading}>{loading ? '处理中...' : '登录'}</button>        <button onClick={handleSignup} disabled={loading}>注册</button>      </form>    </div>  );}实时 (Realtime)
通过 Supabase 的实时订阅,你的应用可以即时响应数据库的变化。
// 监听 messages 表的插入事件const channel = supabase.channel('messages-channel');
channel  .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'messages' }, (payload) => {    console.log('New message:', payload.new);    // 在这里更新你的 UI  })  .subscribe();
// 不再需要时,记得取消订阅// supabase.removeChannel(channel);部署指南 (Deployment Guide)
将集成了 Supabase 的前端应用部署到 Vercel 或 Netlify 等平台非常简单,核心是正确配置环境变量。
- 登录你的托管平台 (Vercel, Netlify, etc.)。
 - 导入你的 Git 仓库。
 - 配置环境变量:
- 在项目的设置页面中,找到环境变量 (Environment Variables) 配置区域。
 - 添加你在 
.env.local文件中使用的PUBLIC_SUPABASE_URL和PUBLIC_SUPABASE_ANON_KEY(或对应框架的前缀)。 - 重要: 确保变量名与你的代码中的 
import.meta.env或process.env调用完全匹配。 
 - 触发部署。
 
平台会自动检测你的框架 (Next.js, Astro, Vite) 并执行正确的构建命令。部署成功后,你的应用就可以在线访问了。