1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【入門】ゼロからNext.js + Supabaseでシンプルなタスク管理アプリを完成させるまでのロードマップ

Last updated at Posted at 2025-11-20

1. はじめに

この記事では Next.jsSupabase を使って、シンプルなタスク管理アプリをゼロから構築する方法を紹介します。
とりあえず動くものをサクッと作りたい」という方向けの入門記事となります。

この記事で扱うこと

  • Next.js(App Router)の基本セットアップ
  • Supabase プロジェクト作成・テーブル準備
  • タスクの追加・更新・削除・取得(CRUD)
  • UI の実装と動作確認

この記事で扱わないこと

  • Next.js の内部挙動の詳細な解説
  • Supabase の高度な機能(Edge Functions, Realtime の深掘り)
  • デザインの細かいカスタマイズ
  • 大規模アプリの構成やアーキテクチャ
  • Vercel へのデプロイ
  • ログイン機能

完成イメージ

画面収録 2025-11-21 2.01.51.gif

上記のようなタスク管理アプリを、一つひとつ手順を追いながら作っていきます。

2. Next のセットアップ

まずは npx を使って Next アプリを作成しましょう。

npx create-next-app@latest task-manager

ここでnpxコマンドが使えない人は、Node.js をインストールする必要があります。
Node.js のインストールはこちらの記事を参照ください。
【Windows】Nodejsをインストールしよう
Macにnodejsをインストールする方法

何か聞かれたら基本的に yes を選べば大丈夫です。

create-next-app@16.0.3
Ok to proceed? (y)  -> y

以下のような選択肢が出たら、Yes, use recommended defaults を選択してください。

? Would you like to use the recommended Next.js defaults? › - Use arrow-keys. Return to submit.
>   Yes, use recommended defaults
    TypeScript, ESLint, Tailwind CSS, App Router
    No, reuse previous settings
    No, customize settings

上手くいくと、以下のようなディレクトリが作成されます

./task-manager
├── app
├── eslint.config.mjs
├── next-env.d.ts
├── next.config.ts
├── node_modules
├── package-lock.json
├── package.json
├── postcss.config.mjs
├── public
├── README.md
└── tsconfig.json

アプリディレクトリに移動し、開発モードで Next を起動しましょう。

cd task-manager
npm run dev

ここで
http://localhost:3000
にアクセスすると、以下のような画面が出てくると思います。

スクリーンショット 2025-11-17 19.11.46.png

これで Next のセットアップは完了です。
お疲れ様でした!

3. Supabase のセットアップ

続いて Supabase 側のセットアップをしていきます。

アカウント・Organazation の作成

まずは Supabase のアカウントを作成します。
https://supabase.com/
にアクセスし、「Start your project」を押してください。

スクリーンショット 2025-11-17 19.14.09.png

Supabase のアカウントを持っていない場合、ここでアカウントを作成することになります。
Github アカウントでログインすることも可能です。

Organization がない場合はこちらから作成します。

スクリーンショット 2025-11-17 19.18.00.png

Organization はプロジェクトをまとめるグループのようなものです。
とりあえず 1 つ作っておけば OK です。

プロジェクトの作成

続いてプロジェクトを作成します。

スクリーンショット 2025-11-17 19.25.01.png

スクリーンショット 2025-11-17 19.26.11.png

テーブルの作成

次に、タスクを保存するテーブルを作っていきましょう。

スクリーンショット 2025-11-17 20.53.54.png

テーブル情報を入力します。
今回はこのような設計にしました。

列名 デフォルト 説明
id int8 主キーとなる数値。自動ナンバリング
created_at timestamp now() 作成日
name varchar no_name タスクの名前
progress int2 0 タスクの進捗状況(0 ~ 100)
due_date date CURRENT_DATE タスクの完了予定日

スクリーンショット 2025-11-17 21.01.07.png

データを追加してみる

insert ボタンからデータを追加してみましょう。
idcreated_atprogress はデフォルト値でいいので、namedue_date だけを設定します。

スクリーンショット 2025-11-17 21.26.59.png

スクリーンショット 2025-11-17 21.29.07.png

以下のようにデータが追加されていれば成功です!

スクリーンショット 2025-11-17 21.32.34.png

RLS policyの追加

最後に RLS policy を追加します。

RLS とは Row Level Security の略で、データベースに対し、どのユーザーがどの程度の権限を持って操作を行えるかを決めるものです。
RLS について詳しく知りたい方はこちらなどが参考になります。

