Aku Pindah dari Pages Router ke App Router Next.js Ini Dia Masalah Server Component yang Harus Kamu Tahu

Pindah dari Pages Router ke App Router Next.js itu rasanya kayak pindah rumah, dari yang tadinya rumah petak biasa tiba-tiba pindah ke smart home yang semua pintunya otomatis. Keren sih, performa langsung loncat, tapi banyak kebiasaan lama yang harus dirombak total.

Buat kamu yang sudah lama nyaman main di Next.js dengan Pages Router, migrasi ke App Router—yang membawa arsitektur React Server Component (RSC) sebagai jantungnya—adalah langkah besar. Ini bukan cuma ganti nama folder dari pages/ jadi app/, tapi ini adalah pergeseran fundamental tentang di mana kode kamu dieksekusi.

Aku akan bahas pengalaman langsung pindah, khususnya masalah-masalah paling krusial yang muncul gara-gara Server Component (SC) yang kini jadi default di Next.js 13 ke atas. Siapkan kopi, karena ini bakal panjang dan teknis, tapi santai.

*

1. Shock Pertama: Semua Kodenya Jadi Server Component

Di Pages Router, jika kamu ingin kode berjalan di sisi server (seperti saat menggunakan getServerSideProps), kamu harus secara eksplisit menulis fungsi tersebut. Jika tidak, semuanya dianggap Client Component (CC) dan berjalan di browser.

Di App Router, filosofinya 180 derajat berbeda.

Aturan Emas App Router: Semua komponen di dalam direktori app/ adalah Server Component secara default.

Ini yang sering bikin developer Next.js lama kaget. Komponen yang tadinya santai menggunakan useState atau mengakses objek window tiba-tiba crash total saat dirender pertama kali. Kenapa? Karena komponen itu sekarang dieksekusi di Node.js (server), tempat window dan hooks React tidak ada artinya.

Tips Praktis:

Saat kamu mulai migrasi, ubah mindset kamu: Anggap komponenmu adalah kode backend yang menghasilkan HTML, bukan JavaScript yang akan diolah browser.

2. Solusi Sepele yang Menyelamatkan: "use client"

Jika semua default-nya Server Component, bagaimana cara kita kembali menggunakan useState, useEffect, useContext, atau onClick?

Inilah fungsi sakti yang harus kamu pelajari: 'use client'.

Menambahkan string "use client" di bagian paling atas file komponenmu adalah satu-satunya cara untuk memberi tahu Next.js, "Hei, komponen ini butuh interaksi browser. Tolong eksekusi dia di sisi klien."

Masalah yang Sering Muncul:

Banyak developer pemula di App Router membuat kesalahan dengan menaruh "use client" di root komponen mereka (misalnya di layout.tsx atau page.tsx).

Ini adalah anti-pattern! Jika kamu menandai komponen utama sebagai Client Component, kamu menghapus semua keuntungan Server Component yang ada di bawahnya (seperti kemampuan fetching data yang efisien di server).

Tips Aplikasif untuk Penggunaan "use client":

  1. Isolasi: Terapkan strategi "Client Leaf Component". Pastikan komponen yang ditandai "use client" adalah komponen paling bawah (daun) yang benar-benar membutuhkan interaktivitas.
  2. Server Wrapper: Gunakan Server Component sebagai wrapper atau container. Server Component yang bertugas fetching data dan mengatur tata letak, kemudian ia merender Client Component kecil yang hanya menangani interaksi pengguna.

Contoh: * DashboardLayout.tsx (Server Component: Fetches user data). * ProfileButton.tsx (Client Component: Memiliki onClick untuk membuka modal, ditandai "use client").

3. Server Component yang Mendadak Jadi async

Di Pages Router, getServerSideProps atau getStaticProps adalah tempat kita melakukan data fetching. Jika kita melakukan fetching di Client Component, kita pakai useEffect dan useState.

Di App Router, cara fetching berubah total dan jauh lebih elegan.

