Next.js App Router: Panduan Lengkap untuk Developer
Next.js App Router adalah paradigma baru dalam pengembangan aplikasi Next.js yang memperkenalkan konsep-konsep modern seperti Server Components, Streaming, dan file-based routing yang lebih powerful.
Apa itu App Router?
App Router adalah sistem routing baru di Next.js 13+ yang dibangun di atas React Server Components. Ini menggantikan Pages Router dengan pendekatan yang lebih modern dan fleksibel.
Perbedaan dengan Pages Router
| Feature | Pages Router | App Router |
| ------------- | -------------------------------------- | -------------------------- |
| Routing | pages/ directory | app/ directory |
| Layout | _app.js dan _document.js | layout.js nested |
| Data Fetching | getServerSideProps, getStaticProps | fetch() dengan caching |
| Components | Client Components | Server + Client Components |
Struktur Direktori App Router
app/
├── layout.js # Root layout
├── page.js # Home page
├── loading.js # Loading UI
├── error.js # Error UI
├── not-found.js # 404 page
├── global-error.js # Global error UI
├── route.js # API route
├── template.js # Template
├── default.js # Parallel route default
└── dashboard/
├── layout.js # Nested layout
├── page.js # Dashboard page
├── settings/
│ └── page.js # Settings page
└── analytics/
└── page.js # Analytics page
File Conventions
Layout
// app/layout.js
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<header>
<nav>Navigation</nav>
</header>
<main>{children}</main>
<footer>Footer</footer>
</body>
</html>
);
}
Page
// app/dashboard/page.js
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<p>Welcome to your dashboard!</p>
</div>
);
}
Loading
// app/dashboard/loading.js
export default function Loading() {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-gray-900"></div>
</div>
);
}
Error
// app/dashboard/error.js
"use client"; // Error components must be Client Components
export default function Error({ error, reset }) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<h2>Something went wrong!</h2>
<p>{error.message}</p>
<button
onClick={() => reset()}
className="mt-4 px-4 py-2 bg-blue-500 text-white rounded"
>
Try again
</button>
</div>
);
}
Server Components vs Client Components
Server Components (Default)
// app/posts/page.js
// This is a Server Component by default
async function getPosts() {
const res = await fetch("https://api.example.com/posts", {
cache: "force-cache", // Static generation
});
return res.json();
}
export default async function Posts() {
const posts = await getPosts();
return (
<div>
<h1>Posts</h1>
{posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
);
}
Client Components
// app/components/counter.js
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Data Fetching
Fetch dengan Caching
// Static data fetching
async function getStaticData() {
const res = await fetch("https://api.example.com/data", {
cache: "force-cache", // Default behavior
});
return res.json();
}
// Dynamic data fetching
async function getDynamicData() {
const res = await fetch("https://api.example.com/data", {
cache: "no-store",
});
return res.json();
}
// Revalidated data fetching
async function getRevalidatedData() {
const res = await fetch("https://api.example.com/data", {
next: { revalidate: 60 }, // Revalidate every 60 seconds
});
return res.json();
}
Parallel Data Fetching
// app/dashboard/page.js
async function getUser() {
const res = await fetch("https://api.example.com/user");
return res.json();
}
async function getPosts() {
const res = await fetch("https://api.example.com/posts");
return res.json();
}
export default async function Dashboard() {
// Parallel fetching
const [user, posts] = await Promise.all([getUser(), getPosts()]);
return (
<div>
<h1>Welcome, {user.name}!</h1>
<div>
<h2>Your Posts</h2>
{posts.map((post) => (
<article key={post.id}>
<h3>{post.title}</h3>
</article>
))}
</div>
</div>
);
}
Dynamic Routes
Basic Dynamic Route
// app/posts/[id]/page.js
export default function Post({ params }) {
return <h1>Post ID: {params.id}</h1>;
}
// Generate static params
export async function generateStaticParams() {
const posts = await fetch("https://api.example.com/posts").then((res) =>
res.json()
);
return posts.map((post) => ({
id: post.id.toString(),
}));
}
Catch-all Routes
// app/shop/[...slug]/page.js
export default function Shop({ params }) {
// /shop/clothes/shirts/t-shirts
// params.slug = ['clothes', 'shirts', 't-shirts']
return (
<div>
<h1>Shop</h1>
<p>Category: {params.slug.join(" > ")}</p>
</div>
);
}
Route Groups
app/
├── (marketing)/
│ ├── about/
│ │ └── page.js
│ └── contact/
│ └── page.js
├── (shop)/
│ ├── products/
│ │ └── page.js
│ └── cart/
│ └── page.js
└── layout.js
Route groups memungkinkan Anda mengorganisir routes tanpa mempengaruhi URL structure.
Parallel Routes
app/
├── @analytics/
│ └── page.js
├── @team/
│ └── page.js
├── layout.js
└── page.js
// app/layout.js
export default function Layout({ children, analytics, team }) {
return (
<div>
{children}
<div className="grid grid-cols-2 gap-4">
{analytics}
{team}
</div>
</div>
);
}
Intercepting Routes
app/
├── feed/
│ └── page.js
├── photo/
│ └── [id]/
│ └── page.js
└── (..)photo/
└── [id]/
└── page.js
Intercepting routes memungkinkan Anda menampilkan route dalam context yang berbeda (misalnya modal).
API Routes
// app/api/posts/route.js
import { NextResponse } from "next/server";
export async function GET() {
const posts = await fetch("https://api.example.com/posts");
const data = await posts.json();
return NextResponse.json(data);
}
export async function POST(request) {
const body = await request.json();
// Process the data
const newPost = await createPost(body);
return NextResponse.json(newPost, { status: 201 });
}
Dynamic API Routes
// app/api/posts/[id]/route.js
export async function GET(request, { params }) {
const { id } = params;
const post = await getPost(id);
if (!post) {
return NextResponse.json({ error: "Post not found" }, { status: 404 });
}
return NextResponse.json(post);
}
export async function DELETE(request, { params }) {
const { id } = params;
await deletePost(id);
return NextResponse.json({ message: "Post deleted" });
}
Middleware
// middleware.js
import { NextResponse } from "next/server";
export function middleware(request) {
// Check authentication
const token = request.cookies.get("token");
if (!token && request.nextUrl.pathname.startsWith("/dashboard")) {
return NextResponse.redirect(new URL("/login", request.url));
}
// Add custom headers
const response = NextResponse.next();
response.headers.set("x-custom-header", "custom-value");
return response;
}
export const config = {
matcher: ["/dashboard/:path*", "/api/:path*"],
};
Best Practices
1. Gunakan Server Components Secara Default
Hanya gunakan Client Components ketika Anda membutuhkan interaktivitas atau browser APIs.
2. Optimasi Data Fetching
// Good: Fetch data di level yang tepat
export default async function Layout({ children }) {
const user = await getUser(); // Fetch sekali untuk semua child routes
return (
<div>
<UserProvider user={user}>{children}</UserProvider>
</div>
);
}
3. Gunakan Streaming untuk UX yang Lebih Baik
import { Suspense } from "react";
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<div>Loading analytics...</div>}>
<Analytics />
</Suspense>
<Suspense fallback={<div>Loading posts...</div>}>
<Posts />
</Suspense>
</div>
);
}
4. Error Boundaries yang Granular
Tempatkan error.js di level yang tepat untuk error handling yang lebih spesifik.
Kesimpulan
Next.js App Router memberikan cara yang lebih modern dan powerful untuk membangun aplikasi React. Dengan Server Components, improved data fetching, dan file-based routing yang fleksibel, Anda dapat membangun aplikasi yang lebih performant dan maintainable.
Mulai dengan migrasi bertahap dari Pages Router, dan manfaatkan fitur-fitur baru seperti parallel routes dan intercepting routes untuk menciptakan user experience yang lebih baik.


