惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

AI
AI
博客园 - 叶小钗
Blog — PlanetScale
Blog — PlanetScale
Microsoft Azure Blog
Microsoft Azure Blog
Vercel News
Vercel News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
MyScale Blog
MyScale Blog
大猫的无限游戏
大猫的无限游戏
A
About on SuperTechFans
量子位
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 【当耐特】
Martin Fowler
Martin Fowler
阮一峰的网络日志
阮一峰的网络日志
D
Docker
Jina AI
Jina AI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
The Register - Security
The Register - Security
J
Java Code Geeks
S
SegmentFault 最新的问题
月光博客
月光博客
G
Google Developers Blog
美团技术团队
Last Week in AI
Last Week in AI
L
LangChain Blog
Apple Machine Learning Research
Apple Machine Learning Research
T
The Blog of Author Tim Ferriss
腾讯CDC
Recent Announcements
Recent Announcements
Recorded Future
Recorded Future
The Cloudflare Blog
有赞技术团队
有赞技术团队
博客园_首页
博客园 - 聂微东
人人都是产品经理
人人都是产品经理
B
Blog
I
InfoQ
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
F
Fortinet All Blogs
B
Blog RSS Feed
Engineering at Meta
Engineering at Meta
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Microsoft Security Blog
Microsoft Security Blog
MongoDB | Blog
MongoDB | Blog
爱范儿
爱范儿
D
DataBreaches.Net
F
Full Disclosure
M
MIT News - Artificial intelligence
博客园 - 司徒正美
H
Help Net Security

Supabase Blog

AI Agents Know About Supabase. They Don't Always Use It Right. Custom OIDC Providers for Supabase Auth 100,000 GitHub stars Supabase docs over SSH Navigating Regional Network Blocks Supabase Joins the Stripe Projects Developer Preview Log Drains: Now available on Pro Supabase Storage: major performance, security, and reliability updates Supabase incident on February 12, 2026 Hydra joins Supabase X / Twitter OAuth 2.0 is now available for Supabase Auth BKND joins Supabase Supabase is now an official Claude connector Supabase PrivateLink is now available Introducing: Postgres Best Practices When to use Read Replicas vs. bigger compute Introducing TRAE SOLO integration with Supabase Supabase Security Retro: 2025 Sync Stripe Data to Your Supabase Database in One Click Building ChatGPT Apps with Supabase Edge Functions and mcp-use Own Your Observability: Supabase Metrics API Introducing iceberg-js: A JavaScript Client for Apache Iceberg Introducing Supabase for Platforms Adding Async Streaming to Postgres Foreign Data Wrappers Build "Sign in with Your App" using Supabase Auth Introducing Seven New Email Templates for Supabase Auth The new Supabase power for Kiro Introducing Supabase ETL Introducing Analytics Buckets Introducing Vector Buckets Snap, Inc. Launches Snap Cloud, Powered by Supabase Triplit joins Supabase Supabase Series E 1000 Y Combinator Founders Choose Supabase gm 👋 web3, welcome aboard to Sign in with Web3 (Solana, Ethereum) Announcing the Supabase Remote MCP Server Enterprise speed, enterprise standards with Bolt Cloud + Supabase PostgREST 13 Lovable Cloud + Supabase: The Default Platform for AI Builders Processing large jobs with Edge Functions, Cron, and Queues Defense in Depth for MCP Servers OrioleDB Patent: now freely available to the Postgres community Supabase Launch Week 15 Hackathon Winner Announcement The Vibe Coder's Guide to Supabase Environments Testing for Vibe Coders: From Zero to Production Confidence The Vibe Coding Master Checklist Vibe Coding: Best Practices for Prompting Supabase Auth: Build vs. Buy Top 10 Launches of Launch Week 15 Supabase Launch Week 15 Hackathon Storage: 10x Larger Uploads, 3x Cheaper Cached Egress, and 2x Egress Quota Persistent Storage and 97% Faster Cold Starts for Edge Functions Algolia Connector for Supabase New Observability Features in Supabase Improved Security Controls and A New Home for Security Introducing Branching 2.0 Stripe-To-Postgres Sync Engine as standalone Library Supabase Analytics Buckets with Iceberg Support Create a Supabase backend using Figma Make Introducing JWT Signing Keys Supabase UI: Platform Kit Build a Personalized AI Assistant with Postgres Announcing Multigres: Vitess for Postgres Building on open table formats Open Data Standards: Postgres, OTel, and Iceberg Simplifying back-end complexity with Supabase Data APIs PostgreSQL Event Triggers without superuser access Top 10 Launches of Launch Week 14 Supabase MCP Server Data API Routes to Nearest Read Replica Declarative Schemas for Simpler Database Management Realtime: Broadcast from Database Keeping Tabs on What's New in Supabase Studio Edge Functions: Deploy from the Dashboard + Deno 2.1 Automatic Embeddings in Postgres Introducing the Supabase UI Library Supabase Auth: Bring Your Own Clerk Postgres Language Server: Initial Release Migrating from Fauna to Supabase Migrating from the MongoDB Data API to Supabase Dedicated Poolers Postgres as a Graph Database: (Ab)using pgRouting AI Hackathon at Y Combinator Calendars in Postgres using Foreign Data Wrappers Supabase Launch Week 13 Hackathon Winners How to Hack the Base! Running Durable Workflows in Postgres using DBOS database.build v2: Bring-your-own-LLM Restore to a New Project Hack the Base! with Supabase Top 10 Launches of Launch Week 13 Supabase Queues High Performance Disk Supabase Cron Supabase CLI v2: Config as Code Supabase Edge Functions: Introducing Background Tasks, Ephemeral Storage, and WebSockets Supabase AI Assistant v2 OrioleDB Public Alpha Executing Dynamic JavaScript Code on Supabase with Edge Functions ClickHouse Partnership, improved Postgres Replication, and Disk Management
Fetching and caching Supabase data in Next.js 13 Server Components
Jon Meyers · 2022-11-17 · via Supabase Blog

