Key Takeaways
- Server Components eliminate client-side JavaScript for non-interactive parts of the page, reducing bundle size significantly
- The golden rule: default to Server Components, only add 'use client' when interactivity is required
- Data fetching is simplified to async/await directly in components, replacing getServerSideProps and getStaticProps
- Nested layouts persist across navigation, preventing unnecessary re-renders and improving perceived performance
- Migration from Pages Router is incremental; both routers coexist during transition
Server Components vs Client Components
Understanding when to use Server vs Client Components is the most important concept in the App Router. The boundary between them determines your application's performance characteristics and developer experience.
| Feature | Server Component | Client Component |
|---|---|---|
| JavaScript shipped | None | Full component bundle |
| Data fetching | Direct (async/await) | useEffect or TanStack Query |
| Access secrets | Yes (env vars, DB) | No (exposed to browser) |
| useState/useEffect | No | Yes |
| onClick handlers | No | Yes |
| Third-party hooks | No | Yes |
| SEO | Excellent (pre-rendered HTML) | Requires SSR/hydration |
Push the 'use client' boundary as deep as possible in the component tree. A page can be a Server Component that renders a small Client Component for an interactive button. Only the button ships JavaScript.
- Server: data display, static content, SEO-critical sections, layouts
- Client: forms, interactive widgets, real-time features, browser API access
- Server Components can import Client Components (not vice versa without serialization)
- Pass serializable data (strings, numbers, arrays) as props from Server to Client Components
Data Fetching Patterns in App Router
App Router simplifies data fetching by using async Server Components. Data fetching happens at the component level, not at the page level, enabling more granular loading states and parallel data fetching.
| Pattern | Replaces | Caching Behavior |
|---|---|---|
| async component + fetch | getServerSideProps | Configurable (cache/no-cache) |
| generateStaticParams | getStaticPaths | Build-time generation |
| fetch with revalidate | ISR (revalidate) | Time-based revalidation |
| unstable_cache | Custom caching | Tag-based invalidation |
| Server Actions | API routes (POST) | Direct server mutation |
Next.js 14+ has multiple caching layers (Request Memoization, Data Cache, Full Route Cache, Router Cache). Understanding which cache affects your data is critical. When in doubt, use `{cache: 'no-store'}` and add caching later.
Routing, Layouts, and Loading States
The App Router uses a file-system based routing convention with special files for layouts, loading states, error handling, and not-found pages. This convention eliminates boilerplate while providing powerful features.
| File | Purpose | Behavior |
|---|---|---|
| page.tsx | Route UI | Renders the page content |
| layout.tsx | Shared layout | Persists across child navigation |
| loading.tsx | Loading fallback | Shows while page/data loads |
| error.tsx | Error boundary | Catches errors in route segment |
| not-found.tsx | 404 page | Shows when notFound() is called |
| route.ts | API endpoint | Handles HTTP requests |
- Layouts wrap child pages and do not re-render during navigation between children
- loading.tsx creates automatic Suspense boundaries for progressive loading
- Use route groups (parentheses) to organize routes without affecting the URL structure
- Parallel routes (@) render multiple pages in the same layout simultaneously
- Intercepting routes (.) enable modal patterns that work with direct URL access
Incremental Migration from Pages Router
Migrating from Pages Router to App Router does not need to happen all at once. Both routers coexist in the same Next.js project, allowing gradual migration page by page.
- Start with the root layout: create app/layout.tsx with html, body, and shared providers
- Migrate simple, static pages first (about, contact, privacy policy)
- Convert getServerSideProps pages to async Server Components with direct data fetching
- Convert getStaticProps pages using generateStaticParams for dynamic routes
- Move interactive components to Client Components with 'use client' directive
- Replace API routes with Route Handlers or Server Actions
- Test thoroughly: verify SEO, loading states, error handling, and navigation
Pages Router routes take precedence over App Router routes with the same path. During migration, delete the Pages Router version before creating the App Router version to avoid conflicts.
Frequently Asked Questions
Related services from Verbsz Technologies
If this article is relevant to a project on your roadmap, these are the services our team typically delivers.