Server Component kini mendukung sintaks async secara native.

Kamu bisa melakukan await fetch(...) atau bahkan koneksi database langsung (jika aman) tepat di dalam fungsi komponen kamu.

typescript
// app/dashboard/page.tsx (Ini adalah Server Component)
async function getPostData() {
  const res = await fetch('https://api.example.com/posts', { cache: 'no-store' });
  if (!res.ok) throw new Error('Failed to fetch');
  return res.json();
}export default async function DashboardPage() { // Perhatikan 'async' di sini
  const posts = await getPostData();return (
    
      {/ Render komponen lain /}
    
  );
}

Kelebihan (dan Jebakan) dari SC async:

No Waterfalls: Karena data diambil sebelum rendering dimulai, kamu mengurangi masalah waterfall* network request yang sering terjadi di sisi klien.

  • Direct Access: Akses langsung ke sumber daya server tanpa perlu API Gateway tambahan (meski dalam praktik profesional, tetap disarankan melalui API internal).

Jebakan: Jangan coba-coba menggunakan state atau hooks di dalam komponen async ini! Jika kamu butuh loading state atau error state saat fetching data, kamu harus memanfaatkan fitur bawaan App Router: loading.tsx atau error.tsx, atau menggunakan Suspense Boundaries React.

4. Problem Serialization: Apa yang Boleh dan Tidak Boleh di Props?

Ini adalah salah satu tantangan paling menjebak dan bikin pusing di awal migrasi.

Server Component dan Client Component hidup di dunia yang berbeda. Data yang kamu kirimkan dari SC ke CC harus bisa "di-serialize" (diubah menjadi format yang bisa dikirim melalui jaringan, mirip JSON) saat di-hydrate di sisi klien.

Apa yang TIDAK BOLEH kamu kirim sebagai props dari SC ke CC:

  1. Fungsi (Functions): Kamu tidak bisa mengirimkan fungsi yang didefinisikan di Server Component ke Client Component. Ini akan menyebabkan error serialization.
  2. Date Objects: Objek Date tidak selalu terserialisasi dengan baik. Lebih baik ubah menjadi string ISO sebelum dilempar ke CC.
  3. Complex Classes: Objek kelas kustom yang kompleks (bukan plain JavaScript objects) akan gagal di-serialize.
  4. JSX/React Elements yang Mengandung Hooks/State: Jika kamu mencoba mengirimkan elemen React sebagai prop (misalnya headerComponent={}) dan komponen Header itu adalah Client Component yang membutuhkan hooks, ini bisa menjadi masalah rumit.

Solusi Jitu: Gunakan children

Jika kamu ingin Server Component memberikan konten kepada Client Component, jangan gunakan props biasa untuk elemen yang kompleks. Gunakan properti children.

Kenapa children aman?

Ketika SC merender CC, konten di dalam children sudah diolah dan dikirim sebagai HTML/React Server Payload yang sudah aman dan siap di-hydrate. Client Component hanya menerima dan merender output dari Server Component. Ini adalah cara paling aman untuk menyuntikkan konten yang didefinisikan di server ke dalam container klien.

5. Dilema Context API dan State Management

Banyak aplikasi besar menggunakan Context API atau state management library (Redux, Zustand) yang mengandalkan hooks (useContext). Masalahnya, Server Component tidak bisa menggunakan hooks!

Bagaimana SC dan CC bisa berbagi state atau context?

SC tidak bisa menyediakan context, karena penyediaan context membutuhkan hooks/state.

Solusi Wajib:

  1. Client-Side Provider: Context Provider harus dibatasi dan dibuat di dalam file yang ditandai "use client".
  2. Wrap Layout: Provider tersebut biasanya diletakkan di layer layout yang sangat atas, setelah layout.tsx utama (yang merupakan SC).

Contoh Struktur:

