Waduh, Next.js App Router kita error lagi? Yuk, hindari kesalahan umum ini!

Waduh, Next.js App Router kita error lagi? Yuk, hindari kesalahan umum ini!
Photo by Nong/Unsplash

Pernah nggak sih kita lagi asik ngoding Next.js App Router, eh tiba tiba errornya muncul lagi? Rasanya seperti ketemu mantan yang nggak sengaja di minimarket, kaget tapi harus tetap cool menghadapinya. Tenang, kita nggak sendiri kok. Banyak dari kita yang masih meraba raba seluk beluk App Router yang memang lumayan beda dari Pages Router yang sudah lama kita kenal. Tapi jangan khawatir, di artikel ini kita akan bongkar tuntas kesalahan umum yang sering bikin pusing, plus tips ampuh biar App Router kita jalan mulus dan anti error. Yuk, kita mulai petualangan mencari tahu kenapa App Router kita kadang suka ngambek.

Memahami Jantung App Router Server dan Client Components

App Router itu memperkenalkan konsep Server Components dan Client Components yang mungkin awalnya agak membingungkan. Ini adalah salah satu penyebab utama banyak error yang muncul, apalagi kalau kita belum sepenuhnya paham kapan dan di mana harus memakainya. Kita seringkali tergoda untuk langsung menambahkan use client di mana mana padahal sebenarnya tidak selalu diperlukan. Memahami perbedaan mendasar ini akan jadi kunci untuk aplikasi kita yang lebih stabil dan performa yang lebih optimal.

Kapan Sebaiknya Kita Pakai use client

Server Components itu defaultnya di Next.js App Router. Ini artinya komponen komponen kita akan dirender di server secara bawaan. Asik kan? Jadi, kalau komponen kita tidak membutuhkan interaktivitas browser seperti useState, useEffect, atau event handler onClick dan sejenisnya, biarkan saja jadi Server Component. Mereka ringan dan cepat karena tidak perlu di bundle dan dikirim ke browser.

Nah, kalau kita butuh interaksi dengan browser, misalnya untuk state internal komponen, event listener, atau memakai hooks React lainnya, barulah kita perlu menambahkan use client di paling atas file komponen kita. Ingat ya, cukup di root komponen yang memang butuh interaktivitas. Misalnya, kita punya tombol Tambah ke Keranjang. Tombol ini pasti perlu onClick dan mungkin state untuk menampilkan loading. Maka, komponen tombol ini atau parent terdekat yang membungkusnya bisa jadi Client Component. Kita harus hindari menaruh use client di layout.tsx atau page.tsx kalau tidak benar benar diperlukan, karena itu bisa membuat seluruh pohon komponen di bawahnya jadi Client Component dan malah memberatkan bundle.

Jangan Sampai Kena Hydration Mismatch

Salah satu error yang bikin jengkel itu adalah hydration mismatch. Ini terjadi ketika output HTML yang dirender di server berbeda dengan yang dirender oleh React di sisi klien. Biasanya penyebabnya adalah adanya kode yang hanya bisa berjalan di browser tapi ada di Server Component, atau sebaliknya. Contohnya, kita mengakses window atau localStorage langsung di Server Component. Tentu saja itu akan error karena di server hal hal seperti itu tidak ada.

Untuk menghindari ini, pastikan kode yang mengakses objek spesifik browser hanya ada di Client Components. Jika ada sebuah Server Component yang perlu menampilkan sesuatu berdasarkan kondisi klien, seperti ukuran layar, kita bisa melakukan dua pendekatan. Pertama, kirim data dari server dan biarkan Client Component yang menampilkannya dengan kondisi di sisi klien. Kedua, render placeholder di server dan biarkan Client Component me mount dan menampilkan kontennya setelah hidrasi. Intinya, Server Components itu untuk data fetching dan rendering statis, Client Components itu untuk interaktivitas dinamis. Jangan sampai tertukar.

Strategi Data Fetching yang Anti Gagal

Data fetching di App Router mengalami perubahan yang signifikan dan membawa banyak keuntungan. Kita sekarang bisa fetching data langsung di Server Components, yang artinya data bisa diambil sebelum halaman dikirim ke browser. Ini sangat bagus untuk SEO dan performa awal halaman kita. Namun, kesalahan dalam mengimplementasikan strategi data fetching ini seringkali menjadi sumber masalah.

Data Fetching di Server Components Jadi Lebih Asik

Di Server Components, kita bisa memakai fetch API bawaan JavaScript atau library fetching data apa pun tanpa perlu useEffect atau useState. Ini jauh lebih bersih dan efisien. fetch API di Next.js sudah di patch untuk memiliki caching yang powerful secara default. Jadi, kalau kita memanggil fetch dengan URL yang sama berulang kali di berbagai Server Component, Next.js akan menduplikasinya dan hanya melakukan satu permintaan ke backend.