Fetching and caching Supabase data in Next.js 13 Server Components

The biggest announcement from Next.js Conf 2022 was the release of Next.js 13, which introduces a collection of improvements, most exciting of which is Server Components. The combination of Server Components and Suspense allow for a more streamlined, reimagined way to fetch and cache data in Next.js applications. This provides excellent DX improvements — such as async components — and aligns the Next framework even closer with the future of React.

This article is going to look at how we can use these brand new async components to simplify fetching and caching data from Supabase. We will look all things auth in a separate article. Check out the Server Components example in the Auth Helpers repo if you just can't wait!

To learn more about any of the concepts covered in this article, check out the Next.js beta docs.

A good distinction to understand at this point, is that Next.js 13 is stable and ready for production, however, the app directory is still in beta and likely to change. This article will be focusing on the app directory, Server Components and Suspense so let's get experimental!

For an example of the code covered in this tutorial, check out this repo.

If you prefer video, check out our recent live stream where we stepped through a similar example.

Let’s get started by creating a brand new Next.js 13 app using the create-next-app package:


_10

npx create-next-app@latest --experimental-app next13


Now we can run our app in development mode:

And navigate to http://localhost:3000.

This should look pretty familiar, and scanning the folder structure for the app, it should look almost identical to Next.js 12, but with a new folder called app. This is where the new data fetching and caching magic takes place. 🪄

Each folder within the app directory represents a route in our application. Each folder must have a page component, which is rendered when the user navigates to the route, and optional layout, loading and error components.

Learn more about Page, Layout, Loading and Error components in the Next.js beta docs.

Before we jump into fetching data, we need some data to fetch. Let’s create a new Supabase project.

Once your instance is up and running, head over to the SQL Editor, paste in the following snippet and click RUN.


_11

