## Core Principles
### Project Structure
```
my-next-app/
├── src/
│ ├── app/ # App Router
│ │ ├── (auth)/ # Route groups
│ │ │ ├── login/
│ │ │ └── register/
│ │ ├── api/ # API Routes
│ │ │ └── [...]/
│ │ ├── dashboard/
│ │ │ ├── page.tsx
│ │ │ ├── layout.tsx
│ │ │ └── loading.tsx
│ │ ├── layout.tsx # Root layout
│ │ ├── page.tsx # Home page
│ │ ├── error.tsx # Error boundary
│ │ ├── not-found.tsx # 404 page
│ │ └── global-error.tsx # Global error
│ ├── components/
│ │ ├── ui/ # shadcn/ui components
│ │ └── features/ # Feature components
│ ├── lib/
│ │ ├── actions/ # Server actions
│ │ ├── db/ # Database utilities
│ │ └── utils.ts # Utility functions
│ ├── hooks/ # Custom React hooks
│ ├── types/ # TypeScript types
│ └── styles/ # Global styles
├── public/ # Static assets
└── next.config.js
```
## App Router Patterns
### Page Components
```tsx
// src/app/dashboard/page.tsx
import { Suspense } from 'react';
import { getUser } from '@/lib/actions/user';
import { DashboardSkeleton } from '@/components/skeletons';
import { Dashboard } from '@/components/features/dashboard';
// Metadata for SEO
export const metadata = {
title: 'Dashboard | MyApp',
description: 'Your personal dashboard'
};
// Server Component by default
export default async function DashboardPage() {
const user = await getUser();
return (
<Suspense fallback={<DashboardSkeleton />}>
<Dashboard user={user} />
</Suspense>
);
}
```
### Layouts
```tsx
// src/app/layout.tsx
import { Inter } from 'next/font/google';
import { ThemeProvider } from '@/components/theme-provider';
import { Toaster } from '@/components/ui/toaster';
import './globals.css';
const inter = Inter({ subsets: ['latin'] });
export const metadata = {
title: {
template: '%s | MyApp',
default: 'MyApp'
},
description: 'A modern Next.js application'
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body className={inter.className}>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
<Toaster />
</ThemeProvider>
</body>
</html>
);
}
```
### Loading States
```tsx
// src/app/dashboard/loading.tsx
import { Skeleton } from '@/components/ui/skeleton';
export default function DashboardLoading() {
return (
<div className="space-y-4">
<Skeleton className="h-8 w-[250px]" />
<Skeleton className="h-[400px] w-full" />
</div>
);
}
```
### Error Handling
```tsx
// src/app/dashboard/error.tsx
'use client';
import { useEffect } from 'react';
import { Button } from '@/components/ui/button';
export default function DashboardError({
error,
reset
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error(error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center min-h-[400px] space-y-4">
<h2 className="text-xl font-semibold">Something went wrong!</h2>
<Button onClick={() => reset()}>Try again</Button>
</div>
);
}
```
## Server Actions
### Form Actions
```tsx
// src/lib/actions/user.ts
'use server';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { z } from 'zod';
import { db } from '@/lib/db';
const UserSchema = z.object({
name: z.string().min(2).max(50),
email: z.string().email()
});
export async function updateUser(formData: FormData) {
const validatedFields = UserSchema.safeParse({
name: formData.get('name'),
email: formData.get('email')
});
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
message: 'Invalid fields'
};
}
const { name, email } = validatedFields.data;
try {
await db.user.update({
where: { id: userId },
data: { name, email }
});
} catch (error) {
return { message: 'Database error' };
}
revalidatePath('/dashboard');
redirect('/dashboard');
}
```
### Using Server Actions
```tsx
// src/components/features/user-form.tsx
'use client';
import { useFormState, useFormStatus } from 'react-dom';
import { updateUser } from '@/lib/actions/user';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
function SubmitButton() {
const { pending } = useFormStatus();
return (
<Button type="submit" disabled={pending}>
{pending ? 'Saving...' : 'Save Changes'}
</Button>
);
}
export function UserForm({ user }: { user: User }) {
const [state, action] = useFormState(updateUser, null);
return (
<form action={action} className="space-y-4">
<div>
<Input name="name" defaultValue={user.name} placeholder="Name" />
{state?.errors?.name && <p className="text-sm text-destructive">{state.errors.name}</p>}
</div>
<div>
<Input name="email" type="email" defaultValue={user.email} placeholder="Email" />
{state?.errors?.email && <p className="text-sm text-destructive">{state.errors.email}</p>}
</div>
<SubmitButton />
</form>
);
}
```
## API Routes
```tsx
// src/app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { db } from '@/lib/db';
import { auth } from '@/lib/auth';
export async function GET(request: NextRequest) {
const session = await auth();
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const searchParams = request.nextUrl.searchParams;
const page = parseInt(searchParams.get('page') || '1');
const limit = parseInt(searchParams.get('limit') || '10');
const users = await db.user.findMany({
take: limit,
skip: (page - 1) * limit,
orderBy: { createdAt: 'desc' }
});
return NextResponse.json({ users, page, limit });
}
export async function POST(request: NextRequest) {
const session = await auth();
if (!session?.user.isAdmin) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const body = await request.json();
const validated = UserSchema.safeParse(body);
if (!validated.success) {
return NextResponse.json({ error: validated.error.flatten() }, { status: 400 });
}
const user = await db.user.create({
data: validated.data
});
return NextResponse.json(user, { status: 201 });
}
```
## Data Fetching Patterns
### Server Component Fetching
```tsx
// Direct database access in Server Components
async function UserList() {
const users = await db.user.findMany({
orderBy: { createdAt: 'desc' },
take: 10
});
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
```
### Parallel Data Fetching
```tsx
export default async function DashboardPage() {
// Fetch in parallel
const [user, posts, notifications] = await Promise.all([
getUser(),
getPosts(),
getNotifications()
]);
return <Dashboard user={user} posts={posts} notifications={notifications} />;
}
```
### Caching Strategies
```tsx
// Revalidate every hour
export const revalidate = 3600;
// Or use cache with tags
import { unstable_cache } from 'next/cache';
const getCachedUser = unstable_cache(
async (id: string) => db.user.findUnique({ where: { id } }),
['user'],
{ tags: ['user'], revalidate: 3600 }
);
```
## TypeScript Best Practices
```tsx
// src/types/index.ts
export interface User {
id: string;
name: string;
email: string;
role: 'user' | 'admin';
createdAt: Date;
}
export type UserWithPosts = User & {
posts: Post[];
};
// Component props types
interface DashboardProps {
user: User;
children?: React.ReactNode;
}
// Server action return types
type ActionResult<T> = { success: true; data: T } | { success: false; error: string };
```
## Performance Optimization
### Image Optimization
```tsx
import Image from 'next/image';
function Avatar({ user }: { user: User }) {
return (
<Image
src={user.avatarUrl}
alt={`${user.name}'s avatar`}
width={40}
height={40}
className="rounded-full"
placeholder="blur"
blurDataURL="/placeholder-avatar.jpg"
/>
);
}
```
### Dynamic Imports
```tsx
import dynamic from 'next/dynamic';
const HeavyChart = dynamic(() => import('@/components/features/heavy-chart'), {
loading: () => <ChartSkeleton />,
ssr: false // Disable SSR for client-only components
});
```
## Best Practices Summary
1. **Server Components First**: Default to Server Components, add 'use client' only when needed
2. **Colocate Files**: Keep related files together (page, loading, error)
3. **Validate Inputs**: Always validate with Zod or similar
4. **Type Everything**: Leverage TypeScript fully
5. **Use Server Actions**: Prefer over API routes for form submissions
6. **Optimize Images**: Always use next/image
7. **Cache Strategically**: Use appropriate revalidation times
8. **Handle Errors**: Implement error.tsx at appropriate levels
9. **SEO Metadata**: Export metadata from every page
## Related Resources
- [Next.js Documentation](https://nextjs.org/docs)
- [React Server Components](https://react.dev/reference/react/use-server)
- [Vercel AI SDK](https://sdk.vercel.ai/)
- [shadcn/ui](https://ui.shadcn.com/)