Next.js App Router — Architecture
The Next.js App Router (introduced in Next.js 13, stable in Next.js 14) fundamentally changes how Next.js apps are structured: file-system routing with layouts, loading/error/not-found files per segment, React Server Components by default, and Server Actions for form handling.
App Router File System Conventions
// App Router file system:
// app/
// layout.tsx ← Root layout (wraps all pages)
// page.tsx ← Home page (/)
// loading.tsx ← Suspense fallback for this segment
// error.tsx ← Error boundary for this segment
// not-found.tsx ← 404 for this segment
// globals.css
//
// products/
// layout.tsx ← Products layout (nested under root)
// page.tsx ← Products list (/products)
// loading.tsx ← Loading skeleton for /products
// [id]/
// page.tsx ← Product detail (/products/[id])
// error.tsx ← Error boundary for /products/[id]
//
// dashboard/
// layout.tsx ← Dashboard layout with sidebar
// page.tsx ← /dashboard
// (analytics)/ ← Route group (no URL segment)
// analytics/
// page.tsx ← /dashboard/analytics
// (settings)/
// settings/
// page.tsx ← /dashboard/settings
// ---- Root Layout — MUST export default, wraps entire app ----
// app/layout.tsx (server component)
import { Inter } from 'next/font/google';
import type { Metadata } from 'next';
const inter = Inter({ subsets: ['latin'] });
export const metadata: Metadata = {
title: { default: 'My App', template: '%s | My App' }, // %s = page title
description: 'A production React application',
metadataBase: new URL('https://myapp.com'),
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
);
}
// ---- Nested layout — for dashboard with sidebar ----
// app/dashboard/layout.tsx (server component)
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div style={{ display: 'flex' }}>
<Sidebar /> {/* persists across /dashboard/* routes */}
<main style={{ flex: 1 }}>{children}</main>
</div>
);
}
// ---- Loading UI — shown while page's async data fetches ----
// app/products/loading.tsx
export default function ProductsLoading() {
return (
<div>
{Array.from({ length: 6 }).map((_, i) => (
<div key={i} style={{ height: 200, background: '#f3f4f6', borderRadius: 8, marginBottom: 16, animation: 'pulse 1.5s infinite' }} />
))}
</div>
);
}
// ---- Error boundary — per route segment ----
// app/products/[id]/error.tsx
'use client'; // error.tsx MUST be a client component (uses hooks)
import { useEffect } from 'react';
export default function ProductError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error(error); // log to error reporting service
}, [error]);
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={reset}>Try again</button>
</div>
);
}
// ---- Dynamic route — /products/[id] ----
// app/products/[id]/page.tsx
export default async function ProductPage({ params }: { params: { id: string } }) {
const product = await fetch(`https://api.example.com/products/${params.id}`, {
next: { revalidate: 3600 }, // ISR: revalidate every hour
}).then(r => r.json());
return <ProductDetail product={product} />;
}
// Opt into static generation for known IDs
export async function generateStaticParams() {
const products = await fetch('https://api.example.com/products').then(r => r.json());
return products.map((p: { id: string }) => ({ id: p.id }));
}Tip
Tip
Practice Nextjs App Router Architecture in small, isolated examples before integrating into larger projects. Breaking concepts into small experiments builds genuine understanding faster than reading alone.
Server-first. RSC by default. Streaming with loading.tsx.
Practice Task
Note
Practice Task — (1) Write a working example of Nextjs App Router Architecture from scratch without looking at notes. (2) Modify it to handle an edge case (empty input, null value, or error state). (3) Share your solution in the Priygop community for feedback.
Quick Quiz
Common Mistake
Warning
A common mistake with Nextjs App Router Architecture is skipping edge case testing — empty inputs, null values, and unexpected data types. Always validate boundary conditions to write robust, production-ready react code.