Next.js App Router Patterns (2026)
Next.js12 min read

Next.js App Router Patterns (2026)

Pola struktur folder, data fetching, dan boundary error/loading yang bikin App Router rapi dan scalable. Dari pengalaman bikin production app di DaunsCode.

DaunsCode Editorial Team

admin@daunscode.com • 15 Feb 2026

Kenapa App Router?

Next.js 13 ngeluarin App Router dan sejak itu dunia React development berubah. Kalau sebelumnya kita pakai Pages Router yang straightforward, App Router bawa konsep baru: React Server Components, nested layouts, streaming, dan lain lain.

Awalnya banyak yang skeptis (termasuk kita). Tapi setelah pake di production, harus diakui App Router itu game changer. Terutama buat performance dan developer experience.

Struktur Folder

Ini yang paling basic tapi sering bikin bingung. Rekomendasi kita:

app/
  (marketing)/
    page.tsx          # Home
    about/page.tsx
    contact/page.tsx
    layout.tsx         # Layout buat marketing pages
  (app)/
    dashboard/page.tsx
    settings/page.tsx
    layout.tsx         # Layout buat app pages (with sidebar)
  blog/
    page.tsx           # Blog listing
    [slug]/page.tsx    # Blog detail
  api/
    admin/
      articles/route.ts
  layout.tsx           # Root layout
  globals.css

Beberapa prinsip:

  • Route groups pakai (nama) buat grouping route yang share layout tanpa ngaruh ke URL
  • API routes taruh di api/ biar kepisah jelas dari page routes
  • Colocation file yang related taro di folder yang sama

Server Components vs Client Components

Ini konsep paling fundamental di App Router. Defaultnya semua komponen itu Server Components.

Server Components (default):

  • Render di server, hasilnya dikirim sebagai HTML
  • Bisa langsung akses database, file system, environment variables
  • Bundle size 0 di client
  • Nggak bisa pakai hooks (useState, useEffect, dll)

Client Components (pakai "use client"):

  • Render di client (browser)
  • Bisa pakai hooks dan browser API
  • Masuk ke JavaScript bundle yang didownload user
  • Harus ditandai dengan "use client" di atas file

Rules of thumb:

Server Component kalau:
- Fetch data
- Akses backend resource
- Render static content
- Nggak butuh interactivity

Client Component kalau:
- Butuh useState, useEffect
- Event handlers (onClick, onChange)
- Browser API (localStorage, window)
- Third party library yang butuh browser

Contoh pattern yang bagus:

// app/blog/page.tsx (Server Component)
import { getArticles } from "@/lib/blog";
import ArticleList from "@/components/article-list";

export default async function BlogPage() {
  const articles = await getArticles(); // fetch di server
  return <ArticleList articles={articles} />; // pass ke client
}

// components/article-list.tsx (Client Component)
"use client";
import { useState } from "react";

export default function ArticleList({ articles }) {
  const [search, setSearch] = useState("");
  const filtered = articles.filter(a => 
    a.title.toLowerCase().includes(search.toLowerCase())
  );
  return (
    <>
      <input value={search} onChange={e => setSearch(e.target.value)} />
      {filtered.map(article => <ArticleCard key={article.id} {...article} />)}
    </>
  );
}

Data fetching di server, interactivity di client. Best of both worlds.

Data Fetching Patterns

Di App Router, data fetching berubah total dari Pages Router. Nggak ada lagi getServerSideProps atau getStaticProps.

Pattern 1: Direct Fetch di Server Component

// Yang paling simpel
export default async function Page() {
  const data = await fetch("https://api.example.com/data");
  const json = await data.json();
  return <div>{json.title}</div>;
}

Next.js otomatis deduplicate fetch yang sama dan cache hasilnya.

Pattern 2: Database Query Langsung

import { supabase } from "@/lib/supabase-server";

export default async function BlogPage() {
  const { data: articles } = await supabase
    .from("articles")
    .select("*")
    .eq("status", "published")
    .order("published_at", { ascending: false });

  return <ArticleGrid articles={articles ?? []} />;
}