Kesalahan umum di sini adalah tidak memahami perilaku caching fetch ini. Secara default, fetch akan melakukan cache force cache yang sangat agresif. Artinya, jika kita memanggil fetch('https://api.example.com/products'), hasil dari panggilan ini akan di cache selamanya sampai kita mendeploy ulang aplikasi kita atau sampai cache di server di purge. Kalau kita ingin data yang lebih segar, kita perlu mengkonfigurasi opsi cache fetch kita. Misalnya, fetch('https://api.example.com/products', { cache: 'no-store' }) untuk selalu mengambil data terbaru, atau fetch('https://api.example.com/products', { next: { revalidate: 60 } }) untuk revalidate setiap 60 detik. Memilih strategi caching yang tepat sangat penting untuk performa dan kesegaran data aplikasi kita.

Revalidasi Data Itu Penting Lho

Selain opsi revalidate di fetch secara individual, kita juga bisa melakukan revalidasi data secara on demand atau per path. Ini sangat berguna ketika data kita berubah di backend dan kita ingin agar perubahan itu langsung terlihat di aplikasi kita tanpa harus menunggu waktu revalidate yang sudah kita set.

Next.js menyediakan fungsi revalidatePath(path) dan revalidateTag(tag) yang bisa kita panggil dari Server Actions atau Route Handlers. Kesalahan yang sering terjadi adalah lupa merevalidasi data setelah melakukan mutasi. Misalnya, kita membuat formulir untuk menambah produk baru. Setelah produk berhasil ditambahkan ke database, kita harus memanggil revalidatePath('/dashboard/products') agar halaman daftar produk kita menampilkan data terbaru. Tanpa revalidasi ini, pengguna mungkin akan melihat data lama sampai cache expired atau aplikasi di deploy ulang. Memahami kapan dan bagaimana merevalidasi data adalah kunci untuk aplikasi yang responsif dan menampilkan informasi yang selalu up to date.

Routing dan Struktur Folder yang Rapi Itu Kunci

App Router memperkenalkan paradigma routing berbasis folder. Struktur folder kita sekarang secara langsung memetakan struktur URL aplikasi kita. Ini membuat routing jadi lebih intuitif dan terorganisir, tapi juga bisa jadi sumber kebingungan kalau kita tidak mengikuti aturannya dengan benar.

Tata Letak File dan Folder yang Bikin Hidup Tenang

Setiap folder di dalam folder app mewakili segmen URL. File page.tsx di dalam folder tersebut akan menjadi UI yang dirender untuk segmen URL itu. Contohnya, app/dashboard/page.tsx akan dirender untuk URL /dashboard. File layout.tsx di dalam folder tersebut akan menjadi layout yang membungkus page.tsx dan semua page.tsx di bawahnya. Kesalahan umum adalah menaruh page.tsx di tempat yang salah atau membuat layout yang tidak sesuai dengan hirarki URL.

Kita juga punya file khusus lain seperti loading.tsx untuk menampilkan loading state, error.tsx untuk menangani error, dan not-found.tsx untuk halaman 404. Memahami peran masing masing file ini sangat penting. Misalnya, loading.tsx akan otomatis ditampilkan saat segmen URL sedang mengambil data. Kalau kita tidak menaruhnya di tempat yang tepat, pengguna mungkin tidak akan melihat indikator loading dan mengira aplikasi kita lambat.

Manfaatkan Layout loading dan error Secara Optimal

layout.tsx, loading.tsx, dan error.tsx itu memiliki peran hirarkis. layout.tsx teratas akan membungkus semua yang ada di bawahnya. error.tsx yang lebih dekat ke page.tsx yang error akan menangkap error tersebut. Ini artinya kita bisa membuat error boundary yang sangat spesifik. Jika kita hanya punya satu error.tsx di root app folder, itu akan menangkap semua error di seluruh aplikasi kita. Tapi kalau kita punya app/dashboard/error.tsx, itu hanya akan menangkap error di segmen /dashboard dan turunannya.

Manfaatkan fitur ini untuk memberikan pengalaman pengguna yang lebih baik. Misalnya, di app/dashboard/loading.tsx, kita bisa menampilkan skeleton UI khusus untuk dashboard. Sementara di app/produk/[id]/error.tsx, kita bisa menampilkan pesan error yang spesifik jika ada masalah saat mengambil detail produk. Kesalahan yang sering terjadi adalah tidak memanfaatkan fitur ini dan hanya mengandalkan error.tsx di root, sehingga semua error terlihat sama dan kurang informatif bagi pengguna.

Mengelola State dan Interaksi Pengguna dengan Bijak

Perbedaan antara Server dan Client Components juga mempengaruhi bagaimana kita mengelola state dan menangani interaksi pengguna. Kita tidak bisa lagi sembarangan memakai useState atau useReducer di mana saja. Kita perlu memikirkan kembali strategi state management kita.

State Management di Lingkungan Server dan Client

Karena Server Components tidak memiliki state, semua state yang dibutuhkan untuk interaksi harus berada di Client Components. Jika kita mencoba menggunakan useState atau useEffect di Server Component, aplikasi kita akan crash atau setidaknya memberikan error yang jelas.