create table if not exists posts (

_11

id uuid default gen_random_uuid() primary key,

_11

created_at timestamp with time zone default timezone('utc'::text, now()) not null,

_11

title text,

_11

content text

_11

);

_11

_11

insert into posts(title, content)

_11

values

_11

('My first post', 'Wow! What a great post.'),

_11

('My second post', 'This one needs a little work!');


This will create a table called posts, and populate it with some example data.

Let’s install the supabase-js library to fetch our posts.


_10

npm install @supabase/supabase-js


And add a .env.local file with the following environment variables:


_10

NEXT_PUBLIC_SUPABASE_URL=your-supabase-url

_10

NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key


The values for these can be found in your project’s API settings.

Lastly, we need to create a Supabase client. Create a file at utils/supabase.ts with the following content:


_10

import { createClient } from '@supabase/supabase-js'

_10

_10

export default createClient(

_10

process.env.NEXT_PUBLIC_SUPABASE_URL!,

_10

process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!

_10

)


For automatically adding types to the Supabase client, check out how to generate types.

Okay, let’s look at some different data fetching and caching strategies.

By default any page component in the app folder is a Server Component, and its data is fetched and cached by Next.js every time we build a new version of our application. This is equivalent to exporting a getStaticProps function from a component in the pages directory.

Let’s create a new file at app/static/page.tsx and populate with the following:


_10

import supabase from '../../utils/supabase'

_10

_10

export default async function Posts() {

_10

const { data: posts } = await supabase.from('posts').select()

_10

return <pre>{JSON.stringify(posts, null, 2)}</pre>

_10

}


Server Components support async/await by default, and suspend the rendering of the component until the data has been fetched. This means we don’t need to handle error or loading states in our component, keeping our rendering logic clean.

To learn more about displaying loading and error states, check out the documentation.

Let’s modify this component to render out a collection of <Link /> components, that navigate to a dedicated page for each post.


_16

import Link from 'next/link'

_16

import supabase from '../../utils/supabase'

_16

_16

export default async function Posts() {

_16

const { data: posts } = await supabase.from('posts').select('id, title')

_16

_16

if (!posts) {

_16

return <p>No posts found.</p>

_16

}

_16

_16

return posts.map((post) => (

_16

<p key={post.id}>

_16

<Link href={`/static/${post.id}`}>{post.title}</Link>

_16

</p>

_16

))

_16

}


Since we are only using id and title in our component, we can scope our query down to only return these two columns for each post.

Let’s create a dynamic route to handle displaying an individual post. Create a new file at app/static/[id]/page.tsx and populate with the following:


_12

import supabase from '../../../utils/supabase'

_12

import { notFound } from 'next/navigation'

_12

_12

export default async function Post({ params: { id } }: { params: { id: string } }) {

_12

const { data } = await supabase.from('posts').select().match({ id }).single()

_12

_12

if (!data) {

_12

notFound()

_12

}

_12

_12

return <pre>{JSON.stringify(data, null, 2)}</pre>

_12

}


Currently, this page is generated on-demand and then cached. This means the first person who visits the page will need to wait for the server to get the post data from Supabase. This won’t take long at all, because Supabase is Supa awesome! But, we can still make this slightly more efficient by telling Next.js a finite collection of paths that we want to generate at build time.

We do this by exporting out a generateStaticParams function from our dynamic page.


_10

export async function generateStaticParams() {

_10

const { data: posts } = await supabase.from('posts').select('id')

_10

_10

return posts?.map(({ id }) => ({

_10

id,

_10

}))

_10

}


This is similar to getStaticPaths in a pages component. Learn more here.

The full component should look something like this:


_20

import supabase from '../../../utils/supabase'

_20

import { notFound } from 'next/navigation'

_20

_20

export async function generateStaticParams() {

_20

const { data: posts } = await supabase.from('posts').select('id')

_20

_20

return posts?.map(({ id }) => ({

_20

id,

_20

}))

_20

}

_20

_20

