この
個人の
とは
この
- 会員だけが
閲覧できる ブログ記事の 公開 - 有料記事の
公開 (会員だけ購入可能) - 今後
コメント機能を 実装するので、 その ときに ユーザ名と プロフィール画像を 表示 - スポンサー機能
- UI の
カスタマイズを 可能に する (何が 良いかは 考え中)
Clerk を 選んだ 理由
認証・認可の
決め手と
そして、
ところが、
な
実装方 法
具体例と
公式ドキュメントに<SignIn />page.tsx に
import { SignIn } from "@clerk/nextjs";
const Page = () => ( <SignIn />);
export default Page;これでも
"use client";
import { SignIn as ClerkSignIn } from "@clerk/nextjs";import { dark } from "@clerk/themes";import { useTheme } from "@kkhys/ui";import React from "react";
export const SignIn = () => { const { theme = "system" } = useTheme();
return ( <ClerkSignIn appearance={{ baseTheme: theme === "dark" ? dark : undefined, }} /> );};余談だが、
な
Inngest で Webhook を 処理する
Clerk では
Clerk では
その後、
import { createUser, deleteUserByClerkId } from "#/app/(auth)/_lib/actions";import { getUserByClerkId } from "#/app/(auth)/_lib/queries";import type { ClerkWebhookUser } from "#/app/(auth)/_types";import { inngest } from "#/lib/inngest";
export const syncCreatedUser = inngest.createFunction( { id: "sync-created-user-from-clerk" }, { event: "clerk/user.created" }, async ({ event }) => { const { id, email_addresses, primary_email_address_id } = event.data; const email = getPrimaryEmailAddress({ email_addresses, primary_email_address_id, });
console.log("Syncing created user", { id, email });
await createUser({ clerkId: id }); },);
export const syncUpdatedUser = inngest.createFunction( { id: "sync-updated-user-from-clerk" }, { event: "clerk/user.updated" }, async ({ event }) => { const { id, email_addresses, primary_email_address_id } = event.data; const email = getPrimaryEmailAddress({ email_addresses, primary_email_address_id, });
const user = await getUserByClerkId(id);
if (!user) { await createUser({ clerkId: id }); }
console.log("Syncing updated user", { id, email }); },);
export const syncDeletedUser = inngest.createFunction( { id: "sync-deleted-user-from-clerk" }, { event: "clerk/user.deleted" }, async ({ event }) => { const { id, email_addresses, primary_email_address_id } = event.data; const email = getPrimaryEmailAddress({ email_addresses, primary_email_address_id, });
console.log("Syncing deleted user", { id, email });
await deleteUserByClerkId(id); },);
const getPrimaryEmailAddress = ({ email_addresses, primary_email_address_id,}: Pick< ClerkWebhookUser["data"], "email_addresses" | "primary_email_address_id">) => email_addresses.find(({ id }) => id === primary_email_address_id) ?.email_address ?? "no email";具体的には
syncCreatedUser: user テーブルに新規レコードを 追加 syncUpdatedUser: user テーブルに対象ユーザの レコードが なければ 追加。 現状は 更新項目が ないため処理なし syncDeletedUser: user テーブルのdeletedAtに実行日時を 記録 (論理削除)
DB は
user テーブルの
import { sql } from "drizzle-orm";import { pgTable } from "drizzle-orm/pg-core";
export const User = pgTable("user", (t) => ({ id: t.uuid().notNull().primaryKey().defaultRandom(), clerkId: t.varchar({ length: 255 }).notNull().unique(), deletedAt: t.timestamp(), createdAt: t.timestamp().defaultNow().notNull(), updatedAt: t .timestamp() .notNull() .$onUpdateFn(() => sql`now()`),}));最後に
import { serve } from "inngest/next";import { syncCreatedUser, syncDeletedUser, syncUpdatedUser,} from "#/app/(auth)/_lib";import { inngest } from "#/lib/inngest";
export const { GET, POST, PUT } = serve({ client: inngest, functions: [syncCreatedUser, syncUpdatedUser, syncDeletedUser],});一見すると、
- 並行性
(Concurrency)を 制御し、 イベントが 急増した 際も API や データベースへの 負担を 効果的に 抑える ことができる - 単一の
イベントから 複数の 処理を 同時に 開始する 機能 (ファンアウト)を 備えている ため、 ワークフローの 柔軟性が 向上する - 特定の
処理を 一定 時間後に 実行する 仕組みを 簡単に 構築できる - 同じイベントの
重複実行を 防ぐ デバウンス機能が ある
こういった
さい ごに
ここまで
とは
もっとも、