Untuk state yang perlu di share antar Client Components yang berbeda, kita bisa tetap menggunakan Context API atau library state management pihak ketiga seperti Zustand atau Redux. Pastikan bahwa provider untuk Context atau store dari library state management kita ditaruh di Client Component, biasanya di root layout.tsx yang sudah ditandai use client. Kesalahan umum di sini adalah mencoba menginisialisasi store di Server Component atau meneruskan objek objek yang tidak bisa di serialize (seperti fungsi atau event handler) dari Server Component ke Client Component sebagai props. Pastikan semua props yang diteruskan antar Server dan Client Components adalah data yang bisa di serialize dengan baik.

Server Actions Senjata Baru Kita untuk Form

Server Actions adalah fitur keren yang memungkinkan kita menjalankan fungsi di server langsung dari komponen klien, tanpa perlu membuat API endpoint terpisah. Ini sangat menyederhanakan proses penanganan form atau mutasi data. Kita bisa membuat form yang action-nya langsung menunjuk ke sebuah fungsi async yang ditandai use server.

Kesalahan yang sering terjadi adalah lupa menandai fungsi kita dengan use server atau tidak memahami bagaimana data form di passing ke Server Action. Fungsi Server Action akan menerima FormData sebagai argumen pertamanya. Kita perlu parsing FormData ini untuk mendapatkan nilai nilai input. Selain itu, setelah Server Action berhasil dijalankan, kita seringkali perlu merevalidasi cache data kita (seperti yang sudah kita bahas sebelumnya) atau mengarahkan pengguna ke halaman lain. Lupa melakukan ini akan membuat pengguna melihat data lama atau tidak ada perubahan pada UI mereka.

Optimasi dan Best Practices Tambahan

Untuk memastikan App Router kita berjalan dengan performa terbaik dan pengalaman pengembangan yang asik, ada beberapa hal tambahan yang perlu kita perhatikan. Ini bukan hanya tentang menghindari error, tapi juga tentang membangun aplikasi yang berkualitas tinggi.

Metadata Dinamis yang Bikin SEO Happy

App Router punya cara baru yang lebih powerful untuk mengelola metadata. Kita bisa export objek metadata atau fungsi generateMetadata dari layout.tsx atau page.tsx. generateMetadata sangat berguna untuk metadata dinamis, misalnya, judul halaman yang diambil dari data produk.

Kesalahan umum adalah tidak memanfaatkan generateMetadata untuk SEO yang lebih baik atau malah mencoba membuat metadata secara manual di Client Components. Karena metadata diproses di server, ini sangat optimal untuk crawler mesin pencari. Pastikan kita mengisi properti penting seperti title, description, keywords, dan openGraph untuk setiap halaman yang relevan. Ini akan sangat membantu visibilitas aplikasi kita di mesin pencari.

Perhatikan Third Party Libraries

Tidak semua library pihak ketiga siap untuk Server Components. Banyak library yang masih mengandalkan efek samping browser seperti window atau document secara langsung. Jika kita menemukan error saat menggunakan library pihak ketiga di Server Component, kemungkinan besar library tersebut perlu dibungkus dalam Client Component.

Solusi paling sederhana adalah membuat komponen wrapper Client Component untuk library tersebut. Misalnya, export default function MyFancyCarousel(props) { return; } lalu tambahkan use client di atasnya. Dengan cara ini, kita memastikan kode library yang spesifik browser hanya dijalankan di lingkungan klien. Jangan paksakan library yang tidak kompatibel di Server Component, itu hanya akan memicu crash.

Ukuran Bundle dan Kinerja Aplikasi

Salah satu janji utama App Router adalah performa yang lebih baik dengan mengurangi JavaScript yang dikirim ke browser. Server Components secara alami membantu hal ini. Namun, kita bisa saja tidak sengaja membuat bundle kita jadi besar jika kita terlalu banyak memakai use client.

Selalu evaluasi apakah sebuah komponen benar benar perlu menjadi Client Component. Gunakan React.lazy dan Suspense untuk lazy loading Client Components yang besar. Hindari mengimpor library besar di Client Component yang sering tidak digunakan. Dengan pendekatan yang hati hati, kita bisa menjaga ukuran bundle kita tetap ramping, yang pada akhirnya akan membuat aplikasi kita lebih cepat di load dan lebih responsif bagi pengguna kita. Gunakan Next.js Bundle Analyzer untuk melihat apa saja yang berkontribusi pada ukuran bundle kita dan identifikasi area untuk optimasi.

Nah, itu dia beberapa kesalahan umum di Next.js App Router yang seringkali bikin kita geleng geleng kepala, beserta tips ampuh untuk menghindarinya. Semoga setelah membaca ini, kita semua jadi lebih paham dan bisa ngoding App Router dengan lebih lancar tanpa ada error error aneh lagi. Ingat, belajar hal baru itu memang butuh waktu dan kesabaran. Jadi, kalau sesekali ketemu error, jangan panik. Coba lagi, coba lagi, dan jangan ragu untuk cari solusi. Kita bisa bikin aplikasi Next.js App Router yang stabil dan performanya asik banget! Selamat ngoding.