Batasan & Aturan Server-Client
Cheat sheet aturan: apa yang boleh lewat boundary dan apa yang tidak.
Tiga aturan boundary
Seluruh drama server vs client diringkas menjadi tiga aturan:
- Server → Client: hanya props yang serializable (string, number, boolean, plain object, array). Fungsi,
Date,Map, class instance: tidak bisa lewat. - Client → Server: tidak bisa mengimpor. Kalau butuh konten server di dalam client, pakai pola children/slot.
- Arah import: Server Component boleh mengimpor Client Component. Sebaliknya dilarang.
Data yang lewat boundary diserialisasi menjadi payload teks mirip JSON. Fungsi tidak bisa diserialisasi dengan aman, dan Date kehilangan prototype-nya.
Contoh 1: Date harus jadi string dulu
// app/blog/page.tsx (server)
// ❌ SALAH: oper objek Date langsung
import CommentList from "./CommentList";
export default async function BlogPage() {
const posts = await db.post.findMany();
return <CommentList posts={posts} now={new Date()} />;
}// app/blog/page.tsx (server)
// ✅ BENAR: serialisasi di server, parsing di client
import CommentList from "./CommentList";
export default async function BlogPage() {
const posts = await db.post.findMany();
const plainPosts = posts.map((p) => ({
...p,
createdAt: p.createdAt.toISOString(), // Date -> string
}));
return <CommentList posts={plainPosts} now={new Date().toISOString()} />;
}// app/blog/CommentList.tsx
"use client";
export default function CommentList({
posts,
now,
}: {
posts: { id: string; title: string; createdAt: string }[];
now: string;
}) {
const nowDate = new Date(now); // parsing kembali di client, aman
return (
<ul>
{posts.map((p) => (
<li key={p.id}>
{p.title} · {new Date(p.createdAt).toLocaleDateString("id-ID")}
</li>
))}
</ul>
);
}Pola umumnya: sederhanakan data menjadi plain object selagi masih di server, bangun kembali tipe kaya (Date, dsb.) di client kalau perlu.
Contoh 2: fungsi tidak bisa jadi props, pindahkan logikanya
// app/todo/page.tsx (server)
// ❌ SALAH: oper fungsi handler ke client
import TodoList from "./TodoList";
export default async function TodoPage() {
const todos = await db.todo.findMany();
async function handleDelete(id: string) {
"use server";
await db.todo.delete({ where: { id } });
}
return <TodoList todos={todos} onDelete={handleDelete} />;
}Catatan: fungsi yang ditandai "use server" (Server Action) memang boleh lewat boundary karena didaftarkan Next.js sebagai endpoint khusus. Yang dilarang adalah fungsi biasa yang tidak bisa diserialisasi. (Server Action dibahas tuntas di modul tersendiri.)
Kalau belum sampai ke Server Action, alternatif paling sederhana: pindahkan logika handler ke dalam Client Component itu sendiri.
// app/todo/TodoList.tsx
"use client";
import { useState } from "react";
export default function TodoList({
todos,
}: {
todos: { id: string; title: string }[];
}) {
const [items, setItems] = useState(todos);
async function handleDelete(id: string) {
await fetch(`/api/todos/${id}`, { method: "DELETE" }); // via Route Handler
setItems((prev) => prev.filter((t) => t.id !== id));
}
return (
<ul>
{items.map((t) => (
<li key={t.id}>
{t.title} <button onClick={() => handleDelete(t.id)}>Hapus</button>
</li>
))}
</ul>
);
}Katalog error umum
| Pesan error | Penyebab | Solusi |
|---|---|---|
| "You're importing a component that needs useState" | Komponen butuh state tapi file-nya tanpa "use client" | Tambahkan "use client" di baris pertama file itu |
| "Functions cannot be passed to Client Components" | Mengoper fungsi biasa sebagai props | Tandai sebagai Server Action ("use server") atau pindahkan logika ke client |
| "Only plain objects can be passed" | Mengoper class instance, Map, atau Date | Sederhanakan jadi plain object/string di server |
window is not defined | Browser API dipakai di Server Component | Pindahkan ke Client Component |
Event handler (onClick) tidak jalan di server | Handler ditulis di Server Component | Ekstrak tombol/handler ke Client Component kecil |
Tips debugging: baca pesan error overlay baik-baik, Next.js biasanya menyebut nama file pelanggarnya. Lalu tanyakan: "file ini server atau client?" dan "apa yang lewat boundary di sini?"
Tantangan
Perbaiki error
Sengaja buat error: oper new Date() dan sebuah fungsi ke Client Component. Baca pesan error-nya, lalu perbaiki dengan serialisasi yang benar.