2
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 SaaSのオンボーディング実装 — 7日間メールシーケンス+チェックリストUIで転換ファネルを整える

2
Posted at

結論:3ファイルでオンボーディングの骨格が完成する

Supabaseに onboarding_steps テーブルを追加し、Next.js にチェックリストコンポーネントを置き、Resend でDay 0/3/6/14のメールシーケンスを動かす——これがオンボーディング実装の全体像です。

// ダッシュボードでの使用例
<OnboardingChecklist userId={session.user.id} />

この記事では Next.js App Router + Supabase + Resend + PostHog の実装を解説します。

必要なパッケージ

npm install resend posthog-js posthog-node

onboarding_stepsテーブル

CREATE TABLE onboarding_steps (
  user_id    uuid REFERENCES auth.users(id) ON DELETE CASCADE,
  step       text NOT NULL,
  completed  boolean DEFAULT false,
  updated_at timestamptz DEFAULT NOW(),
  PRIMARY KEY (user_id, step)
);

チェックリストコンポーネント

src/components/onboarding-checklist.tsx:

'use client'

import { createClient } from '@/lib/supabase/client'
import { usePostHog } from 'posthog-js/react'
import { useEffect, useState } from 'react'

const STEPS = [
  { key: 'profile_set',     label: 'プロフィールを設定する' },
  { key: 'first_content',   label: '最初のコンテンツを読む' },
  { key: 'dashboard_visit', label: 'ダッシュボードを確認する' },
]

export function OnboardingChecklist({ userId }: { userId: string }) {
  const supabase = createClient()
  const posthog  = usePostHog()
  const [completed, setCompleted] = useState<Record<string, boolean>>({})

  useEffect(() => {
    supabase
      .from('onboarding_steps')
      .select('step, completed')
      .eq('user_id', userId)
      .then(({ data }) => {
        if (!data) return
        const map: Record<string, boolean> = {}
        data.forEach((row) => { map[row.step] = row.completed })
        setCompleted(map)
      })
  }, [userId, supabase])

  const toggle = async (step: string) => {
    const next = !completed[step]
    await supabase.from('onboarding_steps').upsert({
      user_id: userId,
      step,
      completed: next,
      updated_at: new Date().toISOString(),
    })
    setCompleted((prev) => ({ ...prev, [step]: next }))

    if (next) {
      const doneCount = Object.values({ ...completed, [step]: true }).filter(Boolean).length
      posthog.capture('onboarding_step_completed', {
        step,
        total_completed: doneCount,
        total_steps: STEPS.length,
      })
    }
  }

  const doneCount = Object.values(completed).filter(Boolean).length
  const pct = Math.round((doneCount / STEPS.length) * 100)

  return (
    <div className="rounded-lg border p-4 space-y-3">
      <div className="flex items-center justify-between">
        <p className="font-medium text-sm">スタートガイド</p>
        <span className="text-xs text-muted-foreground">{pct}% 完了</span>
      </div>
      <div className="w-full bg-muted rounded-full h-1.5">
        <div
          className="bg-primary h-1.5 rounded-full transition-all"
          style={{ width: `${pct}%` }}
        />
      </div>
      <ul className="space-y-2">
        {STEPS.map(({ key, label }) => (
          <li key={key} className="flex items-center gap-2 text-sm">
            <button
              onClick={() => toggle(key)}
              className={`w-4 h-4 rounded border flex-shrink-0 transition-colors ${
                completed[key]
                  ? 'bg-primary border-primary'
                  : 'border-muted-foreground'
              }`}
              aria-label={label}
            />
            <span className={completed[key] ? 'line-through text-muted-foreground' : ''}>
              {label}
            </span>
          </li>
        ))}
      </ul>
    </div>
  )
}

7日間メールシーケンスの拡張(Day 14追加)

前回のメール自動化実装から拡張して、Day 14の再活性化メールを追加します。

// src/lib/resend.ts の templates に追加
14: (name) => ({
  subject: `${name}さん、最近いかがですか?`,
  html: `<p>登録から2週間が経ちました。</p>
    <p>まだ試せていない機能がありましたら、お気軽にご連絡ください。</p>
    <p><a href="https://masatoman.net/dashboard">ダッシュボードを開く →</a></p>`,
}),
-- Webhook受信時にDay 14も予約
INSERT INTO email_sequences (user_id, user_email, user_name, day, send_at)
VALUES ($1, $2, $3, 14, NOW() + INTERVAL '14 days')
ON CONFLICT (user_id, day) DO NOTHING;

PostHogでオンボーディング完了率を計測する

Funnels に以下を設定します:

onboarding_step_completed (profile_set)
→ onboarding_step_completed (first_content)
→ onboarding_step_completed (dashboard_visit)
→ subscription_created

このファネルでオンボーディング完了者と未完了者の転換率差を可視化できます。


設計思想・Aha!moment定義・収益導線の詳細解説はmasatoman.net の記事で公開しています。

2
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
2
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?