export default async function Post({ params: { id } }: { params: { id: string } }) {

_20

const { data: post } = await supabase.from('posts').select().match({ id }).single()

_20

_20

if (!post) {

_20

notFound()

_20

}

_20

_20

return <pre>{JSON.stringify(post, null, 2)}</pre>

_20

}


Awesome! We now have a Supa snappy blog! The user never needs to wait for data to be fetched. All pages are statically generated at build time, and cached at CDN nodes close to our users! 🎉

Unfortunately, this means any changes we make in Supabase — adding, updating or deleting posts etc — will not be reflected in our blog. If we want to refresh this data on a regular basis, we need to tell Next.js when to revalidate.

By exporting a revalidate variable from our component, we can specify how many seconds we consider this data to be “fresh”.


_10

export const revalidate = 60


This is similar to returning a revalidate key from the getStaticProps function in a component from the pages directory.

So, for 60 seconds Next.js will continue to respond with the static version of our page. After 60 seconds, it will fetch fresh data from Supabase and generate a new static page. However, there is no downtime while this happens, as the previous static page will continue to be served until the “fresh” one has been successfully generated.

The Posts component should now look like this:


_18

import Link from 'next/link'

_18

import supabase from '../../utils/supabase'

_18

_18

export const revalidate = 60

_18

_18

export default async function Posts() {

_18

const { data: posts } = await supabase.from('posts').select('id, title')

_18

_18

if (!posts) {

_18

return <p>No posts found.</p>

_18

}

_18

_18

return posts.map((post) => (

_18

<p key={post.id}>

_18

<Link href={`/static/${post.id}`}>{post.title}</Link>

_18

</p>

_18

))

_18

}


And the Post component should look like this:


_22

import supabase from '../../../utils/supabase'

_22

import { notFound } from 'next/navigation'

_22

_22

export const revalidate = 60

_22

_22

export async function generateStaticParams() {

_22

const { data: posts } = await supabase.from('posts').select('id')

_22

_22

return posts?.map(({ id }) => ({

_22

id,

_22

}))

_22

}

_22

_22

export default async function Post({ params: { id } }: { params: { id: string } }) {

_22

const { data: post } = await supabase.from('posts').select().match({ id }).single()

_22

_22

if (!post) {

_22

notFound()

_22

}

_22

_22

return <pre>{JSON.stringify(post, null, 2)}</pre>

_22

}


We now get all the benefits of static — users not waiting around while data is fetched at request time — but we also get the benefits of dynamic data, as it is being refreshed on a regular basis.

Very cool! 😎

If we want fresh data to be fetched on every single request, we can simply set our revalidate value to 0.

Posts Component


_18

import Link from 'next/link'

_18

import supabase from '../../utils/supabase'

_18

_18

export const revalidate = 0

_18

_18

export default async function Posts() {

_18

const { data: posts } = await supabase.from('posts').select('id, title')

_18

_18

if (!posts) {

_18

return <p>No posts found.</p>

_18

}

_18

_18

return posts.map((post) => (

_18

<p key={post.id}>

_18

<Link href={`/static/${post.id}`}>{post.title}</Link>

_18

</p>

_18

))

_18

}


Single Post Component


_22

import supabase from '../../../utils/supabase'

_22

import { notFound } from 'next/navigation'

_22

_22

export const revalidate = 0

_22

_22

export async function generateStaticParams() {

_22

const { data: posts } = await supabase.from('posts').select('id')

_22

_22

return posts?.map(({ id }) => ({

_22

id,

_22

}))

_22

}

_22

_22

export default async function Post({ params: { id } }: { params: { id: string } }) {

_22

const { data: post } = await supabase.from('posts').select().match({ id }).single()

_22

_22

if (!post) {

_22

notFound()

_22

}

_22

_22

return <pre>{JSON.stringify(post, null, 2)}</pre>

_22

}


This is similar to exporting a getServerSideProps function from a component in the pages directory.

All this server stuff is great, but what if you want to use Supabase client-side? 🤔

