Next.js 15 App Router best practices, file structure, server/client components, and performance optimization. Use when building pages, layouts, or API routes.
src/app/layout.tsx for navigation, headers, sidebarssrc/app/api/ for backend endpointssrc/
├── app/
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Home page
│ ├── dashboard/
│ │ ├── layout.tsx # Dashboard layout
│ │ ├── page.tsx # Dashboard home
│ │ ├── [id]/
│ │ │ └── page.tsx # Dynamic route
│ │ └── api/
│ │ └── route.ts # API endpoint
│ └── api/
│ └── webhooks/
│ └── stripe/
│ └── route.ts
├── components/
│ ├── layout/ # Layout components
│ ├── ui/ # shadcn/ui components
│ └── features/ # Feature-specific components
├── lib/
│ ├── supabase.ts # Database client
│ ├── stripe.ts # Stripe utilities
│ ├── auth.ts # Auth utilities
│ └── utils.ts # Helpers
└── types/
└── index.ts # TypeScript definitions
Use Server Components for:
Use Client Components for:
// Server Component (default)
export default async function Page() {
const data = await fetchData()
return <div>{data}</div>
}
// Client Component
'use client'
import { useState } from 'react'
export default function InteractiveComponent() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(count + 1)}>{count}</button>
}
Use layouts for shared UI structure:
// src/app/dashboard/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="flex">
<Sidebar />
<main className="flex-1">{children}</main>
</div>
)
}
API endpoints are in src/app/api/:
// src/app/api/content/route.ts
import { NextRequest, NextResponse } from 'next/server'
export async function GET(request: NextRequest) {
try {
// Server-side logic here
return NextResponse.json({ data })
} catch (error) {
return NextResponse.json(
{ error: 'Internal error' },
{ status: 500 }
)
}
}
export async function POST(request: NextRequest) {
const data = await request.json()
// Handle POST
return NextResponse.json({ success: true })
}
Use [id] for dynamic segments:
// src/app/dashboard/content/[id]/page.tsx
export default async function ContentPage({ params }: { params: { id: string } }) {
const content = await getContent(params.id)
return <Editor content={content} />
}
// Good: Server Component
async function UserData({ userId }: { userId: string }) {
const user = await db.query.users.findById(userId)
return <div>{user.name}</div>
}
// Bad: Client Component making multiple requests
'use client'
function UserData({ userId }: { userId: string }) {
const [user, setUser] = useState(null)
useEffect(() => {
fetch(`/api/users/${userId}`).then(/* ... */)
}, [userId])
// Many requests, no caching
}
(group) to organize without affecting URL[...slug] for flexible routing[[...slug]]