master-project
CHAPTER 20 / 50
읽기 약 2분
FUNCTION
실시간 알림: 토스트 + 벨 아이콘
핵심 개념
sonner 토스트 + Supabase Realtime + 벨 아이콘 + 읽음 처리 — 사용자 피드백·재참여 기능.
본문
sonner 토스트 셋업
pnpm add sonner// src/app/layout.tsx
import { Toaster } from 'sonner'
<Toaster richColors position="top-right" />// 사용
import { toast } from 'sonner'
toast.success('생성 완료!')
toast.error('실패: 잠시 후 재시도')
toast.loading('생성 중...')
toast.promise(saveData(), {
loading: '저장 중...',
success: '저장 완료',
error: '저장 실패',
})notifications 테이블
create table notifications (
id uuid primary key default gen_random_uuid(),
user_id uuid references users(id) on delete cascade,
type text not null, -- 'generation_done', 'subscription_renewed', etc.
title text not null,
body text,
data jsonb,
read_at timestamptz,
created_at timestamptz default now()
);
create index idx_notif_user on notifications(user_id, read_at, created_at desc);
alter table notifications enable row level security;
create policy "users own" on notifications for all using (auth.uid() = user_id);벨 아이콘 + 카운트
// src/components/notification-bell.tsx
'use client'
import { useEffect, useState } from 'react'
import { Bell } from 'lucide-react'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { createClient } from '@/lib/supabase/client'
import { formatDistanceToNow } from 'date-fns'
import { ko } from 'date-fns/locale'
export function NotificationBell() {
const supabase = createClient()
const [notifs, setNotifs] = useState<any[]>([])
const unreadCount = notifs.filter(n => !n.read_at).length
useEffect(() => {
const load = async () => {
const { data } = await supabase
.from('notifications')
.select('*')
.order('created_at', { ascending: false })
.limit(20)
setNotifs(data ?? [])
}
load()
// 실시간 구독
const ch = supabase.channel('notifications')
.on('postgres_changes',
{ event: 'INSERT', schema: 'public', table: 'notifications' },
payload => setNotifs(prev => [payload.new, ...prev])
)
.subscribe()
return () => { supabase.removeChannel(ch) }
}, [])
const markAllRead = async () => {
await supabase.from('notifications').update({ read_at: new Date().toISOString() }).is('read_at', null)
setNotifs(prev => prev.map(n => ({ ...n, read_at: new Date().toISOString() })))
}
return (
<Popover>
<PopoverTrigger className="relative">
<Bell className="h-5 w-5" />
{unreadCount > 0 && (
<span className="absolute -top-1 -right-1 h-4 w-4 bg-red-500 text-white text-[10px] rounded-full flex items-center justify-center">
{unreadCount}
</span>
)}
</PopoverTrigger>
<PopoverContent className="w-80 p-0">
<div className="flex justify-between p-3 border-b">
<h3 className="font-semibold">알림</h3>
{unreadCount > 0 && (
<button onClick={markAllRead} className="text-xs text-primary">모두 읽음</button>
)}
</div>
<ul className="max-h-80 overflow-y-auto">
{notifs.length === 0 ? (
<li className="p-6 text-center text-sm text-muted-foreground">알림이 없습니다</li>
) : (
notifs.map(n => (
<li key={n.id} className={`p-3 border-b ${!n.read_at ? 'bg-primary/5' : ''}`}>
<p className="font-medium text-sm">{n.title}</p>
{n.body && <p className="text-xs text-muted-foreground mt-1">{n.body}</p>}
<time className="text-[10px] text-muted-foreground">
{formatDistanceToNow(new Date(n.created_at), { locale: ko, addSuffix: true })}
</time>
</li>
))
)}
</ul>
</PopoverContent>
</Popover>
)
}알림 발송 (Server Action)
// 생성 완료 시 알림
await supabase.from('notifications').insert({
user_id: userId,
type: 'generation_done',
title: 'AI 카피 생성 완료',
body: '결과를 확인해보세요',
data: { generation_id: gen.id },
})Push 알림 (Web Push)
# 큰 서비스 단계
pnpm add web-push
# Service Worker 등록 → Push subscription 저장
# 백엔드에서 webpush.sendNotification()이메일 알림 (Resend)
// 큰 이벤트 (결제·구독 만료) 이메일
import { Resend } from 'resend'
const resend = new Resend(process.env.RESEND_API_KEY)
await resend.emails.send({
from: 'My SaaS <noreply@mysaas.com>',
to: user.email,
subject: '구독이 갱신되었습니다',
html: '<p>...</p>',
})다음 챕터
CH.21 "설정 페이지: 프로필/알림/결제".
AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude
무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6
내 마스터 프로젝트의 실시간 알림 구현 부분을 분석해서 실전 적용 + 개선 우선순위 3가지를 알려줘.
ChatGPT
무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro
실시간 알림 구현 관련 모범 사례·안티패턴 5개를 비교 분석해서 실전 적용를 위한 추천 방안을 알려줘.
Gemini
무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro
내 프로젝트 전체에서 실시간 알림 구현 최적화 가능 위치와 리스크를 보고해줘.
Grok
무료: Grok 4.1 / SuperGrok $30/mo
2026년 한국 1인 개발자 시장의 실시간 알림 구현 트렌드와 차별화 포인트를 정리해줘.
⭐ 이것만 기억하세요
실시간 알림: 토스트 + 벨 아이콘은 이 3가지만 확실히 잡으세요
1.sonner = 즉각 피드백 (toast)
2.Supabase Realtime = 실시간 알림 표시
3.다음 챕터에서 설정 페이지
공유하기
진행도 20 / 50