Add RLS policy ボタンから追加していきます。

スクリーンショット 2025-11-17 22.16.02.png

Create policy ボタンでポリシーを追加します。

スクリーンショット 2025-11-17 22.17.53.png

今回は以下のように設定しました。

スクリーンショット 2025-11-17 22.22.19.png

この設定は、URL と KEY からアクセスしたユーザー全員が全権限を持つ設定となります。実際の運用の際は必要に応じて細かくポリシーを設定する必要があるでしょう。
ポリシーの切り分けにも先ほどと同じくこちらのサイトが参考になります。

以上で Supabase のセットアップは終了です。
お疲れ様でした!

4. Next.js と Supabaseの連携

それでは、いよいよ Next.js と Supabase の連携を作っていきます。

必要パッケージのインストール

以下のコマンドを task-manager ディレクトリで実行し、必要なパッケージをインストールします。

npm install @supabase/supabase-js
npm install @supabase/ssr

.env ファイルの作成

先ほど作った task-manager ディレクトリに、.env ファイルを作成します。

task-manager/.env
NEXT_PUBLIC_SUPABASE_URL=あなたのURL
NEXT_PUBLIC_SUPABASE_ANON_KEY=あなたのANON_KEY

URLKEY は、Supabase の画面から確認できます。

スクリーンショット 2025-11-17 21.52.57.png

黒く塗りつぶされている部分があなたの URLKEY です。

スクリーンショット 2025-11-17 21.53.57.png

Supabase クライアントをcreateする関数を作成

Next から Supabase を操作するためには、Supabase クライアントを create する必要があります。
task-manager ディレクトリにutils フォルダを作成し、以下のような構成にしてください。

./utils
└── supabase
    ├── client.ts
    └── server.ts

今作った server.tsclient.ts をそれぞれ書いていきましょう。

まずは server.ts です。

task-manager/utils/supabase/server.ts

import { createServerClient, type CookieOptions } from "@supabase/ssr";
import { cookies } from "next/headers";

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;

export const createClient = async () => {
  const cookieStore = await cookies();
  return createServerClient(
    supabaseUrl!,
    supabaseKey!,
    {
      cookies: {
        async getAll() {
          return await cookieStore.getAll()
        },
        async setAll(cookiesToSet) {
          try {
            for(const { name, value, options } of cookiesToSet) {
              await cookieStore.set(name, value, options)
            }
          } catch {
            // The `setAll` method was called from a Server Component.
            // This can be ignored if you have middleware refreshing
            // user sessions.
          }
        },
      },
    },
  );
};

次に client.ts です。

task-manager/utils/supabase/client.ts
import { createBrowserClient } from "@supabase/ssr";

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;

export const createClient = () =>
  createBrowserClient(
    supabaseUrl!,
    supabaseKey!,
  );

ここで client.tsserver.ts に 2 つの createClient 関数を作っていますが、これは Nexst のサーバーコンポーネントクライアントコンポーネントで Supabase クライアントの扱いが異なるためです。
サーバーコンポーネントとクライアントコンポーネントについてはここでは深く言及しませんが、学びたい方はこちらなどに簡潔にまとめてあります。

pageの設定

画面用のコードを書いていきます。
task-manager/app/page.tsx を以下のように書き換えましょう。

task-manager/app/page.tsx
import { createClient } from '@/utils/supabase/server'


export default async function Home() {

  const supabase = await createClient();
  const { data: tasks, error } = await supabase.from('task').select('*')

  if (error) {
    console.error(error)
    return <div>データ取得に失敗しました</div>
  }

  return (
    <div className='bg-white flex items-center justify-center h-screen'>
      {tasks?.map((task) => {
        return <div key={task.id} className='text-black'>{task.name}</div>
      })}
    </div>
  );
}

http://localhost:3000
にアクセスして、先ほど Supabase で登録したデータの name が表示されていれば成功です!

スクリーンショット 2025-11-17 23.14.13.png

ここまでできれば、Supabase 上でデータを追加・削除すると、サイト上でも追加・削除されている様子が確認できると思います。

以上でNext.js と Supabase の連携は完了です!

5. React で画面UIを作成する

本記事では React の詳細な説明は行いません。この章のプログラムの意味を知りたい方はこちら(執筆予定)の記事にまとめる予定です。

task-list ページ作成

