Kenapa ini penting
Di React + Vite kamu harus memasang React Router dan menulis daftar rute secara manual. Di Next.js, struktur folder adalah daftar rute. Kamu membuat folder, menaruh file page.js, dan URL-nya langsung ada. Cara ini membuat proyek besar tetap mudah dinavigasi: kalau mau mencari kode halaman /blog/kategori, kamu tahu persis foldernya.
Konsep inti
Folder = segmen URL, page.js = halaman
Analoginya seperti gedung perkantoran. Folder adalah lantai dan ruangan, sedangkan page.js adalah papan nama di pintu. Ruangan tanpa papan nama tidak bisa dikunjungi tamu.
| File | URL |
|---|---|
app/page.js |
/ |
app/tentang/page.js |
/tentang |
app/blog/page.js |
/blog |
app/blog/kategori/page.js |
/blog/kategori |
app/blog/_components/Kartu.js |
tidak bisa diakses (folder privat) |
Aturan penting:
- Sebuah rute baru bisa diakses publik kalau ada file
page.js(atauroute.jsuntuk API) di foldernya. - File lain di folder yang sama (komponen, util) aman diletakkan di situ dan tidak menjadi rute. Ini disebut colocation.
- Folder berawalan garis bawah, seperti
_components, adalah folder privat: diabaikan total oleh sistem routing. - Ekstensi yang didukung untuk halaman:
.js,.jsx,.tsx.
layout.js: UI yang dipakai bersama
layout.js membungkus semua halaman di folder itu dan subfoldernya lewat prop children. Saat user berpindah halaman, layout tidak di-render ulang dan state-nya tetap. Cocok untuk navbar, sidebar, dan footer. Detailnya dibahas di modul Layout.
Pindah halaman dengan <Link>
Jangan pakai <a href> biasa untuk link internal. Pakai komponen Link dari next/link:
- Navigasi terjadi di sisi client tanpa reload halaman penuh, jadi layout dan state tetap.
- Halaman tujuan di-prefetch (dimuat duluan) saat link terlihat di layar, sehingga perpindahan terasa instan. Prefetch hanya aktif di mode produksi.
Untuk link ke situs luar, <a href="https://..."> biasa tetap tepat.
useRouter dan usePathname
Dua hook dari next/navigation (bukan next/router, itu milik Pages Router). Keduanya hanya bisa dipakai di Client Component (file dengan 'use client' di baris pertama; dibahas lengkap di modul 6).
usePathname()mengembalikan path saat ini, misalnya'/blog/halo-nextjs'. Berguna untuk menandai menu yang aktif.useRouter()memberi fungsi navigasi lewat kode:router.push('/blog')pindah halaman dan menambah riwayat.router.replace('/login')pindah halaman tanpa menambah riwayat.router.back()danrouter.forward()seperti tombol browser.router.refresh()meminta ulang data Server Component untuk halaman ini tanpa kehilangan state di client.router.prefetch('/blog')memuat duluan sebuah rute.
Aturan praktis: kalau bisa pakai <Link>, pakai <Link>. useRouter untuk kasus "pindah halaman setelah sesuatu terjadi", misalnya setelah form berhasil dikirim.
Route group: (nama)
Kadang kamu ingin mengelompokkan halaman tanpa mengubah URL. Bungkus nama folder dengan tanda kurung: app/(marketing)/tentang/page.js tetap menjadi /tentang. Kegunaannya:
- Merapikan folder (bagian marketing, bagian toko, bagian admin).
- Memberi layout berbeda untuk kelompok halaman tertentu:
app/(marketing)/layout.jshanya membungkus halaman di dalam(marketing).
Hati-hati: dua group tidak boleh menghasilkan URL yang sama, misalnya (marketing)/tentang/page.js dan (toko)/tentang/page.js akan bentrok.
Halaman 404: not-found.js
File app/not-found.js menampilkan UI saat URL tidak cocok dengan rute mana pun. Kamu juga bisa memanggil fungsi notFound() dari next/navigation di dalam halaman untuk memicu 404 secara manual, misalnya saat artikel tidak ditemukan di database. Kita pakai ini di modul berikutnya.
Contoh kode
Struktur yang akan kita bangun:
app/
├── layout.js
├── page.js → /
├── not-found.js → UI 404
├── (marketing)/
│ ├── layout.js → layout khusus marketing
│ └── tentang/page.js → /tentang
├── blog/
│ ├── layout.js → navbar blog
│ └── page.js → /blog
└── components/ (atau di luar app/, misalnya /components)
Komponen navigasi dengan penanda menu aktif. Karena memakai hook, file ini harus Client Component:
// components/NavLink.jsx
'use client'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
export default function NavLink({ href, children }) {
const pathname = usePathname() // → misalnya '/blog/halo-nextjs'
const aktif = pathname === href || pathname.startsWith(href + '/')
return (
<Link
href={href}
className={aktif ? 'font-bold text-blue-600' : 'text-gray-600 hover:text-gray-900'}
>
{children}
</Link>
)
}
Layout blog memakai NavLink. Layout sendiri tetap Server Component:
// app/blog/layout.jsx
import NavLink from '@/components/NavLink'
export default function BlogLayout({ children }) {
return (
<section className="mx-auto max-w-2xl p-6">
<nav className="mb-6 flex gap-4">
<NavLink href="/blog">Semua artikel</NavLink>
<NavLink href="/tentang">Tentang</NavLink>
</nav>
{children}
</section>
)
}
// → di /blog, menu "Semua artikel" tebal biru; di /tentang, menu "Tentang" yang aktif
Halaman daftar artikel dengan <Link>:
// app/blog/page.jsx
import Link from 'next/link'
const posts = [
{ slug: 'halo-nextjs', judul: 'Halo, Next.js!' },
{ slug: 'routing-itu-folder', judul: 'Routing Itu Folder' },
]
export default function BlogPage() {
return (
<ul className="space-y-2">
{posts.map((post) => (
<li key={post.slug}>
<Link href={`/blog/${post.slug}`} className="underline">
{post.judul}
</Link>
</li>
))}
</ul>
)
}
// → klik link: URL berubah ke /blog/halo-nextjs tanpa reload penuh
Navigasi lewat kode dengan useRouter:
// components/TombolKembali.jsx
'use client'
import { useRouter } from 'next/navigation' // BUKAN 'next/router'
export default function TombolKembali() {
const router = useRouter()
return (
<div className="flex gap-2">
<button onClick={() => router.back()}>← Kembali</button>
<button onClick={() => router.push('/blog')}>Ke daftar blog</button>
</div>
)
}
Route group dengan layout sendiri, dan halaman 404 kustom:
// app/(marketing)/layout.jsx, hanya membungkus halaman di dalam (marketing)
export default function MarketingLayout({ children }) {
return <div className="bg-amber-50 p-6">{children}</div>
}
// app/(marketing)/tentang/page.jsx, URL-nya /tentang, bukan /marketing/tentang
export default function TentangPage() {
return <h1 className="text-2xl font-bold">Tentang kami</h1>
}
// app/not-found.jsx
import Link from 'next/link'
export default function NotFound() {
return (
<main className="p-6">
<h1 className="text-2xl font-bold">404 - Halaman tidak ada</h1>
<Link href="/" className="underline">Kembali ke beranda</Link>
</main>
)
}
// → buka /halaman-ngawur → status 404 dan UI di atas tampil
Kesalahan umum
- Import
useRouterdarinext/router. Itu API Pages Router. Di App Router pakaiimport { useRouter } from 'next/navigation'. - Memakai hook di Server Component.
usePathnamedanuseRouterhanya jalan di file dengan'use client'. Kalau lupa, muncul error yang menyuruh menambahkan directive tersebut. - Membuat folder tanpa
page.jslalu heran kenapa 404. Folder saja tidak cukup; harus adapage.jsdi dalamnya. - Memakai
<a href="/blog">untuk link internal. Halaman akan reload penuh, state hilang, dan tidak ada prefetch. Pakai<Link>. - Mengira route group masuk ke URL.
(marketing)tidak muncul di URL. Kalau mau segmen URL sungguhan, pakai folder biasa tanpa kurung.
Latihan
- Mudah, Buat halaman
/kontakyang menampilkan alamat email, lalu tambahkan link ke halaman itu dari halaman utama memakai<Link>. - Sedang, Buat route group
(toko)berisi/menudan/keranjangdengan layout yang punya header "Kedai Kopi". Halaman/tentangdi luar group tidak boleh memakai header itu. - Menantang, Buat komponen
Breadcrumb(Client Component) yang membacausePathname()dan menampilkan jejak sepertiBeranda / blog / halo-nextjs, dengan tiap bagian berupa<Link>ke path-nya.
Lihat solusi
1.
// app/kontak/page.jsx
export default function KontakPage() {
return <p>Email kami: halo@kedaikopi.id</p>
}
// app/page.jsx
import Link from 'next/link'
export default function HomePage() {
return (
<main className="p-6">
<h1>Kedai Kopi</h1>
<Link href="/kontak" className="underline">Hubungi kami</Link>
</main>
)
}
2.
app/
├── (toko)/
│ ├── layout.jsx
│ ├── menu/page.jsx → /menu
│ └── keranjang/page.jsx → /keranjang
└── tentang/page.jsx → /tentang (tanpa header toko)
// app/(toko)/layout.jsx
export default function TokoLayout({ children }) {
return (
<>
<header className="bg-amber-900 p-4 text-white">Kedai Kopi</header>
<main className="p-6">{children}</main>
</>
)
}
// app/(toko)/menu/page.jsx
export default function MenuPage() {
return <h1>Menu hari ini</h1>
}
// app/(toko)/keranjang/page.jsx
export default function KeranjangPage() {
return <h1>Keranjang kamu kosong</h1>
}
3.
// components/Breadcrumb.jsx
'use client'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
export default function Breadcrumb() {
const pathname = usePathname() // → '/blog/halo-nextjs'
const bagian = pathname.split('/').filter(Boolean) // → ['blog', 'halo-nextjs']
return (
<nav className="text-sm text-gray-500">
<Link href="/">Beranda</Link>
{bagian.map((nama, i) => {
const href = '/' + bagian.slice(0, i + 1).join('/')
return (
<span key={href}>
{' / '}
<Link href={href}>{nama}</Link>
</span>
)
})}
</nav>
)
}
// → Beranda / blog / halo-nextjs
Ringkasan
- Folder di
app/menjadi segmen URL; rute baru publik kalau adapage.js. - File lain bisa diletakkan di folder rute dengan aman; folder
_privatdiabaikan routing. - Pakai
<Link>darinext/linkuntuk navigasi internal: tanpa reload dan dengan prefetch. usePathnamedanuseRouterdiimpor darinext/navigationdan hanya bisa dipakai di Client Component.- Route group
(nama)mengelompokkan halaman dan memberi layout sendiri tanpa mengubah URL;not-found.jsmenampilkan UI 404.