# Next.js App Router — Modern Patterns for 2026
TL;DR
Next.js App Router (introduced 2023) has replaced Pages Router as the default for new Next projects. This guide covers the patterns that matter for production — Server Components, Server Actions, streaming, partial prerendering, middleware, and deploying on DomainIndia VPS vs Vercel vs Cloudflare.
## App Router vs Pages Router — why change
The old `pages/` directory approach was fine but bolted routing atop client-side React. App Router makes the server first-class:
| Feature | Pages Router | App Router |
| Server/client boundary | getServerSideProps / getStaticProps at page level | Per-component via 'use client' |
| Layouts | Manually composed | Native layout files |
| Data fetching | Hook-based mostly | fetch() in Server Components |
| Streaming | Limited | Built-in via <Suspense> |
| Forms | onSubmit handler | Server Actions (no fetch needed) |
| Metadata | <Head> component | metadata export + generateMetadata |
For new projects in 2026: always App Router. Pages Router receives maintenance but no new features.
## Project layout
```
app/
├── layout.tsx # root layout (wraps everything)
├── page.tsx # home page
├── loading.tsx # shown while page is loading
├── error.tsx # shown on errors
├── not-found.tsx # 404
├── (marketing)/ # route group — doesn't add to URL
│ ├── about/page.tsx
│ └── pricing/page.tsx
├── (app)/ # route group for authenticated routes
│ ├── layout.tsx # different layout (e.g. with sidebar)
│ ├── dashboard/page.tsx
│ └── settings/page.tsx
├── api/ # route handlers (former API routes)
│ └── users/route.ts
├── blog/
│ ├── page.tsx # /blog
│ └── [slug]/page.tsx # /blog/
```
Parentheses `(group)` don't affect URL. Brackets `[slug]` = dynamic segment.
## Server Components by default
Every component is server-rendered unless marked `'use client'`.
**Server Component (default):**
```tsx
// app/blog/page.tsx
import { db } from '@/lib/db';
export default async function BlogList() {
const posts = await db.post.findMany();
return (
{posts.map(p => - {p.title}
)}
);
}
```
- Runs on server
- Can directly query DB, use secrets
- Ships zero JS to client for this component
- Can't use `useState`, `useEffect`, browser APIs
**Client Component:**
```tsx
// app/components/LikeButton.tsx
'use client';
import { useState } from 'react';
export function LikeButton({ postId }: { postId: string }) {
const [liked, setLiked] = useState(false);
return (
setLiked(!liked)}>
{liked ? '❤️' : '🤍'}
);
}
```
- Runs on server (for SSR) AND client (for interactivity)
- Needs `'use client'` directive at top
- Use for anything interactive
**Rule:** ship as much as Server Components; use Client Components only when needed.
## Data fetching
Built-in `fetch` in Server Components with caching:
```tsx
export default async function Page() {
// Static — cached indefinitely
const staticData = await fetch('https://api.example.com/static').then(r => r.json());
// Revalidate every 60s
const dynamicData = await fetch('https://api.example.com/news', {
next: { revalidate: 60 },
}).then(r => r.json());
// Always fresh
const liveData = await fetch('https://api.example.com/stock', {
cache: 'no-store',
}).then(r => r.json());
return
...;
}
```
Next.js dedupes concurrent identical fetches per render. No extra query.
## Server Actions — mutations without fetch
```tsx
// app/post/[id]/page.tsx
import { revalidatePath } from 'next/cache';
async function addComment(postId: string, formData: FormData) {
'use server';
const text = formData.get('text') as string;
await db.comment.create({ data: { postId, text } });
revalidatePath(`/post/${postId}`);
}
export default async function Post({ params }: { params: { id: string } }) {
return (
Comment
);
}
```
- Function executes on server
- No fetch/API route needed
- `revalidatePath` refreshes cached data
- Progressive enhancement — works without JavaScript
## Streaming with Suspense
Long-loading components don't block the page:
```tsx
import { Suspense } from 'react';
export default function Dashboard() {
return (
);
}
```
Header shows instantly; reports stream in when ready. Shell is fast; content fills in.
## Partial Prerendering (2026 stable)
Combines static + dynamic in one route. Static parts ship from CDN; dynamic parts stream from origin.
```tsx
// app/product/[id]/page.tsx
export const experimental_ppr = true;
import { Suspense } from 'react';
export default function Product({ params }) {
return (
{/* Static — prerendered */}
Loading cart...
}>
{/* Dynamic — user-specific, rendered per request */}
);
}
```
Best of SSG + SSR.
## Middleware — edge logic
`middleware.ts` at project root — runs before every matched request:
```typescript
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// Redirect non-www to www
if (!request.nextUrl.host.startsWith('www.')) {
return NextResponse.redirect(new URL(`https://www.${request.nextUrl.host}${request.nextUrl.pathname}`));
}
// Auth gate
if (request.nextUrl.pathname.startsWith('/dashboard')) {
const token = request.cookies.get('session');
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
}
// A/B test
const bucket = request.cookies.get('ab')?.value || (Math.random() > 0.5 ? 'a' : 'b');
const response = NextResponse.next();
response.cookies.set('ab', bucket);
return response;
}
export const config = {
matcher: ['/((?!api|_next|favicon.ico).*)'],
};
```
Runs at the edge. Use for redirects, auth, A/B tests, geolocation routing.
## Metadata API (SEO)
```tsx
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next';
export async function generateMetadata(
{ params }: { params: { slug: string } }
): Promise {
const post = await getPost(params.slug);
return {
title: `${post.title} | Your Blog`,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.coverImage],
},
twitter: { card: 'summary_large_image' },
};
}
```
Next.js generates `` tags automatically.
## Static vs ISR vs SSR vs PPR
```tsx
// Static (at build time) — default behaviour
export default async function Page() {
const data = await fetch('...'); return
{data}
;
}
// ISR (Incremental Static Regeneration)
export const revalidate = 60; // seconds
// SSR (every request fresh)
export const dynamic = 'force-dynamic';
// PPR (partial, as above)
export const experimental_ppr = true;
```
Pick based on data freshness:
| Data freshness | Strategy |
| Never changes (docs, landing page) | Static |
| Changes hourly | ISR with revalidate: 3600 |
| Per-user (dashboard) | SSR |
| Mixed (user header + static product) | PPR |
## Deployment on DomainIndia
**VPS (recommended for self-hosting):**
```bash
npm run build # produces .next/ build output
npm install --production
node_modules/.bin/next start -p 3000
```
systemd + nginx reverse proxy. See our [Go/Rust deployment articles](https://domainindia.com/support/kb/running-go-applications-vps-systemd-reverse-proxy) for pattern.
**Shared cPanel:**
Use `next build && next export` for static export (no dynamic features). Upload `out/` to public_html.
For dynamic Next.js: needs Node.js app support on cPanel. Works for small apps.
**Vercel:** simplest but vendor-locked and $$ at scale.
**Cloudflare Workers:** runs Next.js via `@cloudflare/next-on-pages`. Good for global distribution.
## Common pitfalls
## FAQ
Q
Pages Router or App Router for new projects?
App Router, unquestionably. Pages is legacy.
Q
Next.js vs Remix vs Astro?
Next.js — most features, biggest ecosystem, enterprise-ready. Remix — similar features, smaller ecosystem, different philosophy. Astro — content-focused (blogs, docs), per-component framework choice. Pick Next for apps, Astro for content-heavy sites.
Q
Can I host Next.js on shared hosting?
Static export yes. Dynamic rendering — needs Node.js app support. VPS is simpler for serious use.
Q
Do Server Components replace APIs?
For internal data fetching — often yes. For mobile apps or third-party integrations, you still need REST/GraphQL APIs.
Q
Server Actions vs traditional API routes?
Actions for forms + mutations triggered by user. API routes for webhooks, external consumers, GraphQL.
Deploy Next.js on a DomainIndia VPS — no vendor lock-in.
Start with VPS