# Struktur Proyek Next.js yang Skalabel dengan App Router

> Struktur folder yang telah teruji untuk proyek Next.js 15 App Router yang bisa berkembang dari side project hingga SaaS produksi.

URL: https://www.matthewswong.com/id/blog/nextjs-project-structure-scalable/

- Author: Matthews Wong (https://www.matthewswong.com/id/about/)
- Published: 2025-06-10
- Updated: 2026-10-17
- Category: Web Dev

Setiap proyek Next.js dimulai sederhana — beberapa halaman, beberapa komponen, mungkin folder lib. Tiga bulan kemudian, kamu mencari fungsi utilitas yang terkubur di suatu komponen, dan direktori app-mu terlihat seperti gudang digital yang berantakan. Saya sudah membangun ulang struktur ini dua kali di proyek nyata, dan pola yang saya bagikan ini adalah yang benar-benar saya gunakan untuk aplikasi web.

## Mengapa Struktur Lebih Penting di App Router

App Router memperkenalkan model mental yang berbeda secara fundamental. File di dalam app/ bisa berupa Server Components, Client Components, layout, loading state, error boundaries, atau API routes — semuanya berkolokasi. Tanpa strategi folder yang disengaja, ini menjadi kacau dengan cepat. Struktur yang saya gunakan memisahkan concern dengan jelas: routing ada di app/[locale]/(pages)/, komponen bersama di app/components/, utilitas di app/lib/, dan seterusnya.

### Pola Route Group

Route group (folder yang dibungkus dalam tanda kurung) memungkinkan kamu mengorganisir route tanpa memengaruhi URL. Saya menggunakan (pages) untuk mengelompokkan semua halaman yang dihadapi pengguna di bawah awalan locale tanpa menambahkan 'pages' ke URL. Ini berarti /en/about diselesaikan dari app/[locale]/(pages)/about/page.tsx. Kamu juga bisa menggunakan route group untuk menerapkan layout berbeda — misalnya, grup (dashboard) dengan layout sidebar terpisah dari halaman (marketing) dengan navbar biasa.

### Kolokasi vs Pemisahan

Perdebatan umum: apakah komponen fitur harus berada di samping route-nya, atau di folder components terpusat? Aturan saya: jika komponen hanya digunakan oleh satu route, kolokasikan. Jika dibagi dua atau lebih route, pindahkan ke app/components/. Ini mencegah masalah 'di mana ini tinggal?'. Untuk blog, BlogCard dan BlogFilters hanya milik listing blog. Tapi Navbar dan Footer bersifat global, jadi mereka ada di app/components/layout/.

> Gunakan barrel export (file index.ts) di folder komponen kamu. Daripada mengimpor dari path dalam seperti @/components/ui/Button, ekspor semuanya dari @/components/ui/index.ts dan impor '{ Button, Input, Modal }' from '@/components/ui'. Ini membuat refactoring nama file internal menjadi mudah.

## TypeScript Path Alias dan Barrel Export

Path alias tidak bisa dinegosiasikan pada proyek apapun dengan lebih dari 3 developer atau yang akan bertahan lebih dari sebulan. Import relatif seperti '../../../lib/db' adalah mimpi buruk pemeliharaan. Konfigurasikan paths tsconfig.json sekali, gunakan @/lib/db di mana saja. Pola singleton Prisma client juga kritis — menginstansiasi PrismaClient pada setiap hot module reload di development akan menghabiskan connection pool database kamu dalam hitungan menit.

### Environment Variables dan Konfigurasi

Simpan semua parsing environment variable di satu tempat: app/lib/env.ts. Gunakan t3-env atau pola berbasis zod untuk memvalidasi env var saat startup, bukan saat runtime ketika pengguna memicu code path. Ini menangkap STRIPE_SECRET_KEY atau DATABASE_URL yang hilang sebelum kamu deploy, bukan di produksi pukul 2 pagi. Ekspor konstanta bertipe dari file ini sehingga setiap konsumen mendapatkan autocomplete yang tepat.

## Organisasi Berbasis Fitur vs Berbasis Layer

Berbasis layer (controllers/, services/, repositories/) bekerja dengan baik untuk aplikasi backend NestJS. Untuk frontend Next.js, saya lebih suka pengelompokan berbasis fitur di dalam components/. Folder fitur 'products' berisi ProductCard.tsx, ProductFilters.tsx, ProductTable.tsx, dan useProducts.ts. Ini memudahkan menghapus atau memindahkan seluruh fitur tanpa mencari di banyak direktori.

> **Hindari nesting route group terlalu dalam**

> Nesting route group lebih dari dua level menciptakan rantai pewarisan layout Next.js yang sulit dipahami. Jika kamu menemukan diri di app/[locale]/(pages)/(dashboard)/(settings)/profile/page.tsx, pertimbangkan untuk meratakan. Route group adalah untuk berbagi layout, bukan untuk mengekspresikan hierarki yang tidak ada di URL.

## Menangani i18n dalam Struktur

Segmen dinamis [locale] di root memberikan internasionalisasi tanpa kekacauan direktori i18n terpisah. Semua terjemahan ada di messages/en.json dan messages/id.json di root proyek. Library next-intl menangani sisanya — useTranslations() di Client Components, getTranslations() di Server Components. Jaga namespace kunci terjemahan konsisten dengan struktur fitur: blog.posts.*, dashboard.sidebar.*, common.* untuk label bersama.

## Checklist Akhir Sebelum Kamu Berkembang

Sebelum proyek berkembang melampaui MVP, jalankan ini: (1) Apakah semua utilitas bersama ada di lib/, bukan tersebar di components? (2) Apakah semua Server Component tetap server-side — tidak ada import useState atau useEffect? (3) Apakah Prisma diinstansiasi sebagai singleton? (4) Apakah API route menggunakan penanganan error yang tepat dengan bentuk respons yang konsisten? (5) Apakah environment variable divalidasi saat startup? (6) Apakah ada satu konfigurasi tsconfig paths yang digunakan seluruh tim?

## Sumber & Bacaan Lanjutan

- Vercel / Next.js — Struktur Proyek & Konvensi File — https://nextjs.org/docs/app/getting-started/project-structure
- Vercel — Cara Kami Mengoptimalkan Import Package di Next.js — https://vercel.com/blog/how-we-optimized-package-imports-in-next-js
- TypeScript — Referensi tsconfig paths — https://www.typescriptlang.org/tsconfig#paths

## Source links

- https://nextjs.org/docs/app/getting-started/project-structure
- https://vercel.com/blog/how-we-optimized-package-imports-in-next-js
- https://www.typescriptlang.org/tsconfig#paths

## FAQ

### Mengapa menggunakan route group seperti (pages) di App Router, dan apakah itu memengaruhi URL?

Route group — folder yang dibungkus dalam tanda kurung — memungkinkan kamu mengorganisir route dan berbagi layout tanpa menambahkan segmen apapun ke URL. Misalnya, menempatkan halaman di bawah app/[locale]/(pages)/ berarti /en/about diselesaikan dari app/[locale]/(pages)/about/page.tsx, menjaga URL tetap bersih. Kamu juga bisa menggunakan route group terpisah seperti (dashboard) dan (marketing) untuk menerapkan layout berbeda pada bagian app yang berbeda.

### Kapan sebuah komponen harus dikolokasikan di samping route-nya versus dipindahkan ke folder components bersama?

Aturannya sederhana: jika komponen hanya digunakan oleh satu route, kolokasikan bersama route itu. Begitu dua atau lebih route berbagi komponen, pindahkan ke app/components/. Misalnya, BlogCard dan BlogFilters berada di dalam folder route blog, sementara Navbar dan Footer — yang digunakan di setiap halaman — ada di app/components/layout/.

### Mengapa TypeScript path alias dan barrel export dianggap wajib untuk proyek yang skalabel?

Import relatif seperti '../../../lib/db' menjadi mimpi buruk pemeliharaan seiring proyek berkembang, terutama di tim dengan lebih dari tiga developer. Mengonfigurasi paths tsconfig.json sekali (misalnya @/lib/db) dan menggunakan barrel export (index.ts) berarti reorganisasi file internal tidak pernah memutus import statement di tempat lain. Barrel export juga memungkinkan import yang bersih seperti '{ Button, Input, Modal }' from '@/components/ui' daripada path per-file yang dalam.

### Mengapa Prisma client harus diinstansiasi sebagai singleton, dan apa yang terjadi jika tidak?

Dalam mode development Next.js, hot module reload dapat memicu banyak instansiasi PrismaClient. Setiap instance membuka connection pool database-nya sendiri, sehingga tanpa pola singleton, pool habis dalam hitungan menit. Singleton memastikan hanya ada satu instance PrismaClient per proses Node.js, menjaga penggunaan koneksi tetap terprediksi.

### Bagaimana menyimpan parsing environment variable di satu file meningkatkan keandalan?

Memusatkan semua parsing env var di app/lib/env.ts — yang divalidasi dengan t3-env atau pola berbasis zod — menangkap variabel yang hilang seperti DATABASE_URL atau STRIPE_SECRET_KEY saat aplikasi startup, bukan saat pengguna memicu code path di runtime. Mengekspor konstanta bertipe dari file tersebut juga menghilangkan akses process.env mentah yang tersebar di seluruh codebase, memberikan autocomplete yang tepat dan mencegah miskonfigurasi diam yang muncul di produksi.

---

- HTML version: https://www.matthewswong.com/id/blog/nextjs-project-structure-scalable/
- English: https://www.matthewswong.com/en/blog/nextjs-project-structure-scalable/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