There are many use-cases where you need to use Supabase client-side:

  1. Authentication

    Supabase Auth does a bunch of stuff behind the scenes — handling 3rd party OAuth flows, for example. This will break if you try to sign users in and out on the server.

  2. Realtime

    Supabase manages the awesome power of websockets on your behalf — something that is not yet solved in this serverless world.

  3. You prefer it

    There is nothing wrong with this! You do you!

To use Supabase client-side, we need to tell Next.js that this is a Client Component. We do this by specifying the use client directive at the top of our component. This opts into a similar flow to the pages directory — the component is rendered on the server and hydrated client-side.

The React team is working on an awesome new hook called "use", which will drastically simplify fetching data client-side, but for now, we still need to rely on the combination of useState and useEffect.

Let’s implement client-side data fetching.


_21

'use client'

_21

_21

import { useEffect, useState } from 'react'

_21

import supabase from '../../utils/supabase'

_21

_21

export default function ClientPosts() {

_21

const [isLoading, setIsLoading] = useState(true)

_21

const [posts, setPosts] = useState<any>([])

_21

_21

useEffect(() => {

_21

const fetchPosts = async () => {

_21

const { data } = await supabase.from('posts').select()

_21

setPosts(data)

_21

setIsLoading(false)

_21

}

_21

_21

fetchPosts()

_21

}, [])

_21

_21

return isLoading ? <p>Loading</p> : <pre>{JSON.stringify(posts, null, 2)}</pre>

_21

}


Again, check out how to generate types to add proper typing for Post.

But now we have loading spinners! Yuck!

Realtime allows us to subscribe to changes in Supabase — inserted, updated or deleted posts — and update our UI dynamically. In order to receive realtime events, we need to enable replication on the posts table.

Let’s merge the two previous concepts and fetch the initial state of our posts in a Server Component, and then render a Client Component to do client-y things — like subscribe to changes in the DB and update the UI dynamically:

Server Component


_10

import supabase from '../../utils/supabase'

_10

import RealtimePosts from './realtime-posts'

_10

_10

export const revalidate = 0

_10

_10

export default async function Realtime() {

_10

const { data } = await supabase.from('posts').select('*')

_10

return <RealtimePosts serverPosts={data} />

_10

}


Client Component


_27

'use client'

_27

_27

import { useEffect, useState } from 'react'

_27

import supabase from '../../utils/supabase'

_27

_27

export default function RealtimePosts({ serverPosts }: { serverPosts: any }) {

_27

const [posts, setPosts] = useState(serverPosts)

_27

_27

useEffect(() => {

_27

setPosts(serverPosts)

_27

}, [serverPosts])

_27

_27

useEffect(() => {

_27

const channel = supabase

_27

.channel('*')

_27

.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'posts' }, (payload) =>

_27

setPosts((posts: any) => [...posts, payload.new])

_27

)

_27

.subscribe()

_27

_27

return () => {

_27

supabase.removeChannel(channel)

_27

}

_27

}, [serverPosts])

_27

_27

return <pre>{JSON.stringify(posts, null, 2)}</pre>

_27

}


useEffect is used to subscribe to changes to the serverPosts prop. Without this, our component would not display fresh server-side results when the parent component is re-rendered, only on the first render.

This is a great pattern for fetching initial data server-side and subscribing to realtime changes client-side. This will likely be replaced by the use hook once it is stable with Next.js — it also uses suspense to suspend the rendering of a component while fetching data, and cleans up those loading and error states.

Next.js 13 Server Components are awesome! Suspense is awesome! Async components are awesome!

The combination of these concepts allow us to think about data fetching and caching as separate concerns, rather than specifying completely different data fetching functions like getStaticProps and getServerSideProps. If our caching requirements for a component change, we simply update the caching value, rather than refactoring our data fetching logic.

Additionally, by allowing any component in the tree to be either a server or client component — that is responsible for its own data and suspends rendering until it is ready — drastically simplifies our code, and provides much more flexible patterns for creating maintainable applications as complexity grows.