まずは現在のタスクリストを表示する画面を作ります。
完成イメージの右側の部分です。

/task-manager/app の中に task-list というフォルダを作り、task-list/page.tsx を以下のように設定しましょう。

/task-manager/app/task-list/page.tsx
import { createClient } from '@/utils/supabase/server'

export default async function TaskListPage(){
  // Supabase のデータ取得
  const supabase = await createClient();
  const { data: tasks, error } = await supabase.from('task').select('*')
  if (error) {
    console.error(error)
    return <div>データ取得に失敗しました</div>
  }

  // テーブルを作成する
  return (<>
    {/* ページタイトル */}
    <div className='w-full'>
      <div className='border-b border-b-black mb-4'>
        <h2 className='font-bold text-black text-3xl p-2'>Task List</h2>
      </div>
    </div>
    {/* テーブル部分 */}
    <table className='border-collapse border border-gray-300 w-full bg-red'>
      <tbody>
        {/* 列名表示 */}
        <tr>
          <th className='border border-gray-300 p-2 bg-black text-white font-bold w-1/4 text-left' >Name</th>
          <th className='border border-gray-300 p-2 bg-black text-white font-bold w-1/4 text-left'>Created At</th>
          <th className='border border-gray-300 p-2 bg-black text-white font-bold w-1/4 text-left'>Due Date</th>
          <th className='border border-gray-300 p-2 bg-black text-white font-bold w-1/4 text-left'>Progress</th>
        </tr>
        {/* 各行表示 */}
        {tasks?.map((task) => {
          return <tr key={task.id} className='text-black'>
            <td className='p-2 border border-gray-300'>{task.name}</td>
            <td className='p-2 border border-gray-300'>{new Date(task.created_at).toISOString().split("T")[0]}</td>
            <td className='p-2 border border-gray-300'>{task.due_date}</td>
            <td className='p-2 border border-gray-300'>{task.progress}%</td>
          </tr>
        })}
      </tbody>
    </table>
  </>);
}

この時点で http://localhost:3000/task-list に接続して確認すると、表示が崩れているかもしれません。
Layout の設定まで進めば解消されますので、Layoutの設定が終わったら再度確認してみてください。

Tab 作成

タブ部分を作成していきます。
task-manager/app/components/tab.tsx を作成し、以下のように設定します。

/task-manager/app/components/tab.tsx
"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";

const tabs = [
  { name: "TaskList", href: "/task-list" },
];

export default function Tabs() {
  const pathname = usePathname();

  return (<>
    { tabs.map((tab) => {
      const isActive = pathname == tab.href;

      return (
        <Link 
          key = {tab.name}
          href = {tab.href}
          className={`p-2 flex items-center border-b border-gray-300 cursor-pointer hover:bg-gray-200 ${isActive && "font-bold"}`}>
          {tab.name}
        </Link>
      );
    })}
  </>);
}

新たにタブを作りたい場合は、ここの tabs に追加していきます。

Layout の設定

次に Layout の設定をします。

Layout とは、複数ページで共通のページ構成などを定義する場合に使います。

今回の場合、task-list ページと create ページはどちらも「左側にタブ、右側に main コンテンツ」という構成になっているので、その部分を Layout を用いて作成していきます。

task-manager/app/layout.tsx を以下のように編集しましょう。

task-manager/app/layout.tsx
import Tabs from "./components/tab";
import './globals.css';

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className='flex h-screen font-mono'>
        
        {/* 左側 */}
        <aside className='w-1/8 shadow-lg bg-gray-100 text-black border-r border-gray-300'>
          {/* ロゴ */}
          <div className='font-serif p-6 border-b border-gray-300 flex items-center justify-center text-xl'>
            Task Manager
          </div>
          {/* タブ */}
          <Tabs />
        </aside>

        {/* 右側 */}
        <main className='bg-gray-100 h-screen w-7/8 p-4'>
          {/* main コンテンツ */}
          {children}
        </main>
        
      </body>
    </html>
  );
}

ここで
http://localhost:3000/task-list
に接続すると、UIが作成されていることが確認できます!

スクリーンショット 2025-11-20 21.21.24.png

ここからは、タスクの追加や削除など各種機能を作っていきましょう。

6. Create 機能の実装

この章ではタスクを追加するための create ページを作成します。

task-manager/appcreate フォルダを作り、page.tsx を以下のように設定しましょう。

/task-manager/app/create/page.tsx
"use client";

