Next.js v13をSupabaseのデータフェッチと一緒に理解する

2023.01.06

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは。データアナリティクス事業本部 サービスソリューション部の北川です。

Supabaseとは、firebaseの代替サービスとも言われるBaasです。データベースにRDBを採用しているのが特徴です。

Next.js v13を学んでいる時に以下の記事を見つけました。Next.jsプロジェクトから、Supabaseのデータ取得を行っており、わかりやすかったです。

Next.js v13のインプットにも、Supabaseの入門にも役に立つチュートリアルだと思います。

今回は、こちらの記事を元にNext.js v13の機能を、v12と比較したりしながら試してみました。

Supabase側の設定

Supabaseにログインし、新しいプロジェクトを作成します。

左サイドバーから[SQL Editor]に移動し、以下のコードをエディタに貼り付けます。右下の[RUN]ボタンから、クエリを実行します。

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
);

insert into posts(title, content)
values
  ('My first post', 'Wow! What a great post.'),
  ('My second post', 'This one needs a little work!');

[Table Editor]から、新規にデータが追加されているのが確認できます。

Next.js側の設定

Next.jsのプロジェクトを作成します。

npx create-next-app@latest --experimental-app supabase_next13 —ts

ちなみに、Next.jsのアプリを立ち上げた時の初期デザインが変わっていました。

yarn dev

Supabaseクライアントの作成

Supabaseをインストールします。

yarn add @supabase/supabase-js

.env.localを作成し、環境変数を追加します。 キーとURLはSupabaseの[Project Settings]→「API」から確認することができます。

NEXT_PUBLIC_SUPABASE_URL=<Project URL>
NEXT_PUBLIC_SUPABASE_ANON_KEY=<Project key>

/utils/supabase.tsを作成し、Supabaseクライアントを初期化します。

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!
);

React Server Components

Next.js v13では、appディレクトリ配下がルートになります。また、ファイルをページとして機能させるには、page.tsxを作成します。

また、getServerSidePropsgetStaticPropsは廃止され、appフォルダ配下のコンポーネントをデフォルトでReact Server Componentsとして扱います。そのため、コンポーネント内でasync、awaitの使用が可能になります。

app/static/page.tsxを作成し、以下を記述します。

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 <p>No posts found.</p>;
  }

  return posts.map((post) => (
    <p key={post.id}>
      <Link href={`/static/${post.id}`}>{post.title}</Link>
    </p>
  ));
}

generateStaticParams

次に、記事の中身を見る個別ページを実装します。

ダイナミックルーティングにおけるパスのリストの生成は、generateStaticParamsを使用します。以前は、getStaticPath/getStaticPropsを使用していましたね。返り値も変わっています。

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 <pre>{JSON.stringify(post, null, 2)}</pre>;
}

ブログを実装することができました。v12と比べて、記述がシンプルになったと思います。

  • page.tsx

  • [id]/page.tsx

revalidateの設定

現状は、Supabaseに新規で作成した記事が反映されないので、再検証できるようにします。

Next.js v12までは、ISRはgetStaticPropsにrevalidateを指定して実装していました。

  • v12以前の記述
    export async function getStaticProps() {
      const res = await fetch('https://.../posts')
      const posts = await res.json()
    
      return {
        props: {
          posts,
        },
        revalidate: 10, // 再検証する
      }
    }

Next.js v13では、appディレクトリ配下のpage.tsx、layout.tsxページでrevalidate変数を定義し、exportするだけで機能します。

page.tsx、[id]/page.tsxをそれぞれ以下のように変更します。

import Link from "next/link";
import supabase from "../../utils/supabase";

export const revalidate = 60;

export default async function Posts() {
  /*
    ...
  */
}
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 };
}) {
  /*
  ...
  */
}

これだけで、ISRを実装することができました。リクエストごとに毎回新しいデータを取得したい場合は、revalidateの値を0にすればいいだけです。

export const revalidate = 0

まとめ

今回は、SupabaseのNext.jsを使用したブログ実装を試してみました。v13は非常に大きなアップデートだったので、まだまだドキュメントを見ながらインプット中です。ちなみに、Next.jsは既にv13.1も発表されています。

ではまた。