Next.js App Router: Patterns for Production
A practical guide to building fast, scalable web apps with the Next.js App Router — server components, streaming, parallel routes, intercepting routes, and caching strategies.
The Next.js App Router is a fundamental shift in how React apps are structured and rendered. This guide covers the patterns Ventra Rocket uses in production applications.
1. Server Components First
The default in the App Router is Server Components. Zero JavaScript sent to the client for data-fetching logic.
// app/dashboard/page.tsx — Server Component by default
async function DashboardPage() {
const metrics = await fetchMetrics(); // direct DB or API call
return <MetricsGrid data={metrics} />;
}
Rules:
- Data fetching → Server Component
- Interactivity (onClick, useState) → Client Component
- Browser APIs (localStorage) → Client Component
2. Streaming with Suspense
import { Suspense } from "react";
export default function Page() {
return (
<main>
<HeroSection />
<Suspense fallback={<SkeletonCard />}>
<SlowDataComponent />
</Suspense>
</main>
);
}
Use loading.tsx for route-level loading UI — it is an automatic Suspense wrapper for page.tsx.
3. Route Handlers for APIs
// app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";
export async function GET(request: NextRequest) {
const { searchParams } = request.nextUrl;
const page = Number(searchParams.get("page") ?? 1);
const users = await db.users.findMany({ skip: (page - 1) * 10, take: 10 });
return NextResponse.json({ users, page });
}
4. Caching Strategy
| Layer | Mechanism | Duration | |-------|-----------|----------| | Request Memoization | React cache | Per render | | Data Cache | fetch + next.revalidate | Persistent | | Full Route Cache | Static HTML | Build time | | Router Cache | Client-side | Per session |
// ISR: revalidate every hour
const data = await fetch("https://api.example.com/config", {
next: { revalidate: 3600 },
});
// Always fresh
const live = await fetch("https://api.example.com/prices", {
cache: "no-store",
});
5. Middleware for Auth
// middleware.ts
export function middleware(request: NextRequest) {
const token = request.cookies.get("auth_token");
const isProtected = request.nextUrl.pathname.startsWith("/dashboard");
if (isProtected && !token) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
}
export const config = { matcher: ["/dashboard/:path*"] };
Performance Checklist
- Use
next/imagefor all images (automatic WebP, lazy loading) - Use
next/fontfor fonts (self-hosted, zero layout shift) - Prefer Server Components for data fetching
- Wrap slow data sources in Suspense
- Use
revalidateTagfor on-demand cache invalidation
Conclusion
The App Router is production-ready when used correctly: push data fetching to the server, use Suspense for async boundaries, be deliberate about Client Components. Ventra Rocket has shipped multiple Next.js 15 apps using these patterns, achieving sub-second Time to First Byte.
Related Articles
10 TypeScript Patterns for Cleaner Code
A practical collection of 10 TypeScript patterns — from discriminated unions and template literal types to the satisfies operator — that make your codebase more type-safe and maintainable.
React State Management in 2026: Zustand, TanStack Query, and URL State
Choosing the right state management for React — TanStack Query for server state, Zustand for global UI state, URL state for filters, useState for local state.
Web Security Essentials: OWASP Top 10 for Node.js and React
Practical security — preventing SQL injection, XSS, broken authentication, and IDOR in Node.js applications.