import { createClient } from '@/utils/supabase/client';
import { useActionState, useState } from 'react';

export default function CreatePage(){

  const supabase = createClient();

  const [progress, setProgress] = useState(0);

  const handleSubmit = async (prevState:unknown, formData: FormData) => {
    const name = formData.get("name") as string;
    const dueDate = formData.get("due_date") as string;
    const progress = Number(formData.get("progress"));

    const { error } = await supabase
      .from("task")
      .insert({
        name,
        due_date: dueDate,
        progress,
      });

    if (error) {
      console.log(error);
      return "エラーが発生しました";
    }

    return "追加完了!";
  }

  const [ feedback, formAction, isPending ] = useActionState(handleSubmit, "");


  // テーブルを作成する
  return (<>
    {/* ページタイトル */}
    <div className='w-full'>
      <div className='border-b border-b-black mb-4'>
        <h2 className='font-bold text-black text-3xl p-2'>Create</h2>
      </div>
    </div>

    {/* 追加フォーム */}
    <form action={formAction} className='text-black'>
      {/* Name */}
      <div className="grid grid-cols-2 items-center gap-2 w-1/3 my-2">
        <label htmlFor="name" className='text-black'>Name</label>
        <input type="text" name="name" className='border border-gray-300 p-2'/>
      </div>

      {/* Due_data */}
      <div className="grid grid-cols-2 items-center gap-2 w-1/3 my-2">
        <label htmlFor="due_date" className='text-black'>Due Date</label>
        <input type="date" name="due_date" className='border border-gray-300 p-2'/>
      </div>

      {/* Progress */}
      <div className="grid grid-cols-2 items-center gap-2 w-1/3 my-2">
        <label htmlFor="progress" className='text-black'>Progress({progress})</label>
        <input type="range" name="progress" min={0} max={100} defaultValue={progress} className='border border-gray-300' onChange={(e) => setProgress(Number(e.target.value))}/>
      </div>
      
      {/* 送信ボタン */}
      <button className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 block my-4">保存</button>
      {isPending && <p>送信中...</p>}
      {!isPending && <p>{feedback}</p>}
    </form>
  </>);
}

長めなコードですが、やっていることはシンプルです。

名前、完了予定日など各項目について input を作成し、送信のタイミングで supabaseinsert する設計となっています。

途中で出てきた useActionState フックについては公式サイトがわかりやすいです。

Create タブを追加するため、先ほどの /task-manmager/app/components/tab.tsx を少し編集しましょう。

/task-manager/app/components/tab.tsx
"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";

const tabs = [
  { name: "TaskList", href: "/task-list" },
+ { name: "Create", href: "/create"},
];

export default function Tabs() {
  const pathname = usePathname();

  return (<>
    { tabs.map((tab) => {
      const isActive = pathname == tab.href;

      return (
        <Link 
          key = {tab.name}
          href = {tab.href}
          className={`p-2 flex items-center border-b border-gray-300 cursor-pointer hover:bg-gray-200 ${isActive && "font-bold"}`}>
          {tab.name}
        </Link>
      );
    })}
  </>);
}

これでタブ部分に Create が追加されたはずです!

スクリーンショット 2025-11-20 23.10.49.png

実際にタスクが追加できることも確認してみましょう。

タスクの内容を設定して「保存」ボタンを押すと...

スクリーンショット 2025-11-20 23.12.09.png

スクリーンショット 2025-11-20 23.12.35.png

追加されました!

7. Delete 機能の実装

この章では、タスク削除のボタンを作成していきます。
/task-manager/app/task-list/page.tsx を以下のように変更してみましょう。

/task-manager/app/task-list/page.tsx
import { createClient } from '@/utils/supabase/server'
+import { revalidatePath } from 'next/cache';

