diff --git a/Complete Next.js-Supabase Development Guide 2025 b/Complete Next.js-Supabase Development Guide 2025 deleted file mode 100644 index 0d3ee14..0000000 --- a/Complete Next.js-Supabase Development Guide 2025 +++ /dev/null @@ -1,1535 +0,0 @@ -# Complete Next.js + Supabase Development Guide 2025 - -**A comprehensive, standalone guide for building production-ready applications with Next.js 15 and Supabase** - -This guide covers everything from project setup to deployment, including authentication, database operations, TypeScript integration, and modern best practices. Perfect for beginners and as a reference for experienced developers. - ---- - -## Table of Contents -1. [Prerequisites & Initial Setup](#prerequisites--initial-setup) -2. [Project Creation & Configuration](#project-creation--configuration) -3. [Supabase Integration](#supabase-integration) -4. [Project Structure](#project-structure) -5. [Database Schema & Security](#database-schema--security) -6. [Authentication Implementation](#authentication-implementation) -7. [CRUD Operations](#crud-operations) -8. [UI Components & Styling](#ui-components--styling) -9. [Error Handling & Loading States](#error-handling--loading-states) -10. [TypeScript Configuration](#typescript-configuration) -11. [Deployment & Production](#deployment--production) -12. [Complete Code Examples](#complete-code-examples) - ---- - -## Prerequisites & Initial Setup - -### System Requirements -- **Node.js**: 18.17.0 or higher -- **npm**: 9.0.0 or higher (or yarn/pnpm equivalent) -- **Git**: For version control -- **Code Editor**: VS Code recommended - -### Version Requirements (2025) -- **Next.js**: 15.x (latest) -- **React**: 19 RC -- **Supabase JS**: 2.50.1+ -- **TypeScript**: 5.1.3+ -- **Tailwind CSS**: 4.x - -### Supabase Account Setup -1. Go to [supabase.com](https://supabase.com) -2. Sign up for a free account -3. Create a new project -4. Choose a database password (save it securely) -5. Wait for project initialization (~2 minutes) - ---- - -## Project Creation & Configuration - -### 1. Create Next.js Application - -```bash -npx create-next-app@latest my-supabase-app -``` - -**Choose these options when prompted:** -``` -✓ Would you like to use TypeScript? → Yes -✓ Would you like to use ESLint? → Yes -✓ Would you like to use Tailwind CSS? → Yes -✓ Would you like to use `src/` directory? → Yes -✓ Would you like to use App Router? → Yes -✓ Would you like to use Turbopack for `next dev`? → Yes -✓ Would you like to customize the default import alias (@/*)? → Yes -``` - -### 2. Navigate to Project & Install Dependencies - -```bash -cd my-supabase-app -npm install @supabase/supabase-js @supabase/ssr -``` - -**Important**: Don't install `@supabase/auth-helpers-nextjs` - it's deprecated. Use `@supabase/ssr` instead. - -### 3. Environment Variables Setup - -Create `.env.local` in your project root: - -```env -# Supabase Configuration -NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url -NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key - -# Optional: For admin operations (keep secure, never expose to client) -SUPABASE_SERVICE_ROLE_KEY=your_service_role_key -``` - -**To find your Supabase credentials:** -1. Go to your Supabase project dashboard -2. Click on "Settings" → "API" -3. Copy "Project URL" and "anon public" key - ---- - -## Supabase Integration - -### 1. Client-Side Supabase Client - -Create `src/lib/supabase/client.ts`: - -```typescript -import { createBrowserClient } from '@supabase/ssr' - -export function createClient() { - return createBrowserClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! - ) -} -``` - -### 2. Server-Side Supabase Client - -Create `src/lib/supabase/server.ts`: - -```typescript -import { createServerClient } from '@supabase/ssr' -import { cookies } from 'next/headers' - -export async function createClient() { - const cookieStore = await cookies() - - return createServerClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, - { - cookies: { - getAll() { - return cookieStore.getAll() - }, - setAll(cookiesToSet) { - try { - cookiesToSet.forEach(({ name, value, options }) => { - cookieStore.set(name, value, options) - }) - } catch { - // Server component case - cookies can't be set - } - }, - }, - } - ) -} -``` - -### 3. Session Management Middleware - -Create `middleware.ts` in your project root (same level as `package.json`): - -```typescript -import { type NextRequest } from 'next/server' -import { updateSession } from '@/lib/supabase/middleware' - -export async function middleware(request: NextRequest) { - return await updateSession(request) -} - -export const config = { - matcher: [ - '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)', - ], -} -``` - -Create `src/lib/supabase/middleware.ts`: - -```typescript -import { createServerClient } from '@supabase/ssr' -import { NextResponse, type NextRequest } from 'next/server' - -export async function updateSession(request: NextRequest) { - let supabaseResponse = NextResponse.next({ - request, - }) - - const supabase = createServerClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, - { - cookies: { - getAll() { - return request.cookies.getAll() - }, - setAll(cookiesToSet) { - cookiesToSet.forEach(({ name, value, options }) => { - request.cookies.set(name, value) - supabaseResponse.cookies.set(name, value, options) - }) - }, - }, - } - ) - - // Refresh session if needed - const { data: { user } } = await supabase.auth.getUser() - - return supabaseResponse -} -``` - ---- - -## Project Structure - -### Recommended File Organization - -``` -my-supabase-app/ -├── src/ -│ ├── app/ # App Router (Next.js 13+) -│ │ ├── globals.css # Global styles -│ │ ├── layout.tsx # Root layout -│ │ ├── page.tsx # Home page -│ │ ├── loading.tsx # Global loading UI -│ │ ├── error.tsx # Global error boundary -│ │ ├── not-found.tsx # 404 page -│ │ │ -│ │ ├── (auth)/ # Route groups for organization -│ │ │ ├── login/ -│ │ │ │ ├── page.tsx -│ │ │ │ ├── actions.ts # Server actions -│ │ │ │ └── loading.tsx -│ │ │ └── signup/ -│ │ │ └── page.tsx -│ │ │ -│ │ ├── dashboard/ # Protected routes -│ │ │ ├── layout.tsx # Dashboard layout -│ │ │ ├── page.tsx -│ │ │ ├── actions.ts # Server actions for dashboard -│ │ │ ├── loading.tsx -│ │ │ ├── error.tsx -│ │ │ └── tasks/ -│ │ │ └── page.tsx -│ │ │ -│ │ └── api/ # API routes -│ │ └── auth/ -│ │ └── confirm/ -│ │ └── route.ts # Email confirmation -│ │ -│ ├── components/ # Reusable components -│ │ ├── ui/ # Basic UI components (shadcn/ui) -│ │ │ ├── button.tsx -│ │ │ ├── card.tsx -│ │ │ ├── input.tsx -│ │ │ └── skeleton.tsx -│ │ ├── layout/ # Layout components -│ │ │ ├── header.tsx -│ │ │ ├── nav.tsx -│ │ │ └── footer.tsx -│ │ └── features/ # Feature-specific components -│ │ ├── auth/ -│ │ │ ├── login-form.tsx -│ │ │ └── logout-button.tsx -│ │ └── tasks/ -│ │ ├── task-list.tsx -│ │ └── task-form.tsx -│ │ -│ ├── lib/ # Utility libraries -│ │ ├── utils.ts # General utilities -│ │ ├── dal.ts # Data Access Layer (security) -│ │ ├── validations.ts # Schema validations -│ │ └── supabase/ # Supabase configuration -│ │ ├── client.ts # Client-side client -│ │ ├── server.ts # Server-side client -│ │ └── middleware.ts # Session management -│ │ -│ ├── hooks/ # Custom React hooks -│ │ ├── use-auth.ts -│ │ └── use-local-storage.ts -│ │ -│ └── types/ # TypeScript definitions -│ └── database.types.ts # Generated Supabase types -│ -├── middleware.ts # Next.js middleware -├── next.config.ts # Next.js configuration -├── tailwind.config.ts # Tailwind configuration -├── tsconfig.json # TypeScript configuration -└── package.json -``` - ---- - -## Database Schema & Security - -### 1. Create Tables in Supabase - -Go to your Supabase project → SQL Editor → New Query, and run: - -```sql --- Create the tasks table -CREATE TABLE tasks ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - title TEXT NOT NULL, - description TEXT, - completed BOOLEAN DEFAULT FALSE, - user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, - created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc'::text, NOW()) NOT NULL, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc'::text, NOW()) NOT NULL -); - --- Enable Row Level Security (RLS) -ALTER TABLE tasks ENABLE ROW LEVEL SECURITY; - --- Create security policies -CREATE POLICY "Users can view own tasks" ON tasks - FOR SELECT USING (auth.uid() = user_id); - -CREATE POLICY "Users can insert own tasks" ON tasks - FOR INSERT WITH CHECK (auth.uid() = user_id); - -CREATE POLICY "Users can update own tasks" ON tasks - FOR UPDATE USING (auth.uid() = user_id); - -CREATE POLICY "Users can delete own tasks" ON tasks - FOR DELETE USING (auth.uid() = user_id); - --- Create indexes for performance -CREATE INDEX idx_tasks_user_id ON tasks(user_id); -CREATE INDEX idx_tasks_created_at ON tasks(created_at DESC); - --- Add updated_at trigger -CREATE OR REPLACE FUNCTION update_updated_at_column() -RETURNS TRIGGER AS $$ -BEGIN - NEW.updated_at = TIMEZONE('utc'::text, NOW()); - RETURN NEW; -END; -$$ language 'plpgsql'; - -CREATE TRIGGER update_tasks_updated_at - BEFORE UPDATE ON tasks - FOR EACH ROW - EXECUTE FUNCTION update_updated_at_column(); -``` - -### 2. Generate TypeScript Types - -Run this command to generate types from your database: - -```bash -npx supabase gen types typescript --project-id your_project_id > src/types/database.types.ts -``` - -Replace `your_project_id` with your actual Supabase project ID (found in Project Settings → General). - ---- - -## Authentication Implementation - -### 1. Data Access Layer (Security) - -Create `src/lib/dal.ts`: - -```typescript -import 'server-only' -import { redirect } from 'next/navigation' -import { createClient } from '@/lib/supabase/server' -import { cache } from 'react' - -export const verifySession = cache(async () => { - const supabase = await createClient() - const { data: { user }, error } = await supabase.auth.getUser() - - if (error || !user) { - redirect('/login') - } - - return { isAuth: true, user } -}) - -export const getUser = cache(async () => { - const session = await verifySession() - if (!session) return null - - return session.user -}) -``` - -### 2. Login Page - -Create `src/app/(auth)/login/page.tsx`: - -```typescript -import { login, signup } from './actions' - -export default function LoginPage() { - return ( -
-
-
-

