From 0fd5b733f36d522b91a5ac205c821da2ee8f170a Mon Sep 17 00:00:00 2001 From: Jon Meyers Date: Wed, 16 Nov 2022 11:48:53 +1100 Subject: [PATCH] add server components article --- ...ing-data-from-supabase-with-next-js-13.mdx | 159 ------ ...base-data-in-next-js-server-components.mdx | 466 ++++++++++++++++++ 2 files changed, 466 insertions(+), 159 deletions(-) delete mode 100644 apps/www/_blog/2022-10-30-fetching-data-from-supabase-with-next-js-13.mdx create mode 100644 apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx diff --git a/apps/www/_blog/2022-10-30-fetching-data-from-supabase-with-next-js-13.mdx b/apps/www/_blog/2022-10-30-fetching-data-from-supabase-with-next-js-13.mdx deleted file mode 100644 index 592babcdd06..00000000000 --- a/apps/www/_blog/2022-10-30-fetching-data-from-supabase-with-next-js-13.mdx +++ /dev/null @@ -1,159 +0,0 @@ ---- -title: 'Fetching and caching Supabase data with Next.js 13' -description: 'Next.js 13 introduces new data fetching and caching methods to enable React Server Components and Suspense.' -author: jonmeyers -image: TODO -thumb: TODO -tags: - - Next.js 13 - - Data Fetching - - Caching -date: '2022-10-30' -toc_depth: 3 ---- - -The PostgreSQL community has a specific, effective, and time-tested method for contributing to the core code. - -Proposals and changes to PostgreSQL (called patches) are submitted via email to the common Postgres hackers' mailing list pgsql-hackers@postgresql.org. Then you can track all activity simply by reading your mailbox. You can also follow this mailing list online at [postgresql.org/list/pgadmin-hackers](https://www.postgresql.org/list/pgadmin-hackers/). - -The mailing list contains a lot of different discussions. Some of the patches are preliminary POCs (proof-of-concept) not to be committed to the core directly, and some are urgent bug fixes. When a feature is intended for merging this should be done by creating a Commitfest entry, which resembles a pull request. - -## What is Commitfest? - -Commitfest is a month-long activity where Postgres contributors and core staff (committers) dedicate to looking at the patches, reviewing them, improving, and committing to the master branch. Effectively these are the months you most probably get feedback on your patches. - -There are 5 commitfests each year. You can see the schedule at [commitfest.postgresql.org](https://commitfest.postgresql.org/). You can only attach a patch to a commitfest **that hasn't started yet** (i.e. before 1st day of a commitfest month). - -## Why participate in Commitfest? - -There are numerous benefits: - -- You gain knowledge of PostgreSQL internals by reviewing other hackers' patches, and grow your profile within the community. -- The process of reviewing involves more senior PostgreSQL experts and as a rule, the code proposed becomes better. -- Lower risk for your code. If you're running a fork or extension that diverges from PG core then only you are responsible for its maintenance. Once it is merged, the entire community will help with maintenance and the API becomes the "official" way to do it. -- PostgreSQL commits are likely to survive longer than you do. Like a book manuscript, you get to leave a small history in the world. -- To re-enforce the community. Many features, like performance, have diverse options and many people discuss how to make them better. This is more effective when you join the party. - -## Which patches are welcome on a Commitfest? - -Generally, all patches that touch the core code are welcome. It's not necessary that they are complex. In fact, it's an excellent strategy to start by fixing or improving something small. - -The main topics to propose are: bugfixes, performance, server features, clients, security, SQL commands, testing improvements, replication and recovery, procedural languages, code refactoring (yes, it's very important!), monitoring and control, and documentation. - -Some ideas on what to submit: - -- Very small and uncontroversial fixes (especially bug fixes) have a good chance to be reviewed and committed even before Commitfest. If they aren't, just attach it to commitfest. -- If you make your feature an extension, the chance of committing it to the core is small. There are nice extensions like Pageinspect and Amcheck, that are committed into PG code as contribs but it is rare (contribs - are extensions that are inside the main PG package) -- There is no need to propose patches to refine translations, .po files etc. This is done by specialists. If you have user-visible messages in your patch just leave it English-only. -- Refining documents, readmes, and comments are welcome if they are clear and precise enough. You can propose a fix for only one line of comment but please don't do nitpicking. -- Performance-related features should better demonstrate performance increase at least in some use cases and no performance degradation in others. -- If your patch changes user behavior be ready to discuss and rework the interface. It's not easy to reach an agreement among many hackers with varying opinions. - -You can find a list of patches for the upcoming commitfest here: [commitfest.postgresql.org/40](https://commitfest.postgresql.org/40/) - -## Quick steps on how to make a patch - -Make a git branch that is based on the most recent master branch ([github.com/postgres/postgres](https://github.com/postgres/postgres)): - -```bash -git clone https://github.com/postgres/postgres -git checkout master -git checkout -b your_branch_name -``` - -Add your code to the branch (i.e. cherry-pick your commits from another branch). - -Rebase and squash your patch code to be 1 commit against master: - -``` -git rebase -i HEAD~5 - -# This will take the last 5 commits in a branch -# and you can do anything with them: -# squash, change order, change commit message, etc -``` - -Edit your commit message to be meaningful and clear. Be concise, but don't be too short. Reviewers enjoy understanding the purpose of a patch and what it does. This is especially important if the patchset consists of several patches. The first line of the commit message will become the patch name on a step later so make that line particularly clear and concise. - -Don't forget to add all the patch authors with their emails to the commit message. - -``` -git format-patch -1 -v13 - -# 1 = patch depth. -# The number of topmost patches to include -# v13 = the patch version. -# Make new version number anytime you want to -# send updated patch to the same thread) -``` - -Attach the file(s) to an email message (they look like `v13-0001-Naming-of-a-patch.patch`) - -Compose email message to pgsql-hackers@postgresql.org. Write all rationale and details of your proposal. The main thing to bear in mind is that attracting attention to your proposal is like 50% of success. Also, please be polite. - -It's encouraged to split complicated patches into several independently working parts as it's much easier to review (although not all complicated patches are easy to decompose). - -## Submitting to a Commitfest thread - -After you have created a patch, you can attach it to a Commitfest thread: - -- Make sure that you have sent email with your patch to the pgsql-hackers@postgresql.org mailing list -- Open commitfests page [commitfest.postgresql.org](https://commitfest.postgresql.org/) and choose the nearest commitfest with the status "Open" -- Click "New patch" button. -- Create a concise description, chose the relevant topic, and fill in the msgid in the hackers mailing list to attach a thread (which can be found in a full header of a mail message e.g.`Message-ID: `) -- Click "Create patch" button. - -That's all. Congrats! - -## After your patch is submitted to commitfest - -After you have a submitted your patch, several things will happen: - -- It will appear on CI page [cfbot.cputube.org](http://cfbot.cputube.org/) which will check its health against several architectures and OSes. If the patch has any errors then you should fix/rebase it. New versions of patches sent in messages replying to the mail thread are taken by CFBot automatically. Send updated versions of all patches in a patchset even if only one changed, otherwise cfbot will not take it properly. Clean status patches will attract reviewers' and committers' attention first. -- It will get "Needs review" status and anyone could add a review and change it to "Ready for committer" or "Waiting on author". -- You are supposed to **answer the questions** and communicate on your patch regularly during commitfest. That includes fixing the things the other hackers propose. It's likely better to follow their opinion and it almost always takes much less effort than arguing. Be polite, even if comments seem like nitpicking (probably they are not, actually). -- Give back. You are supposed to **review** the same number of patches that you proposed to commitfest and with similar complexity. You can choose what to review based on your interest, your knowledge of a particular part of Postgres (e.g. indexes or planner things), or even your personal acquaintance with the author. As a rule of thumb, you are supposed to specify if you are from the same company as the author (it is not prohibited but this should be clear, the best way is just to add your company to the email signature). - -The commit process is a two-step: - -1. Preliminary review. It could be done by anyone (including you). A detailed guide on how to do this [wiki.postgresql.org/wiki/Reviewing_a_Patch](https://wiki.postgresql.org/wiki/Reviewing_a_Patch) If there are no doubts reviewer sets the status to "Ready for committer". If the patch needs some changes a reviewer can set "Waiting on author". Reviewers can leave a favorable review but that's only a signal. After other reviewers join the conversation, if there are no objections then one of them will make it "Ready for committer". It's pretty normal if there is more than one reviewer. In fact the more reviewers, the better. -2. Final review by a committer. Committers are the most senior members of a community and it's a formal status. The list of committers: [wiki.postgresql.org/wiki/Committers](https://wiki.postgresql.org/wiki/Committers) As a result a committer can commit it or push back to "Waiting on author", or "Need review". - -The time and attention of committers are limited, which is why there is a two-step process. Some patches that pass the initial review won't receive reviewers' attention. In that case, they will be transferred to the next commitfest automatically. - -## When does the commitfest finish? - -CF finishes around the 1st day of the next month (i.e. November CF starts on November 1 and finishes on December 1). For any patches that do not get merged: - -- Patches with the status "Ready for committer" or "Need review" will be transferred to the next CF. -- Patches with the status "Waiting on author" are transferred automatically if the questions and proposals in the thread are not unaddressed for more than 2 weeks. Otherwise, it will be dropped from CF with "Returned with feedback". - -All these "automatic" things are actually done by commitfest managers (volunteers), so if you have any requests, doubts, or questions you can write them personally. - -Please note that hackers almost never reject patches! They just return it and the author if he is still willing and has time to work on the patch further can attach it (the same thread) to the next commitfest anytime. - -## What is feature-freeze? - -Feature-freeze is an annual event that targets patches released into a major PostgreSQL version. Feature-freeze for the "current-year" release is at the end of a March commitfest. All patches committed on and before will be released in the PG major release (in October). Everything committed after March (July, September, etc) will go into next year's major release. - -For that reason, the March Commitfest takes **10-15 days longer** than the usual 1 month, finishing in mid-April. It's important for the author to continue watching your patches and feedback until that time. Commits in early April are big! - -## Important tips - -- If you want to discuss something with hackers without a thread overhead (which makes it more difficult to read by other hackers) you can wipe out pgsql-hackers@postgresql.org from the reply list and leave only the participants you want. If you do this, you should add `[offlist]` to the message subject to prevent confusion. -- The hackers mail list receives _a lot_ of messages. It's advisable to set up mail filtering so that hackers' messages are sorted into separate places and shown as threads. But ensure that all messages that contain **your address** will NOT go into this place so that you can read all answers to your proposal among your prioritized emails. -- Patches that need minor rework like rebase or comments refine are pushed back to "Waiting on author". Therefore, almost-ready patch very often stall, waiting for a new review, during which it will need another rebase and it goes into a vicious circle. If you are certain that the change is minor and doesn't affect functionality, you can return the patch to the same state it had before (if it was "Ready-for-committer" it should be ready for committer even after rebase). It's good manners to mention this change in a hacker's thread to remove suspicions that you want to hijack the patch status. -- It’s very good to be proactive with your patch not only change something on request. Sometimes it’s hard to describe in detail what needs improving and hackers may give only approximate direction of work. If you see criticism it’s very good to answer this with possible proposals on how can you change the code, or what measurements you can do to drop possible doubts about functionality or performance. -- It’s also very nice to keep watching activity in the patch thread after it is committed. Sometimes small problems arise later and you can propose a separate fix and get a reputation as a responsible contributor. Also, you know your code better and it would be very valuable for everyone though other hackers could help you with this. - -If you're interested in contributing to PostgreSQL full time, [Supabase is hiring Postgres Core Contributors](https://boards.greenhouse.io/supabase/jobs/4307456004). - -## More Postgres resources - -- [Postgres Full Text Search vs the rest](https://supabase.com/blog/postgres-full-text-search-vs-the-rest) -- [Postgres WASM by Snaplet and Supabase](https://supabase.com/blog/postgres-wasm) -- [Choosing a Postgres Primary Key](https://supabase.com/blog/choosing-a-postgres-primary-key) -- [Implementing "seen by" functionality with Postgres](https://supabase.com/blog/seen-by-in-postgresql) -- [Partial data dumps using Postgres Row Level Security](https://supabase.com/blog/partial-postgresql-data-dumps-with-rls) -- [Postgres Views](https://supabase.com/blog/postgresql-views) -- [Realtime Postgres RLS on Supabase](https://supabase.com/blog/realtime-row-level-security-in-postgresql) diff --git a/apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx b/apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx new file mode 100644 index 00000000000..c660a5b183f --- /dev/null +++ b/apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx @@ -0,0 +1,466 @@ +--- +title: 'Fetching and caching Supabase data in Next.js 13 Server Components' +description: 'Next.js 13 introduces new data fetching and caching methods to enable React Server Components and Suspense.' +author: jonmeyers +image: TODO +thumb: TODO +tags: + - Next.js 13 + - React Server Components + - app directory + - data fetching + - caching + - async components + - Suspense +date: '2022-11-18' +toc_depth: 3 +--- + +The biggest announcement from Next.js Conf 2022 was the release of Next.js 13)[https://nextjs.org/blog/next-13](https://nextjs.org/blog/next-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. 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 at authentication and using the Next.js Auth Helpers to make authenticated Supabase queries from Server Components in a separate article. Coming soon! + +> To learn more about any of the concepts covered in this article, check out [Next.js' beta docs](https://beta.nextjs.org/docs). + +A good distinction to understand 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](https://github.com/dijonmusters/fetching-and-caching-supabase-data-in-next-js-13-server-components). + +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: + +```bash +npx create-next-app@latest --experimental-app next13 +``` + +Now we can run our app in development mode: + +```bash +npm run dev +``` + +And navigate to [http://localhost:3000](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 an optional `layout`, `loading` and `error` component. + +> Learn more about page, layout, loading and error components in the Next.js docs [TODO link to each of those docs]. + +Before we jump into fetching data, we need some data to fetch. Let’s [create a new Supabase project](https://app.supabase.com). + +Once your instance is up and running, head over to the [SQL Editor](https://app.supabase.com/project/_/sql), paste in the following snippet and click `RUN`. + +```sql +create table if not exists posts ( + id uuid default uuid_generate_v4() primary key, + created_at timestamp with time zone default timezone('utc'::text, now()) not null, + title text, + content text, + is_published boolean default false +); + +insert into posts(title, content, is_published) +values + ('My first post', 'Wow! What a great post.', true), + ('My second post', 'This one needs a little work!', false); +``` + +This will create a table called `posts`, and populate it with some example data. + +Let’s install the [`supabase-js` library](https://www.npmjs.com/package/@supabase/supabase-js) to fetch our `posts`. + +```bash +npm install @supabase/supabase-js +``` + +And add a `.env.local` file with the following environment variables. + +``` title=".env.local" +NEXT_PUBLIC_SUPABASE_URL=your-supabase-url +NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key +``` + +> The values for these can be found in [your project’s API settings](https://app.supabase.com/project/_/settings/api). + +Lastly, we need to create a Supabase client. Create a file at `utils/supabase.ts` with the following content: + +```tsx title="utils/supabase.ts" +import { createClient } from "@supabase/supabase-js"; + +export default createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! +); +``` + +Okay, let’s look at some different data fetching and caching strategies. + +# Static + +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 the equivalent of 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: + +```jsx title="app/static/page.tsx" +import supabase from "../../utils/supabase"; + +export default async function Posts() { + const { data: posts } = await supabase.from("posts").select(); + return
{JSON.stringify(posts, null, 2)}
; +} +``` + +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](https://beta.nextjs.org/docs/api-reference/file-conventions/loading). + +Let’s modify this component to render out a collection of `` components, that navigate to a dedicated page for each post. + +```tsx title="app/static/page.tsx" +import Link from "next/link"; +import supabase from "../../utils/supabase"; + +export default async function Posts() { + const { data: posts } = await supabase.from("posts").select("id, title"); + + if (!posts) { + return

No posts found.

; + } + + return posts.map((post) => ( +

+ {post.title} +

+ )); +} +``` + +> 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: + +```tsx title="app/static/[id]/page.tsx" +import supabase from "../../../utils/supabase"; +import { notFound } from "next/navigation"; + +export default async function Post({ + params: { id }, +}: { + params: { id: string }; +}) { + const { data } = await supabase.from("posts").select().match({ id }).single(); + + if (!data) { + notFound(); + } + + return
{JSON.stringify(data, null, 2)}
; +} +``` + +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 StaticPost page. + +```tsx title="app/static/[id]/page.tsx" +export async function generateStaticParams() { + const { data: posts } = await supabase.from("posts").select("id"); + + return posts?.map(({ id }) => ({ + id, + })); +} +``` + +> This is similar to `getStaticPaths` in a pages component. Learn more [here](https://beta.nextjs.org/docs/data-fetching/generating-static-params). + +The full component should look something like this: + +```tsx title="app/static/[id]/page.tsx" +import supabase from "../../../utils/supabase"; +import { notFound } from "next/navigation"; + +export async function generateStaticParams() { + const { data: posts } = await supabase.from("posts").select("id"); + + return posts?.map(({ id }) => ({ + id, + })); +} + +export default async function Post({ + params: { id }, +}: { + params: { id: string }; +}) { + const { data: post } = await supabase.from("posts").select().match({ id }).single(); + + if (!post) { + notFound(); + } + + return
{JSON.stringify(post, null, 2)}
; +} +``` + +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*. + +# Static with Revalidation + +By exporting a `revalidate` variable from our component, we can specify how many seconds we consider this data to be “fresh”. + +```tsx +export const revalidate = 60; +``` + +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: + +```tsx title="app/static-with-revalidate/page.tsx" +import Link from "next/link"; +import supabase from "../../utils/supabase"; + +export const revalidate = 60; + +export default async function Posts() { + const { data: posts } = await supabase.from("posts").select("id, title"); + + if (!posts) { + return

No posts found.

; + } + + return posts.map((post) => ( +

+ {post.title} +

+ )); +} +``` + +And the `Post` component should look like this: + +```tsx title="app/static-with-revalidate/[id]/page.tsx" +import supabase from "../../../utils/supabase"; +import { notFound } from "next/navigation"; + +export const revalidate = 60; + +export async function generateStaticParams() { + const { data: posts } = await supabase.from("posts").select("id"); + + return posts?.map(({ id }) => ({ + id, + })); +} + +export default async function Post({ + params: { id }, +}: { + params: { id: string }; +}) { + const { data: post } = await supabase.from("posts").select().match({ id }).single(); + + if (!post) { + notFound(); + } + + return
{JSON.stringify(post, null, 2)}
; +} +``` + +# Dynamic + +If we want fresh data to be fetched on every single request, we can simply set our `revalidate` value to `0`. + +**Posts Component** + +```tsx title="app/server-rendered/page.tsx" +import Link from "next/link"; +import supabase from "../../utils/supabase"; + +export const revalidate = 60; + +export default async function Posts() { + const { data: posts } = await supabase.from("posts").select("id, title"); + + if (!posts) { + return

No posts found.

; + } + + return posts.map((post) => ( +

+ {post.title} +

+ )); +} +``` + +**Single Post Component** + +```tsx title="app/server-rendered/[id]/page.tsx" +import supabase from "../../../utils/supabase"; +import { notFound } from "next/navigation"; + +export const revalidate = 60; + +export async function generateStaticParams() { + const { data: posts } = await supabase.from("posts").select("id"); + + return posts?.map(({ id }) => ({ + id, + })); +} + +export default async function Post({ + params: { id }, +}: { + params: { id: string }; +}) { + const { data: post } = await supabase.from("posts").select().match({ id }).single(); + + if (!post) { + notFound(); + } + + return
{JSON.stringify(post, null, 2)}
; +} +``` + +This is similar to exporting a `getServerSideProps` function from a component in the `pages` directory. + +# 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 manage the awesome power of websockets on your behalf — something that is not yet solved with this new 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`](https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md#example-use-in-client-components-and-hooks), 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. + +```tsx title="app/client-side/page.tsx" +"use client"; + +import { useEffect, useState } from "react"; +import supabase from "../../utils/supabase"; + +export default function ClientPosts() { + const [isLoading, setIsLoading] = useState(true); + const [posts, setPosts] = useState([]); + + useEffect(() => { + const fetchPosts = async () => { + const { data } = await supabase.from("posts").select(); + setPosts(data); + setIsLoading(false); + }; + + fetchPosts(); + }, []); + + return isLoading ? ( +

Loading

+ ) : ( +
{JSON.stringify(posts, null, 2)}
+ ); +} +``` + +But now we have loading spinners! Yuck! + +# Realtime + +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](https://app.supabase.com/project/lzdftaiyqrgmgqunxlwf/database/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** + +```tsx title="app/realtime/page.tsx" +import supabase from "../../utils/supabase"; +import RealtimePosts from "./realtime-posts"; + +export const revalidate = 0; + +export default async function Realtime() { + const { data } = await supabase.from("posts").select("*"); + return ; +} +``` + +**Client Component** + +```tsx title="app/realtime/realtime-posts.tsx" +"use client"; + +import { useEffect, useState } from "react"; +import supabase from "../../utils/supabase"; + +export default function RealtimePosts({ serverPosts }: { serverPosts: any }) { + const [posts, setPosts] = useState(serverPosts); + + useEffect(() => { + setPosts(serverPosts); + }, [serverPosts]); + + useEffect(() => { + const channel = supabase + .channel("*") + .on( + "postgres_changes", + { event: "INSERT", schema: "public", table: "posts" }, + (payload) => setPosts((posts: any) => [...posts, payload.new]) + ) + .subscribe(); + + return () => { + supabase.removeChannel(channel); + }; + }, [serverPosts]); + + return
{JSON.stringify(posts, null, 2)}
; +} +``` + +> `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 — as it also uses suspense to *suspend* the rendering of a component while fetching data. + +# Conclusion + +Next.js 13 Server Components and Suspense are awesome! Async components 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. + +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 maintainable applications. + +# More Next.js 13 Resources +- [Next.js 13 Beta Docs](https://beta.nextjs.org/docs) +- [Creating an authenticated Supabase client in Next.js 13 Server Component](https://github.com/supabase/auth-helpers/tree/main/examples/nextjs-server-components) +- [Data fetching and caching with Next.js 13 - Supabase Happy Hour #26](https://www.youtube.com/watch?v=QH0P5xZt5wY)