# Next.js 15 Patterns - CareBridge ## Critical Changes from Next.js 14 Next.js 15 introduces async Request APIs. Several patterns have changed. ## Async Params (CRITICAL) ### In App Router Pages **ALL dynamic route params must be awaited** ```typescript // ❌ WRONG - Next.js 14 pattern (will error in 15) export default function Page({ params }: { params: { id: string } }) { const { id } = params // ERROR! } // ✅ CORRECT - Next.js 15 pattern type Props = { params: Promise<{ id: string }> } export default async function Page({ params }: Props) { const { id } = await params // Use id } ``` ### In API Routes **ALL dynamic route params must be awaited** ```typescript // ❌ WRONG export async function GET( request: Request, { params }: { params: { userId: string } } ) { const { userId } = params // ERROR! } // ✅ CORRECT export async function GET( request: Request, { params }: { params: Promise<{ userId: string }> } ) { const { userId } = await params // Use userId } ``` ## Async SearchParams **SearchParams are also async in Next.js 15** ```typescript // ❌ WRONG export default function Page({ searchParams, }: { searchParams: { caseId?: string } }) { const caseId = searchParams.caseId // ERROR! } // ✅ CORRECT type SearchParams = Promise<{ caseId?: string }> export default async function Page({ searchParams, }: { searchParams: SearchParams }) { const params = await searchParams const caseId = getCaseIdFromParams(params) } ``` ## Server Components vs Client Components ### When to Use Server Components (Default) Use server components (no `'use client'`) for: - Pages that fetch data - Pages that need SEO - Static content - Database queries ```typescript // Server Component (default) import { createServerClient } from '@/lib/supabase/server' export default async function DashboardPage() { const supabase = await createServerClient() const { data } = await supabase.from('cases').select('*') return
{/* Render data */}
} ``` ### When to Use Client Components Use `'use client'` for: - Interactive UI (onClick, onChange, etc.) - React hooks (useState, useEffect, useContext) - Browser APIs (localStorage, window, etc.) - Third-party libraries that need client-side ```typescript 'use client' import { useState } from 'react' export function InteractiveComponent() { const [count, setCount] = useState(0) return } ``` ### Mixing Server and Client **Pattern**: Server component fetches data, passes to client component ```typescript // app/page.tsx (Server Component) import { ClientComponent } from './client-component' export default async function Page() { const data = await fetchData() return } // client-component.tsx (Client Component) 'use client' export function ClientComponent({ data }) { const [selected, setSelected] = useState(null) return (
setSelected(data)}> {/* Interactive UI */}
) } ``` ## Metadata API Use the Metadata API for SEO: ```typescript import type { Metadata } from 'next' export const metadata: Metadata = { title: 'Dashboard | CareBridge', description: 'Manage your care cases', } export default function Page() { return
Dashboard
} ``` ## Loading and Error States ### Loading States Create `loading.tsx` next to `page.tsx`: ```typescript // app/dashboard/loading.tsx export default function Loading() { return
Loading dashboard...
} ``` ### Error Boundaries Create `error.tsx` next to `page.tsx`: ```typescript // app/dashboard/error.tsx 'use client' // Error boundaries must be client components export default function Error({ error, reset, }: { error: Error & { digest?: string } reset: () => void }) { return (

Something went wrong!

) } ``` ## Data Fetching Patterns ### Server-Side Fetching (Preferred) ```typescript // In Server Component export default async function Page() { const supabase = await createServerClient() const { data } = await supabase.from('cases').select('*') return } ``` ### Client-Side Fetching (When Needed) ```typescript 'use client' import { useEffect, useState } from 'react' export function ClientDataFetcher() { const [data, setData] = useState(null) useEffect(() => { fetch('/api/data') .then((res) => res.json()) .then(setData) }, []) if (!data) return
Loading...
return
{/* Render data */}
} ``` ## Form Actions Use Server Actions for forms: ```typescript // app/actions.ts 'use server' export async function createCase(formData: FormData) { const name = formData.get('name') const supabase = await createServerClient() await supabase.from('cases').insert({ name }) revalidatePath('/dashboard') } // app/page.tsx import { createCase } from './actions' export default function Page() { return (
) } ``` ## Common Mistakes ### ❌ Mistake #1: Forgetting 'use client' ```typescript // WRONG - Will error because useState needs client import { useState } from 'react' export function Component() { const [count, setCount] = useState(0) // ERROR! } // CORRECT 'use client' import { useState } from 'react' export function Component() { const [count, setCount] = useState(0) } ``` ### ❌ Mistake #2: Not awaiting params ```typescript // WRONG export default function Page({ params }) { const { id } = params // ERROR in Next.js 15! } // CORRECT export default async function Page({ params }) { const { id } = await params } ``` ### ❌ Mistake #3: Using client-side hooks in server components ```typescript // WRONG export default function Page() { const router = useRouter() // ERROR! } // CORRECT - Add 'use client' 'use client' export default function Page() { const router = useRouter() } ``` ### ❌ Mistake #4: Importing server-only code in client components ```typescript // WRONG - Client component trying to use server-only code 'use client' import { createServerClient } from '@/lib/supabase/server' // ERROR! export function Component() { const supabase = await createServerClient() // Won't work! } // CORRECT - Use Server Action instead 'use client' import { getData } from './actions' export function Component() { const handleClick = async () => { const data = await getData() // Calls server action } } ``` ## Environment Variables ### Public vs Private ```typescript // ✅ Public (available in browser) const key = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY // ✅ Private (server-only) const secret = process.env.STRIPE_SECRET_KEY // Only works in server components/actions ``` ### Accessing in Client Components ```typescript // ❌ WRONG - Private env vars don't work in client 'use client' const secret = process.env.STRIPE_SECRET_KEY // undefined! // ✅ CORRECT - Use public env vars 'use client' const publicKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY ``` ## Caching Next.js 15 has granular caching: ```typescript // Cache indefinitely (static data) const data = await fetch('https://api.example.com/data', { cache: 'force-cache', }) // Revalidate every hour const data = await fetch('https://api.example.com/data', { next: { revalidate: 3600 }, }) // Never cache (dynamic data) const data = await fetch('https://api.example.com/data', { cache: 'no-store', }) ``` ## Route Handlers (API Routes) ```typescript // app/api/route.ts export async function GET(request: Request) { const { searchParams } = new URL(request.url) const id = searchParams.get('id') return Response.json({ id }) } export async function POST(request: Request) { const body = await request.json() return Response.json({ success: true }) } ``` ## Dynamic Routes ```typescript // app/cases/[caseId]/page.tsx type Props = { params: Promise<{ caseId: string }> } export default async function CasePage({ params }: Props) { const { caseId } = await params return
Case: {caseId}
} ``` ## Middleware Use for auth checks, redirects, etc.: ```typescript // middleware.ts import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server' const isProtectedRoute = createRouteMatcher([ '/dashboard(.*)', '/cases(.*)', ]) export default clerkMiddleware((auth, req) => { if (isProtectedRoute(req)) auth().protect() }) export const config = { matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'], } ```