Server และ Client Components
ไอเดียหลักในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียหลักในหนึ่งประโยค”ใน App Router ทุก component เป็น Server Component จนกว่าไฟล์จะบอกว่า "use client" และบรรทัดเดียวนี้ลากเส้น boundary ที่ตัดสินว่าโค้ดไหนจะไปถึง browser
Server Components คือ default
หัวข้อที่มีชื่อว่า “Server Components คือ default”component ใน app/ รันบน server เว้นแต่คุณจะ opt out นั่นแปลว่าเป็น async ได้, await data ได้ และแตะ database, filesystem หรือ secret ตรง ๆ ได้ — โค้ดพวกนี้ไม่ถูกส่งไป browser เลย
// app/dashboard/page.tsx — a Server Component (no directive needed)import { db } from '@/lib/db';
export default async function Dashboard() { const users = await db.user.findMany(); // runs on the server only return <ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;}เพราะสิ่งนี้ไม่เคยถูกส่งไป client browser จึงไม่ต้องดาวน์โหลด JavaScript ของ component พวกนี้เลยสักไบต์ Server Components ใช้ useState, useEffect, event handler หรือ browser API ไม่ได้ — เพราะ render ครั้งเดียวบน server แล้วจบ
”use client” มาร์กให้เป็น Client Component
หัวข้อที่มีชื่อว่า “”use client” มาร์กให้เป็น Client Component”วาง "use client" ไว้บนสุดของไฟล์ แล้วทุกอย่างที่ไฟล์นั้น export จะกลายเป็น Client Component ซึ่งรันใน browser และใช้ hook, state, effect, event handler และ browser API ได้
'use client';
import { useState } from 'react';
export default function Counter() { const [count, setCount] = useState(0); return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;}จุดที่ละเอียดอ่อนคือ boundary นี้เป็น transitive ทุก module ที่ Client Component import เข้ามาจะถูกดึงเข้า client bundle ด้วย ไม่ว่าจะมี directive ของตัวเองหรือไม่ คุณมาร์ก boundary ครั้งเดียวที่บนสุดของ tree — ไม่ต้องใส่ "use client" ซ้ำในทุก child
การประกอบข้าม boundary
หัวข้อที่มีชื่อว่า “การประกอบข้าม boundary”มีสองกฎที่คุมว่าทั้งสองชนิดผสมกันยังไง และไม่สมมาตร —
- Server Component import และ render Client Component ได้ตรง ๆ
- Client Component import Server Component ไม่ได้ — แต่รับเข้ามาเป็น
childrenหรือ prop ได้
กฎข้อที่สองคือ doughnut (slot) pattern — Client Component เป็นวงแหวน และ Server Component เติมเข้าไปในรู เนื้อหาที่ render บน server ยังอยู่บน server แม้จะมี client component ล้อมรอบ
// app/page.tsx — Server Componentimport ClientShell from './client-shell';import ServerData from './server-data';
export default function Page() { return ( <ClientShell> <ServerData /> {/* stays a Server Component, passed as children */} </ClientShell> );}มีข้อจำกัดหนึ่งที่ทำให้สิ่งนี้ทำงานได้ — prop ที่ข้าม boundary ต้อง serializable คุณส่ง string, number, plain object, array และ JSX ได้ แต่ส่ง function, class instance หรือ Date เป็น prop ดิบ ๆ ไม่ได้ — ค่าพวกนี้รอดการเดินทางจาก server ไป client ไม่ได้
flowchart LR Root[Server Component root] --> Client["use client boundary"] Root --> Server[More Server Components] Client --> Bundle[Imports join client bundle] Root -. passes children .-> Client Client -. renders server child .-> Server