nextjs-typescript-tailwindcss-supabase은(는) 무엇을 하나요?
TypeScript, TailwindCSS, Supabase를 활용한 풀스택 Next.js 14 개발로 프로덕션 수준의 웹 애플리케이션을 구축합니다.
개요
이 스킬은 최신 Next.js 14(App Router)를 기반으로 TypeScript, TailwindCSS, Supabase를 통합하여 확장 가능하고 생산성 높은 풀스택 웹 앱을 만드는 방법을 제공합니다.
주요 기능
- Next.js 14 App Router: 서버 컴포넌트, 스트리밍, 병렬 라우트 등 최신 패턴 사용
- TypeScript: 정적 타입 검사로 안정성 향상
- TailwindCSS: 유틸리티 기반 CSS로 빠른 UI 개발
- Supabase: 인증, 데이터베이스, 실시간 구독, 파일 스토리지 통합
시작하기
설치
npx create-next-app@latest my-app --typescript --tailwind --eslint
cd my-app
npm install @supabase/supabase-js @supabase/ssr
Supabase 환경 변수 설정
.env.local 파일 생성:
NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
사용 예시
서버 컴포넌트에서 Supabase 데이터 조회
// app/page.tsx
import { createClient } from '@/utils/supabase/server'
export default async function Home() {
const supabase = createClient()
const { data: posts } = await supabase.from('posts').select('*')
return (
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold">게시글</h1>
{posts?.map(post => (
<div key={post.id} className="p-2 border rounded mt-2">
{post.title}
</div>
))}
</div>
)
}
클라이언트 컴포넌트에서 실시간 구독
'use client'
import { createClient } from '@/utils/supabase/client'
import { useEffect, useState } from 'react'
export default function RealtimePosts() {
const [posts, setPosts] = useState<any[]>([])
const supabase = createClient()
useEffect(() => {
const channel = supabase
.channel('posts')
.on('postgres_changes', { event: '*', schema: 'public', table: 'posts' }, payload => {
console.log('변경 감지:', payload)
})
.subscribe()
return () => { supabase.removeChannel(channel) }
}, [])
return <div>실시간 업데이트 대기 중...</div>
}
Supabase 클라이언트 설정
서버 클라이언트 (utils/supabase/server.ts)
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
export function createClient() {
const cookieStore = cookies()
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll()
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
},
},
}
)
}
클라이언트 컴포넌트용 클라이언트 (utils/supabase/client.ts)
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
}
TailwindCSS 커스터마이징
tailwind.config.ts:
import type { Config } from 'tailwindcss'
export default {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
primary: '#3b82f6',
secondary: '#10b981',
},
},
},
plugins: [],
} satisfies Config
데이터베이스 마이그레이션
Supabase CLI를 사용한 마이그레이션 예시:
-- supabase/migrations/20240101000000_create_posts.sql
create table posts (
id bigint primary key generated always as identity,
title text not null,
content text,
created_at timestamptz default now()
);
인증 예시 (Supabase Auth)
// app/auth/callback/route.ts
import { createClient } from '@/utils/supabase/server'
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
const { searchParams, origin } = new URL(request.url)
const code = searchParams.get('code')
const next = searchParams.get('next') ?? '/'
if (code) {
const supabase = createClient()
const { error } = await supabase.auth.exchangeCodeForSession(code)
if (!error) {
return NextResponse.redirect(`${origin}${next}`)
}
}
return NextResponse.redirect(`${origin}/auth/auth-code-error`)
}
지원 플랫폼
- Claude Code
- Cursor
- Codex
- 기타 AI 코드 에이전트