- Sign in to your account -

-
-
-
-
- - -
-
- - -
-
-
- - -
-
-
-
- ) -} -``` - -### 3. Authentication Server Actions - -Create `src/app/(auth)/login/actions.ts`: - -```typescript -'use server' - -import { revalidatePath } from 'next/cache' -import { redirect } from 'next/navigation' -import { createClient } from '@/lib/supabase/server' - -export async function login(formData: FormData) { - const supabase = await createClient() - - const data = { - email: formData.get('email') as string, - password: formData.get('password') as string, - } - - const { error } = await supabase.auth.signInWithPassword(data) - - if (error) { - console.error('Login error:', error) - redirect('/login?error=Invalid credentials') - } - - revalidatePath('/', 'layout') - redirect('/dashboard') -} - -export async function signup(formData: FormData) { - const supabase = await createClient() - - const data = { - email: formData.get('email') as string, - password: formData.get('password') as string, - } - - const { error } = await supabase.auth.signUp(data) - - if (error) { - console.error('Signup error:', error) - redirect('/login?error=Could not create account') - } - - revalidatePath('/', 'layout') - redirect('/login?message=Check your email to confirm your account') -} - -export async function logout() { - const supabase = await createClient() - await supabase.auth.signOut() - revalidatePath('/', 'layout') - redirect('/login') -} -``` - -### 4. Email Confirmation Route - -Create `src/app/api/auth/confirm/route.ts`: - -```typescript -import { createClient } from '@/lib/supabase/server' -import { NextRequest, NextResponse } from 'next/server' - -export async function GET(request: NextRequest) { - const { searchParams } = new URL(request.url) - const token_hash = searchParams.get('token_hash') - const type = searchParams.get('type') - const next = searchParams.get('next') ?? '/dashboard' - - if (token_hash && type) { - const supabase = await createClient() - - const { error } = await supabase.auth.verifyOtp({ - type: type as any, - token_hash, - }) - - if (!error) { - return NextResponse.redirect(new URL(next, request.url)) - } - } - - // Redirect to error page if confirmation fails - return NextResponse.redirect(new URL('/login?error=Could not confirm account', request.url)) -} -``` - ---- - -## CRUD Operations - -### 1. Dashboard Page with Data Fetching - -Create `src/app/dashboard/page.tsx`: - -```typescript -import { verifySession } from '@/lib/dal' -import { createClient } from '@/lib/supabase/server' -import TaskList from '@/components/features/tasks/task-list' -import TaskForm from '@/components/features/tasks/task-form' -import LogoutButton from '@/components/features/auth/logout-button' - -export default async function Dashboard() { - const session = await verifySession() - const supabase = await createClient() - - const { data: tasks, error } = await supabase - .from('tasks') - .select('*') - .order('created_at', { ascending: false }) - - if (error) { - console.error('Error fetching tasks:', error) - return
Error loading tasks
- } - - return ( -
-
-
-
-
-

- Dashboard -

-

Welcome back, {session.user.email}

-
- -
-
-
-
- -
-
- -
-
-
-
-
-
- ) -} -``` - -### 2. Server Actions for CRUD Operations - -Create `src/app/dashboard/actions.ts`: - -```typescript -'use server' - -import { createClient } from '@/lib/supabase/server' -import { revalidatePath } from 'next/cache' -import { redirect } from 'next/navigation' - -export async function createTask(formData: FormData) { - const supabase = await createClient() - - const { data: { user } } = await supabase.auth.getUser() - if (!user) redirect('/login') - - const title = formData.get('title') as string - const description = formData.get('description') as string - - if (!title.trim()) { - return { error: 'Title is required' } - } - - const { error } = await supabase - .from('tasks') - .insert([ - { - title: title.trim(), - description: description?.trim() || null, - user_id: user.id, - }, - ]) - - if (error) { - console.error('Error creating task:', error) - return { error: 'Failed to create task' } - } - - revalidatePath('/dashboard') - return { success: true } -} - -export async function updateTask(id: string, formData: FormData) { - const supabase = await createClient() - - const title = formData.get('title') as string - const description = formData.get('description') as string - const completed = formData.get('completed') === 'on' - - const { error } = await supabase - .from('tasks') - .update({ - title: title.trim(), - description: description?.trim() || null, - completed, - }) - .eq('id', id) - - if (error) { - console.error('Error updating task:', error) - return { error: 'Failed to update task' } - } - - revalidatePath('/dashboard') - return { success: true } -} - -export async function deleteTask(id: string) { - const supabase = await createClient() - - const { error } = await supabase - .from('tasks') - .delete() - .eq('id', id) - - if (error) { - console.error('Error deleting task:', error) - return { error: 'Failed to delete task' } - } - - revalidatePath('/dashboard') - return { success: true } -} - -export async function toggleTask(id: string, completed: boolean) { - const supabase = await createClient() - - const { error } = await supabase - .from('tasks') - .update({ completed }) - .eq('id', id) - - if (error) { - console.error('Error toggling task:', error) - return { error: 'Failed to update task' } - } - - revalidatePath('/dashboard') - return { success: true } -} -``` - ---- - -## UI Components & Styling - -### 1. Utility Functions - -Create `src/lib/utils.ts`: - -```typescript -import { type ClassValue, clsx } from 'clsx' -import { twMerge } from 'tailwind-merge' - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) -} - -export function formatDate(date: string | Date) { - return new Intl.DateTimeFormat('en-US', { - year: 'numeric', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - }).format(new Date(date)) -} -``` - -### 2. Task List Component - -Create `src/components/features/tasks/task-list.tsx`: - -```typescript -import { toggleTask, deleteTask } from '@/app/dashboard/actions' -import { formatDate } from '@/lib/utils' - -type Task = { - id: string - title: string - description: string | null - completed: boolean - created_at: string - updated_at: string -} - -type TaskListProps = { - tasks: Task[] -} - -export default function TaskList({ tasks }: TaskListProps) { - if (tasks.length === 0) { - return ( -
-

No tasks yet. Create your first task!

-
- ) - } - - return ( -
-

Your Tasks

-
- {tasks.map((task) => ( -
-
-
-
- -
-
-

- {task.title} -

- {task.description && ( -

- {task.description} -

- )} -

- Created {formatDate(task.created_at)} -

-
-
-
- -
-
-
- ))} -
-
- ) -} -``` - -### 3. Task Form Component - -Create `src/components/features/tasks/task-form.tsx`: - -```typescript -'use client' - -import { createTask } from '@/app/dashboard/actions' -import { useActionState } from 'react' - -export default function TaskForm() { - const [state, formAction, pending] = useActionState(createTask, null) - - return ( -
-

Add New Task

-
-
- - -
- -
- -