// app/providers/ThemeProvider.tsx
'use client'; // WAJIB!
import { createContext, useState } from 'react';// ... definisi context dan providerexport default function ThemeProvider({ children }) {
  // Gunakan useState atau hooks lain di sini
  return {children}
}// app/layout.tsx (Server Component)
import ThemeProvider from './providers/ThemeProvider';

Intinya: Server Component hanya berperan sebagai pembungkus dan pengeksekusi awal. Segala sesuatu yang memerlukan state bersama (context) harus didorong ke sisi klien.

6. Konflik dengan Library Pihak Ketiga

Ketika kamu migrasi, 99% masalahmu datang dari library pihak ketiga yang belum sepenuhnya mendukung Server Component.

Contoh umum: UI Library seperti Material UI (MUI) atau Chakra UI lama, atau library animasi seperti framer-motion, atau library notifikasi. Mereka hampir selalu mengakses window atau menggunakan hooks internal.

Saat Server Component mencoba merender library tersebut, ia langsung gagal karena tidak ada window.

Taktik Penyelamatan:

  1. Cek Dokumentasi: Selalu cek apakah library sudah merilis versi yang kompatibel dengan App Router/RSC. (Misalnya, MUI dan Chakra sudah ada adaptasinya).
  2. Dynamic Import dengan SSR Mati: Jika library benar-benar kuno dan tidak bisa diperbaiki, kamu harus memastikan library itu hanya dimuat di sisi klien. Gunakan next/dynamic dengan opsi Server-Side Rendering dimatikan:
typescript
    import dynamic from 'next/dynamic';
  1. Client Wrapper: Cara yang lebih bersih adalah membuat Client Component sederhana sebagai wrapper khusus untuk library tersebut, dan menandainya "use client". Pastikan wrapper itu hanya menerima data yang bisa di-serialize dari Server Component.

7. Memanfaatkan Data Cache di Server Component

Salah satu kekuatan tersembunyi Next.js App Router adalah automatic data caching yang terintegrasi dengan fungsi fetch standar.

Di Pages Router, kamu harus mengatur revalidate di GSP/GSSP. Di App Router, ini lebih otomatis dan terperinci:

  • Secara default, fetch di Server Component akan di-cache dan di-revalidate secara periodik (mirip ISR).

Jika kamu ingin data yang selalu fresh* (mirip SSR):

typescript
    fetch('...', { cache: 'no-store' });
  • Jika kamu ingin data di-cache untuk periode tertentu (mirip ISR):
typescript
    fetch('...', { next: { revalidate: 60 } }); // Revalidasi setiap 60 detik

Tips Profesional (Dari Tim Javapixa Creative Studio):

Saat membangun aplikasi besar menggunakan App Router, tentukan strategi caching di awal. Jangan biarkan semua fetching menggunakan default cache. Jika data kamu sangat dinamis (misalnya data live stock), gunakan cache: 'no-store'. Jika datanya statis (misalnya postingan blog), manfaatkan caching bawaan App Router untuk performa maksimum.

Kesimpulan: Next.js App Router adalah Masa Depan

Migrasi dari Pages Router ke App Router Next.js, terutama saat berhadapan dengan Server Component, memang menantang. Kamu dipaksa untuk berpikir ulang tentang pemisahan tanggung jawab antara Server dan Client.

Namun, begitu kamu menguasai konsep dasar:

  1. Default adalah Server Component.
  2. Gunakan "use client" hanya pada komponen yang benar-benar membutuhkan interaktivitas (di komponen daun).
  3. Lakukan data fetching secara async di Server Component.
  4. Patuhi batasan serialization (khususnya untuk fungsi dan objek kompleks di props).

Maka, kamu akan mendapatkan performa yang jauh lebih unggul, bundle size JavaScript yang lebih kecil, dan Time to Interactive (TTI) yang lebih cepat.

Next.js App Router, dengan arsitektur RSC-nya, adalah standar baru dalam pengembangan aplikasi web modern. Meskipun proses migrasi terasa seperti mendaki gunung, pemandangan performa di puncaknya sangat sepadan. Selamat bertualang di dunia Server Component!