Private Folder (_folder)
Menyembunyikan folder dari routing dengan prefix underscore.
Aturan main: underscore berarti "bukan route"
Di App Router, setiap folder di dalam app/ berpotensi menjadi segmen URL. Tapi kadang kamu butuh folder biasa untuk kerapian: menaruh komponen, helper, atau query yang hanya dipakai satu section. Kalau folder itu dinamai components, Next.js akan mengira itu route /dashboard/components dan menampilkan 404.
Solusinya sederhana: awali nama folder dengan underscore. Folder _components, _lib, atau _utils diabaikan total oleh router: tidak menjadi URL, tidak bisa diakses publik lewat browser, tapi tetap bisa di-import seperti file biasa.
app/dashboard/
├── _components/
│ └── Chart.tsx # hanya dipakai halaman dashboard
├── _lib/
│ └── queries.ts # query khusus dashboard
├── page.tsx → /dashboard
└── settings/page.tsx → /dashboard/settings
Contoh 1: colocation komponen khusus section
Colocation artinya menaruh kode sedekat mungkin dengan tempat yang memakainya. Komponen yang hanya dipakai dashboard tidak perlu dilempar ke folder components/ global yang lama-lama penuh sesak:
// app/dashboard/_components/StatsCard.tsx
export default function StatsCard({ label, value }: { label: string; value: number }) {
return (
<div className="rounded border p-4">
<p className="text-sm text-gray-500">{label}</p>
<p className="text-2xl font-bold">{value}</p>
</div>
);
}// app/dashboard/page.tsx
import StatsCard from "./_components/StatsCard";
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
<StatsCard label="Total pesanan" value={128} />
</div>
);
}Import-nya relatif biasa (./_components/StatsCard), tidak ada perlakuan khusus. Keuntungannya: saat kamu menghapus section dashboard, komponennya ikut terhapus dan tidak menjadi sampah yang tertinggal di folder global.
Contoh 2: helper dan query yang colocated
Bukan cuma komponen, fungsi helper pun bisa tinggal serumah dengan route-nya:
// app/dashboard/_lib/queries.ts
export async function getOrderStats() {
// di aplikasi nyata: query ke database di sini
return { total: 128, pending: 12 };
}// app/dashboard/page.tsx
import { getOrderStats } from "./_lib/queries";
export default async function DashboardPage() {
const stats = await getOrderStats();
return <h1>Total pesanan: {stats.total}</h1>;
}Pola ini membuat setiap section menjadi unit yang mandiri: page, komponen, dan query-nya ada dalam satu folder. Tim yang besar bisa bekerja di section masing-masing tanpa saling menginjak file.
Private folder vs route group
Keduanya tidak muncul di URL, tapi fungsinya berlawanan. Tabel ini membantu tidak tertukar:
_folder | (folder) | |
|---|---|---|
| Masuk URL? | Tidak, diabaikan total | Tidak, tapi isinya jadi route |
| Bisa berisi page.tsx yang jadi route? | Tidak | Ya |
| Bisa punya layout.tsx? | Tidak berpengaruh | Ya, layout khusus grup |
| Kegunaan | Menyimpan kode non-route | Mengelompokkan route |
Cara mengingatnya: underscore _ artinya "folder ini bukan bagian dari routing", sedangkan kurung ( ) artinya "folder ini hanya pembungkus organisasi, isinya tetap route".
Catatan: "private" di sini artinya private dari routing, bukan private dari kode. File di dalamnya tetap bisa di-import dari file mana pun. Kalau butuh benar-benar menyembunyikan sesuatu (misalnya API secret), jangan taruh di kode yang bisa terkirim ke client sama sekali, simpan di environment variable dan akses hanya dari Server Component atau Route Handler.
Tantangan
Colocation
Pindahkan satu komponen yang hanya dipakai halaman dashboard ke app/dashboard/_components/. Pastikan halaman tetap jalan dan URL tidak berubah.