Di Server Component, kamu bisa query database langsung. Nggak perlu API route perantara.

Pattern 3: Parallel Data Fetching

export default async function DashboardPage() {
  // Fetch semuanya parallel, bukan sequential
  const [stats, articles, users] = await Promise.all([
    getStats(),
    getRecentArticles(),
    getActiveUsers(),
  ]);

  return (
    <>
      <StatsOverview stats={stats} />
      <RecentArticles articles={articles} />
      <ActiveUsers users={users} />
    </>
  );
}

Jangan fetch sequential kalau bisa parallel. Bedanya bisa signifikan.

Loading dan Error Boundaries

App Router punya convention buat loading dan error states:

blog/
  page.tsx
  loading.tsx    # Auto jadi loading UI
  error.tsx      # Auto jadi error boundary
  not-found.tsx  # Custom 404

loading.tsx

export default function Loading() {
  return (
    <div className="animate-pulse space-y-4">
      <div className="h-8 bg-gray-200 rounded w-3/4" />
      <div className="h-4 bg-gray-200 rounded w-full" />
      <div className="h-4 bg-gray-200 rounded w-5/6" />
    </div>
  );
}

Next.js otomatis wrap page kamu dalam Suspense boundary pake loading.tsx ini sebagai fallback.

error.tsx

"use client"; // error.tsx HARUS client component

export default function Error({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div>
      <h2>Ada yang error nih</h2>
      <p>{error.message}</p>
      <button onClick={reset}>Coba lagi</button>
    </div>
  );
}

Error boundary ini nangkep error di segment nya dan kasih opsi retry.

Metadata dan SEO

App Router bikin SEO jadi gampang banget:

import type { Metadata } from "next";

export const metadata: Metadata = {
  title: "Blog DaunsCode",
  description: "Artikel seputar web development dan teknologi",
  openGraph: {
    title: "Blog DaunsCode",
    description: "Artikel seputar web development",
    images: ["/og-image.png"],
  },
};

Buat dynamic metadata (misalnya blog detail):

export async function generateMetadata({ params }): Promise<Metadata> {
  const post = await getPost(params.slug);
  return {
    title: post.title,
    description: post.excerpt,
  };
}

Metadata di-generate di server, jadi SEO crawler dapet full metadata tanpa perlu JavaScript.

Middleware

Middleware jalan di edge sebelum request sampai ke route:

// middleware.ts (root level)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  // Redirect, rewrite, atau block request
  if (request.nextUrl.pathname.startsWith("/admin")) {
    // Check auth
    const token = request.cookies.get("auth-token");
    if (!token) {
      return NextResponse.redirect(new URL("/login", request.url));
    }
  }
  return NextResponse.next();
}

export const config = {
  matcher: ["/admin/:path*"],
};

Powerful buat auth check, rate limiting, atau geo based redirects.

Tips dari Production

Beberapa hal yang kita pelajari dari pake App Router di production:

  1. Default ke Server Component jangan buru buru tambahin "use client". Coba dulu apakah bisa tetep server
  2. Granular client boundary daripada bikin satu page jadi client, pecah jadi komponen kecil yang cuma bagian interaktifnya yang jadi client
  3. Parallel fetch selalu pakai Promise.all kalau ada multiple fetch yang independen
  4. Revalidation strategy pikirin kapan data harus di refresh. Static buat yang jarang berubah, dynamic buat yang sering
  5. Error handling selalu sediain error.tsx biar user nggak liat blank page

Kesimpulan

App Router itu investment yang worth it kalau kamu bikin app yang serius. Learning curve nya emang ada, tapi begitu paham pattern nya, development jadi lebih cepet dan hasilnya lebih performant.

Key takeaway: server components buat fetch dan render, client components cuma buat interactivity, parallel fetch selalu, dan error/loading boundary di setiap route segment.

Ready to build the next big thing?

DaunsCode siap bantu arsitektur, UI, dan implementasi produk digital yang scalable untuk bisnis kamu.

Let's Talk Project
Next.jsApp RouterArchitecture
← Kembali ke listing artikel
ContactBlogPortfolioHome