export default async function TaskListPage(){
  // Supabase のデータ取得
  const supabase = await createClient();
  const { data: tasks, error } = await supabase.from('task').select('*')
  if (error) {
    console.error(error)
    return <div>データ取得に失敗しました</div>
  }

+  // 指定されたIDのタスクを削除
+  const handleDelete = async (formData: FormData) => {
+    "use server"
+    const id = formData.get("id") as string;
+    const supabase = await createClient();
+    await supabase.from('task').delete().eq('id', id)
+
+    // 削除後にページを再取得
+    revalidatePath("/task-list")
+  }

  // テーブルを作成する
  return (<>
    {/* ページタイトル */}
    <div className='w-full'>
      <div className='border-b border-b-black mb-4'>
        <h2 className='font-bold text-black text-3xl p-2'>Task List</h2>
      </div>
    </div>
    {/* テーブル部分 */}
    <table className='border-collapse border border-gray-300 w-full bg-red'>
      <tbody>
        {/* 列名表示 */}
        <tr>
          <th className='border border-gray-300 p-2 bg-black text-white font-bold w-1/4 text-left' >Name</th>
          <th className='border border-gray-300 p-2 bg-black text-white font-bold w-1/4 text-left'>Created At</th>
          <th className='border border-gray-300 p-2 bg-black text-white font-bold w-1/4 text-left'>Due Date</th>
          <th className='border border-gray-300 p-2 bg-black text-white font-bold w-1/4 text-left'>Progress</th>
+          <th className='border border-gray-300 p-2 bg-black text-white font-bold w-1/4 text-left'>Delete</th>
        </tr>
        {/* 各行表示 */}
        {tasks?.map((task) => {
          return <tr key={task.id} className='text-black'>
            <td className='p-2 border border-gray-300'>{task.name}</td>
            <td className='p-2 border border-gray-300'>{new Date(task.created_at).toISOString().split("T")[0]}</td>
            <td className='p-2 border border-gray-300'>{task.due_date}</td>
            <td className='p-2 border border-gray-300'>{task.progress}%</td>
+            <td className='p-2 border border-gray-300'>
+              <form action={handleDelete}>
+                <input type="hidden" name="id" value={task.id} />
+                <button className="text-red-500 cursor-pointer">
+                  DELETE
+                </button>
+              </form>
+            </td>
          </tr>
        })}
      </tbody>
    </table>
  </>);
}

handleDelete で出てきた use server は、この関数が「サーバー側で実行される関数ですよ」という宣言です。

なぜ必要なのかはここでは言及しませんが、こちらなどで解説されています。

これで削除機能は完成です!
試してみましょう。

delete.gif

上手くいってますね!

8. Update 機能の実装

最後に、タスク変更の機能を作っていきます。
まずは /task-manager/app/details/page.tsx を作成し、以下のようにします。

/task-manager/app/details/page.tsx
"use client";

import { createClient } from "@/utils/supabase/client";
import { useState, useActionState } from "react";
import { useSearchParams } from "next/navigation";
import { useRouter } from "next/navigation";

export default function DetailsPage() {

  // URLパラメータから、各値をとってくる
  const query = useSearchParams();
  const [progress, setProgress] = useState<number>(Number(query.get('progress')));
  const defaultName = query.get('name') ?? "";
  const defaultDue_date = query.get('due_date') ?? "2000-01-01";
  const id = query.get('id');

  // 指定されたIDのタスクを更新
  const handleUpdate = async (prevState:unknown, formData: FormData) => {
    const name = formData.get("name") as string;
    const due_date = formData.get("due_date") as string;

    const supabase = createClient();
    await supabase.from('task').update({name, due_date, progress}).eq("id", id);

    // 変更が終わったらタスクリストに戻る
    const router = useRouter();
    router.push('/task-list')

    return "変更完了!";
  }

  const [ feedback, formAction, isPending ] = useActionState(handleUpdate, "");
  
  return (<>
  {/* Updateフォーム */}
  <form action={formAction} className='text-black'>
    {/* Name */}
    <div className="grid grid-cols-2 items-center gap-2 w-1/3 my-2">
      <label htmlFor="name" className='text-black'>Name</label>
      <input type="text" name="name" className='border border-gray-300 p-2' defaultValue={defaultName}/>
    </div>

    {/* Due_data */}
    <div className="grid grid-cols-2 items-center gap-2 w-1/3 my-2">
      <label htmlFor="due_date" className='text-black'>Due Date</label>
      <input type="date" name="due_date" className='border border-gray-300 p-2' defaultValue={defaultDue_date}/>
    </div>

    {/* Progress */}
    <div className="grid grid-cols-2 items-center gap-2 w-1/3 my-2">
      <label htmlFor="progress" className='text-black'>Progress({progress})</label>
      <input type="range" name="progress" min={0} max={100} defaultValue={progress} className='border border-gray-300' onChange={(e) => setProgress(Number(e.target.value))}/>
    </div>
    
    {/* 送信ボタン */}
    <button className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 block my-4">保存</button>
    {isPending && <p>送信中...</p>}
    {!isPending && <p>{feedback}</p>}
  </form>
  </>);
}

