The Next.js App Router represents a fundamental shift in how we build React applications. After shipping multiple production applications with it, we have developed patterns that work well at scale.
Server Components by Default
The most important mental shift is thinking server-first. Components are server components by default, which means they run on the server, can access databases directly, and ship zero JavaScript to the client.
When to Use Server Components
- Data fetching from APIs or databases
- Rendering static content
- Components that do not need interactivity
- Layouts and page shells
When to Use Client Components
Mark client components explicitly with the "use client" directive. Keep them as small and leaf-level as possible.
- Interactive elements with event handlers
- Browser APIs like localStorage or geolocation
- React hooks that require client-side state
- Third-party libraries that are not server-compatible
Data Fetching Patterns
Fetch data where you need it, directly in server components. The framework handles deduplication automatically.
Parallel Data Fetching
When you need multiple pieces of data, fetch them in parallel using Promise.all. This prevents waterfall requests and dramatically improves page load times.
Caching Strategy
Next.js caches fetch requests by default. Understand the caching behavior and configure it appropriately for your use case.
- Static data: Use default caching for data that rarely changes
- Dynamic data: Set revalidation intervals for content that updates periodically
- User-specific data: Disable caching for personalized content
Route Organization
The App Router uses file-system based routing. Organize routes thoughtfully to keep your codebase navigable.
Recommended Structure
- Group related routes in folders with parentheses for organization without URL impact
- Use route groups to apply different layouts to different sections
- Keep page components small, delegating to imported components
Parallel Routes and Intercepting Routes
These advanced patterns are powerful for complex UIs. Use parallel routes for dashboards with multiple independent sections. Use intercepting routes for modal patterns where you want to show content in context while supporting direct navigation.
Server Actions
Server Actions replace API routes for mutations. They run on the server and can be called directly from client components.
Best Practices
Server Actions simplify the mental model significantly. Instead of thinking about API endpoints, you think about functions.
- Keep actions small and focused
- Validate all inputs on the server
- Return structured responses for error handling
- Use the useFormStatus hook for loading states
Error Handling
Implement error boundaries at strategic points in your component tree. The App Router supports error.tsx files for route-level error handling.
Error Boundary Strategy
Always provide meaningful error messages and recovery options when possible.
- Root error boundary for catastrophic failures
- Route-level boundaries for section-specific errors
- Component-level boundaries for isolated features
Metadata and SEO
The App Router has excellent built-in support for metadata. Use the metadata export for static metadata and generateMetadata for dynamic metadata.
Dynamic Metadata
When metadata depends on data, use generateMetadata to fetch what you need. The framework will parallelize this with your page data fetching when possible.
Performance Optimization
Streaming and Suspense
Use Suspense boundaries to stream content progressively. This improves perceived performance by showing content as it becomes available.
Image Optimization
Always use the next/image component. It handles lazy loading, responsive images, and format optimization automatically.
Font Optimization
Use next/font to self-host fonts with zero layout shift. It automatically optimizes font loading for performance.
Conclusion
The App Router requires rethinking how you build React applications, but the payoff is significant. Server components reduce client-side JavaScript, streaming improves perceived performance, and the mental model is ultimately simpler once you internalize it. Start with server components everywhere and add client interactivity only where needed.






