Panduan Jadi Mualaf React Router v7 (formerly Remix)
Di tulisan ini banyak opini pribadi gue. Jadi, hati-hati, ya.
Ada banyak hal yang bisa dijadiin alesan buat nggak pake Next.js, mulai dari butuh resource intensif buat dev server, versi stable yang nggak stable, atau bisa juga alasan moral. Ironisnya, web ini juga dibikin pake Next.js~
Dulu, Next.js seolah kayak mesias buat gue yang males setup masalah routing di React. Sampe kebabalsan 5 tahun pake Next.js dan jadi buzzernya di semua platform yang gue punya.
Dari tahun lalu udah nyoba nyari alternatif yang cocok buat gue dan sempet juga nyoba Remix, tapi belum nemu alasan yang kuat buat bener-bener pindah. Nggak sekuat alesan gue dulu pake Vim. Alesan-alesan teknis ini gue rasa masih selalu bisa diakalin walaupun sambil love-hate sama Next.js. Tapi, ya gimana lagi belum nemu yang cocok saat itu.
Akhirnya beberapa waktu lalu gue nemu alesan yang kuat banget buat pindah dan jadi mualaf React Router. Saat itu juga gue langsung cari alternatif yang bener-bener bisa diandelin. Beberapa kandidatnya nggak banyak:
- Tanstack Start
- Sveltekit
- Remix (sekarang React Router v7)
Kenapa Nggak Tanstack Start atau Sveltekit?
Ada beberapa alasan kenapa gue nggak pake Tanstack Start:
- Nggak suka sama APIs-nya, sintaksisnya kayak bloated. Di satu sisi bagus, nggak ada magic yang disembunyiin, tapi di sisi lain terlalu ribet buat orang pragmatis kayak gue.
- Masih baru banget. Ini nggak begitu masalah, karena gue suka nyobain teknologi baru, tapi, gue juga butuh solusi yang bener-bener bisa diandelin buat proyek gue berikutnya.
- Sejujurnya dokumentasinya lebih bikin bingung dibanding Remix. Kalo dibilang deskriptif, iya, tapi nggak straight to the point.
Walaupun begitu, gue suka beberapa hal dari Tanstack Start, kayak server functions dan end-to-end type safety-nya.
Sementara Sveltekit, gue harus belajar Svelte dulu. Gue bakal gas ini kalo ini proyek kantor~
Kenapa Remix?
Selain karena gue udah pelajarin dasar-dasar Remix dari tahun lalu, gue juga betah pakenya. Ini yang bikin gue akhirnya mutusin buat pake Remix. Tapi, saat gue mau coba baca-baca lagi, ternyata Remix yang baru ini udah di-merge ke React Router v7.
Bedanya Remix dan React Router
Jadi, simpelnya, Remix ini dulunya tuh kayak "branch" dari React Router buat solusi full-stack pake React yang didasari React Router. Bener-bener rely heavily on React Router. Itu kenapa mereka bilangnya, Remix ini kayak "thin layer"-nya React Router. Karena emang sebagian besar itu fitur-fiturnya React Router.
Kemudian, mereka bikin banyak perubahan di Remix yang baru ini. Banyak orang ini bakal jadi Remix v3, termasuk gue. Tapi, ternyata perubahan-perubahan itu dijadiin React Router v7 dalam bentuk "framework mode". Jadi, fitur-fitur Remix yang udah ada dibawa ke React Router yang baru dan lo bisa pake itu hari ini.
React Router sekarang jadinya punya 3 mode:
- Declarative: ini cuma buat router aja, yang mungkin biasa atau udah lo pake dari jauh-jauh hari.
- Framework: ini yang baru di React Router v7, setara sama Next.js.
- Data: ini lebih kayak versi mini dari framework, ada beberapa yang lo nggak dapet di mode ini, kayak
<Link prefetech>
atau meta contohnya.
Buat pengguna React Router v6, lo bisa upgrade ke v7 buat nyoba fitur-fitur baru dan khusunya di mode framework. Buat pengguna Remix v2, lo juga bisa "switch" ke React Router v7 buat dapet pengalaman yang harusnya lo dapetin di "Remix v3".
Remix ke depannya bakal tetep lanjut, beberapa waktu lalu Ryan Florence sama Michael Jackson nulis kalo Remix bakal tetep jadi solusi full-stack tapi pake teknologi yang bukan lagi React. Mereka bakal nge-fork Preact yang bakal dijadiin dasar framework-nya. Preact sendiri adalah alternatif React yang lebih cepet dengan API yang sama. Walaupun begitu, React Router bakal tetep dapet update karena udah ada timnya sendiri yang fokus di situ.
Kalo boleh gue ringkas, lo bisa pake React Router v7 mode framework buat full-stack yang basisnya React. Nanti, lo bisa nunggu Remix yang baru rilis kalo mau nyoba solusi yang beda di luar dari React. Intinya, dua-duanya bakal jalan bareng.
Karena gue datang dari Next.js, maka ada beberapa hal yang gue cari dan jadi alesan gue buat pindah ke React Router v7 (yang selanjutnya bakal gue sebut sebagai React Router):
Dev Server yang Lebih Ringan dan Cepet
Gue masih inget banget waktu masih pake Next.js, memory gue abis 3-4GB cuma buat jalain dev server doang, anjing. Klaim doang 10x faster bla-bla-bla, gue nyimpen perubahan aja nggak langsung reflect, musti nunggu beberapa detik dulu. Segitu gue pake Macbook M1.
Sementara di Remix, kalo gue bikin perubahan itu langsung reflek in no time alias instan banget. Memory juga nggak abis banyak, sejauh ini nggak pernah sampe lebih dari 1GB, tapi gue nggak tau kalo proyeknya lebih kompleks, let's see.
Ini sih yang jadi alesan yang signifikan gue suka sama React Router.
Routing yang Sentris
Gue dulu suka banget sama file-system routing-nya Next.js, karena bikin routing jadi gampang. Tinggal bikin file doang udah jadi route tanpa setup ini-itu. Tapi, ketika gue udah mulai bikin-bikin produk enterprise-nya Kredibel yang menurut gue cukup kompleks ini bikin gue sadar kalo file-system routing di Next.js ini nggak banget. Alesannya:
- Semakin panjang path, semakin dalem juga struktur foldernya.
- Kalo gue ganti path, gue pindahin lagi struktur foldernya.
- Next.js makin banyak file convention, kayak route groups salah satunya. Ini yang kadang bikin gue bingung buat nyari lokasi file buat route yang gue cari.
- Penamaan file yang sama ini yang bikin susah dicari dan bikin pusing, gue jadi harus semakin spesifik. Ngetik "page.tsx" doang kan nggak cukup, harus lebih spesifik lagi.
Sementara di React Router, mereka punya sistem routing yang sentris lewat file routes.ts. Kalo lo pernah pake Laravel, ini kayak web.php atau api.php lah, cuman di satu file yang sama. Kalo gini lebih bikin gampang. Misal, lo lagi nerusin kerjaan orang atau kerjaan lo sendiri setelah beberapa bulan dan lo lupa lokasi suatu route, lo tinggal buka aja file routes.ts. Di situ lo bisa tau kalo suatu route itu dia di-reference ke component yang mana. Gini contohnya:
Kalo mau ganti path? Tinggal ganti, nggak ada struktur folder yang diubah. Nyari file? nggak ada lagi tuh "page.tsx" yang bikin pusing. Struktur folder? Nggak harus ngikutin struktur path-nya. Dan juga nggak banyak file convention di dalam folder app, lo bisa naruh component atau apapun di folder tersebut tanpa khawatir terekspos ke publik atau perlu nandain pake simbol-simbol kayak _
atau ( )
. Utter woke nonsense!
Selesai itu Next.js!
Data Loading
Kalo di Next.js disebutnya data fetching, di React Router pake istilah data loading. Pada dasarnya sama aja cuman beda istilah aja. Untuk data loading di React Router, kita bisa nge-export fungsi loader
di dalem file yang disebut route module. Route module ini simpelnya file yang lo referensiin di file routes.ts. Kalo ada kode route("/home", "./home.tsx")
, nah, file home.tsx
itu yang disebut route module.
Contoh loader
di React Router:
Ini mirip sama getServerSideProps
-nya Next.js versi pages router. Fungsi tersebut nantinya bakal dieksekusi di server ketika route tersebut di-render buat initial load. Fungsi ini juga bakal dihapus dari client bundle, jadi lo nggak perlu khawatir buat pake API yang cuman boleh jalan di server (contoh: database).
Alternatif dari loader
ini ada lagi, yaitu clientLoader
. Lo udah bisa nebak bedanya apa, kan? Iya, bedanya fungsi ini bakal dieksekusi di client/browser.
Actions
Action ini fitur React Router yang lo bisa pake buat handle form submission. Sama kayak loader
, action
juga ada dua variant: action
yang dieksekusi di server; clientAction
yang dieksekusi di client/browser. Enaknya action di React Router, kita nggak musti bikin file baru cuman buat naro function-nya, alih-alih kita bisa nge-export action
di dalem satu file route module yang sama.
Fungsi action
juga bakal dihapus dari client bundle, jadi lo aman buat pake API yang cuman boleh jalan di server.
End-to-end Type Safety
End-to-end type safety dalam konteks ini simpelnya adalah lo bisa nulis kode di server dan client dengan tipe data yang sama. Lo nggak perlu khawatir dan capek-capek bikin tipe data yang sama di server dan client, React Router bakal ngurusin itu buat lo. Hal ini juga bisa lo di temuin di loader dan action.
Kalo lo liat contoh di atas, loader
itu ngasih return value yang tipe datanya otomatis di-infer. Jadi, ketika lo pake loaderData
di component, lo udah bisa dapet tipe data yang sesuai dengan apa yang lo return di loader
.
Ini keren, karena lo nggak perlu validasi manual di client atau server yang mungkin biasanya lo lakuin ketika pake fetch
.
Fitur Lainnya
Selain fitur-fitur di atas, React Router juga punya beberapa fitur lain yang biasanya kita butuhin:
- Streaming
- Error boundary
- Meta tags
- Prefetching
- Form component
- Testing
- Dan masih banyak lagi
Gue nggak bakal bahas semuanya di sini secara detail, beberapa yang umum aja yang bakal gue bahas.
Pembahasan
Gue bakal bahas beberapa fitur yang umum biasanya kita pake di Next.js, supaya lo bisa lebih mudah beradaptasi dengan React Router sebagai pengganti Next.js. Itu berarti gue bakal bahas React Router mode framework dengan SSR (Server-Side Rendering) enabled.
Instalasi
Buat mulai project baru React Router, lo bisa perintah berikut:
Biasanya, lo bakal ditanya mau install dependencies sekalian atau nggak. Kalo lo nggak sengaja pilih "No", lo bisa install dependencies-nya manual pake perintah berikut:
Buat jalanin dev server, lo bisa pake perintah berikut:
Sekarang lo bisa akses aplikasi lo di http://localhost:5173
.
Kedepannya, gue bakal asumsiin lo pake pnpm, ya. Karena gue pake pnpm di sini. Nggak beda jauh kok perintahnya~
Pengenalan File dan Folder
Setelah lo bikin project baru, lo bakal nemu struktur folder dan file yang udah ada secara bawaan. Beberapa file lo bisa liat secara langsung, tapi ada juga beberapa yang disembunyiin secara default, kayak entry.client.tsx
dan entry.server.tsx
contohnya. Ini karena emang file-file tersebut nggak perlu di-apa-apain, kecuali ada case khusus yang lo butuhin.
.react-router
Folder ini di-generate otomatis sama CLI-nya React Router. Isi dari folder ini adalah informasi kayak type yang di-generate otomatis sesuai konfigurasi di file app/routes.ts
. Secara umum, lo nggak perlu ngurusin folder ini, tapi lo bisa liat-liat isinya kalo lo penasaran.
app
Folder ini bisa lo isi dengan kode aplikasi lo. Beda dengan Next.js, di dalem folder ini nggak banyak file convention dan ada beberapa file bawaan:
app/routes.tsx
File ini adalah file konfigurasi routing yang bisa lo sesuaikan dan file ini juga yang jadi sumber React Router buat nge-generate type untuk setiap route module (lo bisa baca lebih lanjut di bagian Routing).
app/root.tsx
File ini juga bisa disebut root route, dia yang bakal jadi parent buat semua route yang ada di aplikasi lo.
Kalo di Next.js, ini mirip kayak root layout-nya mereka lah atau file app/layout.tsx
. Ini kenapa apa yang lo declare di file ini, bakal "dibagi" ke semua route yang ada di aplikasi lo.
Di dalem file ini lo bakal nemu <Outlet />
, yang fungsinya buat nge-render child route. Jadi, setiap route yang lo bikin di dalam file routes.tsx
, bakal di-render di dalam <Outlet />
ini.
Juga lo bakal nemu export function Layout
yang bisa lo pake buat nge-define layout component buat ngebungkus setiap route. Setiap kali user pindah route, layout ini bakal tetep ada dan nggak bakal di-render ulang.
Karena file ini adalah root route, jadi lo harus define tag <html>
, <head>
, dan <body>
di dalamnya. Mirip kayak app/layout.tsx
di Next.js.
Lo bisa juga import global CSS kayak gini:
Bisa juga export component ErrorBoundary
yang bakal dipake buat handle error di seluruh aplikasi lo (termasuk error 404).
Kalo di Next.js, ini mirip app/global-error.tsx
perannya.
Lo juga bisa export links
buat nge-load CSS external kayak Google Fonts, misalnya:
File ini juga support beberapa fitur yang bisa dipake di route module, kayak loader
dan action
contohnya.
Colocation
Selain kedua file di atas, sebenernya ada folder lain dengan nama welcome
yang ada di dalam folder app
.
Isi dari folder ini cuman satu file welcome.tsx
dan dua file logo. Lo bisa hapus folder ini kalo lo mau, karena ini cuman contoh aja.
Lo bisa bikin folder lain di dalam folder app
buat naro file-file yang lo butuhin, misal components
, utils
, atau hooks
.
Gue sendiri nge-treat folder app
ini kayak folder src
yang biasanya ada di project React lainnya.
Tapi, ini preferensi pribadi aja, lo bisa naruh file-file lo di luar folder app
kalo mau.
Gue cuman ngasih tau aja kalo cara ini aman dan lo nggak perlu khawatir ada file yang namanya bakal bentrok sama convention yang ada di React Router.
Soalnya ada framework lain yang bentar lagi mau mati ketiban convention-nya sendiri.
public
Folder public
ini adalah tempat lo naro file-file statis yang bisa diakses langsung dari URL. Misalnya, lo mau naro gambar atau file aset yang bisa diakses langsung, lo bisa taro di sini.
vite.config.ts
React Router ini pake Vite sebagai bundler-nya, jadi lo bakal nemuin file konfigurasi Vite di sini. Lo bisa ubah konfigurasi Vite sesuai kebutuhan lo, misalnya nambahin plugin kayak Tailwind CSS atau apaun lah pokoknya. Nyatanya, Tailwind CSS ini udah di-setup secara bawaan, jadi lo tinggal pake aja.
react-router.config.ts
Ini adalah file konfigurasi React Router. Secara umum, lo nggak perlu ngubah apa-apa di sini, kecuali ada prilaku dari React Router yang mau lo ubah, kayak nge-disable SSR atau nge-enable feature experimental contohnya.
Routing
Secara bawaan, React Router punya sistem routing yang sentris, jadi lo bisa bikin routing di satu file routes.ts
yang ada di folder app
. Kalo lo buka file tersebut, lo bakal nemu satu route bawaan yang jadi halaman utama bawaan React Router:
Kalo lo mau bikin route baru, tinggal tambahin aja di array tersebut. Misal, lo mau bikin route /about
, lo bisa tambahin kayak gini:
Fungsi route
butuh dua parameter: path dan file module-nya yang bakal di-render ketika route tersebut match atau diakses.
Isi dari file route module-nya itu React component yang diekspor secara default. Contohnya kayak gini:
Kalo lo liat lagi file routes.ts
, di sana lo notice ada index
dan route
. index
itu buat route yang jadi halaman utama dari parent route-nya, sedangkan route
itu buat route biasa. Secara teknis kalo lo ganti index
ke route
, itu juga bakal jalan, tapi secara semantik lebih baik pake index
kalo itu halaman utama dari parent route-nya.
Setiap file yang lo referensiin di routes.ts
itu disebut route module. Route module ini bisa punya beberapa fungsi yang bisa lo export, kayak loader
, action
, dan meta
. Kalo liat lagi kode di atas, file routes/home.tsx
dan routes/about.tsx
itu adalah route module.
Path import route module ini relatif ke file routes.ts
, ini berarti dia mulai dari folder app
. Jadi, ketika lo nulis routes/home.tsx
, itu berarti lo ngimpor file home.tsx
yang ada di dalam folder app/routes
.
Di mana lo mau naruh route module itu terserah lo, karena nggak ada convention khusus buat itu. Cuman, biasanya orang naruh di folder routes
atau pages
biar gampang dicari dan lebih terstruktur.
Kayak gini nggak masalah:
Kalo kayak gini lo nggak perlu lagi bikin struktur folder yang dalem-dalem buat ngikutin struktur path-nya. Hal ini juga yang ngatasin masalah pribadi gue, dan akhirnya gue bisa bikin struktur folder yang sedangkal mungkin.
Nested
Anggap halaman /about
itu punya subhalaman /about/team
. Lo bisa bikin route-nya kayak gini:
Kalo lo akses /about/team
, lo bakal liat tampilan yang sama kayak /about
. Ini karena route /about
dianggap sebagai parent dari route /about/team
. Untuk itu, lo perlu nge-render component <Outlet />
di dalam component /about
supaya subhalaman tersebut bisa ditampilin.
Secara teknis, route module /about/team
ini dibungkus di dalam route module /about
. Kayak gini kira-kira:
Ini berarti route module /about
juga bisa kita dijadikan sebagai layout untuk subhalaman /about/team
dan subhalaman lainnya yang ada di bawahnya. Ini bermanfaat banget ketika lo mau bikin semacam sub-menu berupa sidebar atau tab yang ada di dalam halaman /about
.
Ini juga berarti, dalam konteks ini, lo nggak perlu naruh konten halaman /about
di dalam file routes/about.tsx
. File tersebut bisa dijadiin sebagai layout aja, dan lo bisa bikin file baru yang khusus buat konten halaman /about
di dalam file app/routes/about-index.tsx
, misalnya.
Jadinya kayak gini:
Untuk isi file app/routes/about.tsx
, lo bisa bikin kayak gini:
Sedangkan untuk file app/routes/about-index.tsx
, lo bisa bikin kayak gini:
Dan untuk file app/routes/about-team.tsx
, lo bisa bikin kayak gini:
In case lo nggak mau bikin nested route dengan struktur kayak gini, lo bisa bikin pake cara yang lebih simpel:
Kalo kayak gini, file app/routes/about.tsx
nggak perlu pake <Outlet />
lagi, karena nggak ada subhalaman yang di-render di dalamnya dan kedua route tersebut nggak punya parent-child relationship. That's perfectly fine kalo lo butuhnya kayak gini.
If it works, it works!
Dynamic
Dynamic segments lo butuhin ketika lo perlu parameter di dalam path suatu route. Misal, lo bikin satu halaman buat detail produk dengan path /product/baju-pria-001
, bagian baju-pria-001
itu yang jadi parameter dinamisnya. Di React Router, lo bisa bikin dynamic segments dengan cara nambahin :
di depan nama parameter yang lo mau.
Contohnya:
Di dalem file app/routes/product.tsx
, lo bisa akses parameter tersebut lewat params
yang ada di dalam component props. Contohnya:
Kalo di Next.js, lo harus nulis type manual buat parameter dinamisnya, misal { pid: string }
. Sementara di React Router, lo bisa langsung akses params
yang udah di-infer tipe datanya dari path yang lo tulis di routes.ts
.
Liat lagi kode di atas, di baris pertama ada import type { Route } from './+types/product';
. Ini adalah type yang di-generate otomatis oleh React Router buat route module ini. Setiap route module yang lo bikin, React Router bakal generate type-nya di dalam folder spesial .react-router/types/
. Dengan konfigurasi rootDirs
di tsconfig.json
, lo bisa import type tersebut seolah-olah itu ada di dalam folder yang sama dengan route module-nya. Dengan demikian, lo bisa dapet type safety buat props yang ada di dalam component lo, termasuk params
, loaderData
, dan actionData
.
Lo juga bisa bikin beberapa dynamic segments di dalam satu route. Misal, lo mau bikin halaman detail produk dengan kategori, lo bisa bikin kayak gini:
Di dalam file app/routes/product.tsx
, lo bisa akses kedua parameter tersebut lewat params
:
Lo juga bisa bikin dynamic segments jadi opsional dengan cara nambahin ?
di belakang nama parameter. Misal, lo mau bikin halaman detail produk yang bisa diakses dengan atau tanpa kategori, lo bisa bikin kayak gini:
Di dalam file app/routes/product.tsx
, lo bisa akses parameter tersebut dengan cara yang sama seperti sebelumnya:
Jadi, route tersebut bakal match dengan path /product/category/baju-pria-001
dan /product/baju-pria-001
.
Prefix
Prefix ini fungsi yang bikin lo bisa bikin route path dengan awal yang sama. Misal, lo mau bikin route /admin/users
dan /admin/products
, lo bisa bikin kayak gini:
Karena prefix
ini return-nya adalah array, lo bisa langsung spread ke dalam array route yang ada di routes.ts
. Ini bikin lo bisa bikin route dengan awalan yang sama tanpa harus nulis ulang awalan tersebut di setiap route.
Layout
Ketika lo mau bikin layout yang berbeda buat beberapa route, lo bisa bungkus route tersebut di dalam fungsi layout
.
Misal, lo mau bikin layout buat halaman admin dan marketing, lo bisa bikin kayak gini:
Di dalam file masing-masing layout, lo bisa define <Outlet />
yang bakal nge-render child route. Contohnya, di file app/routes/admin/layout.tsx
:
Sama kayak layout yang ada di file app/root.tsx
, layout ini juga nggak bakal di-render ulang ketika user pindah-pindah route yang ada di layout tersebut.
Styling
Secara bawaan, React Router udah ngeintegrasiin Tailwind CSS lewat Vite, jadi lo bisa langsung pake Tailwind CSS di project lo. Kalo lo mau pake opsi yang lain, tinggal ikutin aja panduan di dokumentasi library tersebut. Karena cara setup setiap library itu pasti beda-beda.
CSS
Secara mendasar, lo bisa pake CSS reguler dan bisa langsung import file-nya ke component React lo.
Bisa liat contohnya di file app/root.tsx
. Di sana ada import "app.css";
yang ngimpor file CSS global.
Isi dari file tersebut kayak gini:
Kayak yang lo liat, di sini ada @import "tailwindcss";
yang ngimpor Tailwind CSS.
Versi Tailwind CSS yang dipake di sini adalah versi 4.
Itu kenapa lo nggak liat file tailwind.config.js
di dalam project ini, karena buat versi 4 ini mereka pake file CSS buat ngekonfigurasinya.
Fonts
Kalo mau pake custom font, lo bisa taro file font-nya di dalam folder public
dan import font tersebut di file CSS lo kayak gini contohnya:
Alternatifnya, lo bisa pake Google Fonts atau font CDN lainnya. Lo tinggal import aja file CSS-nya di file app/root.tsx
kayak yang udah dicontohin sama React Router secara default:
Buat font CDN lainnya juga sama harusnya kalo mereka nyediain file CSS yang bisa di-import.
Kenapa pake <link> dan nggak pake @import di CSS aja?
Sebagai contoh, kalau lo pakai <link href="a.css">
buat load CSS, dan di dalam a.css
ada @import 'b.css'
, maka browser bakal download a.css
dulu, baru setelah itu download b.css
.
Ini bikin loading jadi sekuensial (satu-satu), bukan paralel. Alhasil, web lo jadi lebih lambat.
Padahal, kalau lo pakai banyak <link>
langsung di HTML (atau @import
di dalam <style>
tag), browser bisa download semua CSS secara paralel—hasilnya lebih cepat.
Kesimpulannya, hindarin @import
di file CSS secara umum. Kecuali kalau lo terpaksa (misalnya di CMS yang cuma bisa akses 1 file CSS), ya udah nggak masalah.
Meta Tags
Sebelum lebih jauh lagi, kita bahas dulu yang gampang, yaitu meta tags.
Di dalem route module, lo bisa nge-export fungsi meta
yang bakal nge-return meta tags yang lo pengen define:
Tapi, karena React 19 punya built-in elemen <meta>
, pake elemen tersebut lebih disarankan daripada pake fungsi meta
:
Cara tersebut menurut gue lebih bersih dan lebih gampang dibaca.
Navigating
Lo udah belajar bikin route, sekarang lo perlu tau cara navigasi di antara route tersebut.
Sama kayak di Next.js, React Router juga punya component <Link>
. Bedanya, alih-alih pake href
, lo pake to
buat ngasih tahu path yang mau dituju.
Contohnya gini:
Selain <Link>
, lo juga bisa pake component <NavLink>
. Component tersebut mirip kayak <Link>
, cuman dia punya fitur tambahan kayak buat nentuin apakah link tersebut aktif atau nggak.
Ini berguna buat styling link yang aktif, contohnya:
Kalo mau navigasi programatis, lo bisa pake hook useNavigate
. Hook ini mirip kayak useRouter
di Next.js, tapi lebih simpel.
Lo bisa pake navigate
buat pindah ke route lain.
Kalo navigasi yang butuh interaksi user, lo bisa pake <Link>
atau <NavLink>
.
Tapi, kalo navigasi yang butuh logika atau kondisi tertentu, lo bisa pake useNavigate
.
Data Loading
Iya, di Next.js namanya data fetching, tapi di React Router nyebutnya data loading. Data loading diperluin kalo lo butuh ambil data saat halaman di-render, misalnya buat nge-fetch data dari API atau database.
React Router punya dua fungsi buat data loading: loader
dan clientLoader
.
Seperti yang gue bahas sebelumnya, loader
dieksekusi di server saat halaman di-render, sedangkan clientLoader
dieksekusi di client/browser.
Contohnya gini:
loaderData
bakal otomatis di-infer tipe datanya dari return value loader
.
Tapi, kalo lo pake fetch
, loaderData
bakal jadi any
.
Ini karena data yang lo fetch
bisa punya tipe yang beda-beda, tergantung dari API yang lo panggil.
Contohnya:
Di kasus kayak gini, lo bisa bikin type guard manual atau pake library kayak zod
:
Kalo misalnya lo pake ORM kayak Drizzle, loader
juga bisa langsung nge-return model yang udah di-infer tipe datanya. Contohnya:
Dengan cara ini, loaderData
di dalam component Product
bakal otomatis punya tipe yang sesuai dengan model products
.
Fungsi loader
ini juga bakal dihapus dari client bundle, jadi lo aman buat pake API yang cuman boleh jalan di server.
Actions
Data loading buat ngambil data, sedangkan action buat mutasi data. Sama kayak loader
, action juga bisa dieksekusi di server lewat fungsi action
atau di client lewat clientAction
.
Bedanya dengan Server Actions di Next.js, actions di React Router nggak perlu ditaro di file khusus.
Lo cuman perlu export fungsi action
dari route module sama kayak lo export loader
.
Nantinya fungsi itu yang bakal dipanggil ketika ada form request.
Contohnya gini:
Penjelasan kodenya gini:
<Form>
adalah component yang disediain React Router buat nge-handle form submission.- Ketika user submit form, hal ini yang terjadi:
action
dieksekusi ketika ada form request dengan methodPOST
,PUT
,PATCH
, atauDELETE
.request.formData()
dipake buat ambil data dari form yang di-submit. Karena lo pake<Form>
, maka tipe datanya adalahFormData
.- Nama produk diambil dari form data dan disimpen ke variabel
name
. - Data produk di-update pake fungsi
db.updateProduct({ name })
(ini contoh aja). - Terus data tersebut di-return sebagai
actionData
, yang bisa lo akses di dalam componentProducts
. - Ketika
action
selesai dieksekusi tanpa error, semua loader data bakal di-revalidate supaya UI dan data tetep sinkron. - Terakhir, lo bisa akses nilai return dari
action
di dalam component lewatactionData
.
Ketika lo pake <Form>
buat submit form, dia bakal nambahin entry ke history browser, jadi user bisa kembali ke halaman sebelumnya pake tombol back di browser.
Kalo lo nggak mau behavior ini, lo bisa pake yang namanya fetcher
:
Ketika pake fetcher
, buat ngambil data return dari action
, lo bisa pake fetcher.data
.
Di kasus nyata, lo biasanya butuh ngevalidasi data sebelum disimpen. Nggak ada cara spesial buat ini, standar aja kayak gini:
Ketika lo mau nge-return error dari action
, lo bisa pake fungsi data
dan ngasih status code yang sesuai.
Hal ini diperluin supaya mencegah React Router nge-revalidate semua loader data yang ada di halaman tersebut.
Authentication
Enaknya di React Router, dia udah nyediain session storage yang bisa lo pake buat bikin sistem autentikasi. Jadi, lo nggak perlu ribet-ribet mikirin solusi sendiri. Ini cukup kalo lo pengen kontrol penuh atas sistem autentikasi lo.
Lo bisa pake createSessionStorage
dari React Router buat bikin session storage. Contohnya:
Session di atas lo bisa pake buat nyimpen data user yang lagi login, misalnya userId
(dalam contoh ini) atau data flash (pesan sekali tampil) buat ngasih feedback ke user setelah mereka melakukan aksi tertentu.
Kalo lo liat lagi, di atas ada tiga fungsi yang di-export:
getSession
: buat ambil session dari cookie.commitSession
: buat simpen session ke cookie.destroySession
: buat hapus session dari cookie.
Sistem session di React Router ini nggak di-spread lewat middleware, jadi lo harus panggil fungsi getSession
setiap kali lo butuh akses session di dalam route module lo.
Sebagai contoh, bikin halaman login:
Penjelasannya gini:
- Di
loader
, kita ngecek apakah user udah login dengan ngecek apakah session punyauserId
:- Kalo udah, kita redirect ke halaman utama.
- Kalo belum, kita ambil data flash
error
dari session dan return ke component buat ditampilin.
- Ketika user submit form,
action
bakal dipanggil dan ini yang terjadi:- Di sini, kita ambil session dari cookie dan ngecek apakah username dan password yang dimasukin valid.
- Kalo valid, simpen
userId
ke session dan redirect ke halaman utama. - Kalo nggak valid, set flash message
error
ke session dan redirect kembali ke halaman login.
Hal yang perlu diinget, ketika lo nge-set sesuatu di session, lo harus nge-commit session tersebut dengan commitSession(session)
supaya perubahan yang lo buat ke session bisa disimpen ke cookie.
Dan kalo lo mau ngehapus session, lo bisa pake destroySession(session)
:
Cara ini cukup buat bikin sistem autentikasi yang simpel. Kalo lo butuh solusi yang lebih kompleks, lo bisa pake library kayak better-auth atau OpenAUTH.
Resource Routes
Resource routes ini simpelnya adalah route yang nggak punya UI, jadi lo cuman export fungsi loader
atau action
aja.
Persis, fungsinya kayak API Routes di Next.js. Maka dari itu, kita coba bikin "API Routes" di React Router pake resource routes.
Pertama, lo bikin route-nya dulu di app/routes.ts
:
Kemudian lo bikin file app/routes/api/products.ts
:
Selesai! Sekarang lo punya resource route yang bisa diakses di /api/products
.
Fungsi loader
bisa lo pake buat nge-handle request GET ke /api/products
.
Sementara buat request POST, PUT, DELETE, atau PATCH, bakal di-handle sama fungsi action
:
Kalo dalam suatu kasus lo pake fetch
dan ngirim payload JSON, lo bisa pake request.json()
:
Sesuai sama namanya, resource routes ini nggak khusus buat API aja. Lo bisa ngelakuin sesuatu yang lain juga, misalnya nge-handle webhook, render PDF, atau nge-stream response dari LLM.
Streaming
Ketika lo bikin halaman yang nge-load beberapa data sekaligus, lo bisa pake streaming supaya halaman tersebut bisa ditampilkan lebih cepat alias nggak nge-block proses rendering. Konsepnya sama kayak yang ada di Next.js, cuman beda caranya aja.
Anggap aja lo punya halaman detail produk yang nampilin inforasi produk dan review produk. Dari kedua data tersebut, yang paling penting adalah informasi produk, sedangkan review produk bisa ditampilin belakangan.
Contohnya kayak gini:
Penjelasannya gini:
- Di
loader
, kita ambil data produk dan review produk.- Data informasi produk ini penting, jadi kita
await
supaya bisa langsung ditampilin. - Sedangkan review produk kita nggak
await
, supaya bisa di-stream.
- Data informasi produk ini penting, jadi kita
- Di dalam component
Product
, kita pake<Await>
dan<Suspense>
buat nge-handle data review yang di-stream.<Await>
bakal nunggu data review resolve dan nge-render list review lewat pattern render props.<Suspense>
bakal nampilin fallback UI (dalam hal ini, "Loading reviews...") selama data review belum tersedia.
Basic-nya kayak gitu. Kalo seaindainya lo pake React 19, lo bisa pke React.use
. Tapi, perlu diabstrak jadi dua komponen:
Sementara isi dari komponen ProductReviews
adalah:
Bebas aja lo mau pake yang mana.
Error Handling
Setiap ada error yang terjadi di aplikasi lo, React Router bakal nge-trigger ErrorBoundary
yang ada di app/root.tsx
.
Lo bisa coba test ini dengan bikin error kayak gini:
Dengan begini, ErrorBoundary
yang ada di app/root.tsx
bakal di-render.
Kalo seandainya lo rajin dan mau bikin error handling yang lebih spesifik buat setiap route, lo bisa bikin ErrorBoundary
di dalam route module itu sendiri.
Variable error
di dalam ErrorBoundary
ini bisa beda-beda tergantung dari error yang terjadi.
Maka dari itu lo bisa cek tipe error-nya kayak yang udah dicontohin di file app/root.tsx
.
Kalo error yang terjadi hasil dari data
dengan status code dan text, branch isRouteErrorResponse
bakal dipake. Contohnya:
Selain itu, kalo error
yang terjadi adalah instance dari Error
, branch error instanceof Error
bakal dipake. Contohnya:
Kalo lo bikin beberapa ErrorBoundary
, React Router bakal nge-render ErrorBoundary
yang paling dekat dengan sumber error terjadi.
Misal, kalo ada error di dalam Product
component, maka ErrorBoundary
yang ada di Product
yang bakal di-render.
Kalo nggak ada, maka ErrorBoundary
parent-nya yang bakal di-render, sampai ke ErrorBoundary
yang ada di app/root.tsx
.
Rendering Strategies
Secara default, React Router bakal nge-render halaman secara server-side pake mekanisme SSR (Server-Side Rendering). Ini sama kayak Next.js versi pages router. Di samping itu, React Router juga support dua strategi rendering lainnya, yaitu:
- Client-Side Rendering: Halaman di-render di client/browser, jadi nggak ada SSR.
- Static Pre-rendering: Halaman di-render di server saat build time, jadi hasilnya adalah HTML statis.
Buat ngubah strategi rendering, lo bisa atur konfigurasinya di file react-router.config.ts
.
Ini contohnya kalo lo mau pake Client-Side Rendering:
Dengan nge-disable SSR, artinya alih-alih pake loader
dan action
, lo bakal pake clientLoader
dan clientAction
buat nge-fetch dan mutasi data di client/browser.
Ini basically lo bikin Single Page Application (SPA) pake React Router.
Penutup
Oke, segitu aja dulu. Buat pelajaran yang lebih spesifik, gue bakal bahas di tulisan yang lain. Terima kasih udah mau baca!