一見複雑そうですが、処理としてはほとんど create ページと同じです。
デフォルト値の設定は URL のクエリパラメータから送るようにしています。

クエリパラメータとは?
クエリパラメータとは、URL に付帯してやり取りされる情報のことです。例えば、
http://hogehoge.com/?name=yamada
というリンクがあったとき、クエリパラメータは「name=yamada」の部分です。

今回のコードでは、
http://localhost:3000/details?name=勉強&due_date=2025-01-01&progress=15
のように設定することで、details ページに情報を送っています。

最後に、task-list ページ → details ページへの遷移を作れば完成です!

/task-manager/app/task-list/page.tsx を少し編集しましょう。

/task-manager/app/task-list/page.tsx

import { createClient } from '@/utils/supabase/server'
import { revalidatePath } from 'next/cache';
import Link from 'next/link';

export default async function TaskListPage(){
  // Supabase のデータ取得
  const supabase = await createClient();
  const { data: tasks, error } = await supabase.from('task').select('*')
  if (error) {
    console.error(error)
    return <div>データ取得に失敗しました</div>
  }

  // 指定されたIDのタスクを削除
  const handleDelete = async (formData: FormData) => {
    "use server"
    const id = formData.get("id") as string

    const supabase = await createClient();
    await supabase.from('task').delete().eq('id', id)

    // 削除後にページを再取得
    revalidatePath("/task-list")
  }

  // テーブルを作成する
  return (<>
    {/* ページタイトル */}
    <div className='w-full'>
      <div className='border-b border-b-black mb-4'>
        <h2 className='font-bold text-black text-3xl p-2'>Task List</h2>
      </div>
    </div>
    {/* テーブル部分 */}
    <table className='border-collapse border border-gray-300 w-full bg-red'>
      <tbody>
        {/* 列名表示 */}
        <tr>
          <th className='border border-gray-300 p-2 bg-black text-white font-bold w-1/4 text-left' >Name</th>
          <th className='border border-gray-300 p-2 bg-black text-white font-bold w-1/4 text-left'>Created At</th>
          <th className='border border-gray-300 p-2 bg-black text-white font-bold w-1/4 text-left'>Due Date</th>
          <th className='border border-gray-300 p-2 bg-black text-white font-bold w-1/4 text-left'>Progress</th>
          <th className='border border-gray-300 p-2 bg-black text-white font-bold w-1/4 text-left'>Delete</th>
        </tr>
        {/* 各行表示 */}
        {tasks?.map((task) => {
          return <tr key={task.id} className='text-black'>
            <td className='p-2 border border-gray-300'>
+              <Link href={{
+                pathname: '/details/',
+                query:{
+                  id: task.id,
+                  name: task.name,
+                  due_date: task.due_date,
+                  progress: task.progress,
+                }
+              }}>
+                {task.name}
+              </Link>
            </td>
            <td className='p-2 border border-gray-300'>{new Date(task.created_at).toISOString().split("T")[0]}</td>
            <td className='p-2 border border-gray-300'>{task.due_date}</td>
            <td className='p-2 border border-gray-300'>{task.progress}%</td>
            <td className='p-2 border border-gray-300'>
            <form action={handleDelete}>
              <input type="hidden" name="id" value={task.id} />
                <button className="text-red-500 cursor-pointer">
                  DELETE
                </button>
              </form>
            </td>
          </tr>
        })}
      </tbody>
    </table>
  </>);
}

お疲れ様でした!!!
以上でシンプルなタスク管理アプリは完成となります!

http://localhost:3000/task-list
に接続すると、ちゃんと動いていることが確認できるはずです。

9. おわりに

今回は、Next.js と Supabase を使って、シンプルなタスク管理アプリを作成しました。

入門編ということで、あえて複雑な設定や高度な実装は避けましたが、必要になったときに深掘りできるよう参考リンクを多めに載せています。
基礎的なセットアップから実際に動くアプリになるまでを一通り体験して、「あ、Next.js と Supabaseって意外と簡単に動かせるんだ」と思ってもらえたら嬉しいです。

この続きを発展させれば、認証、リアルタイム処理、デプロイなど、もっと実用的なアプリにも挑戦できます。ぜひあなたのサービス作りにも活かしてみてください!

10. 参考文献

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?