ข้ามไปยังเนื้อหา

Fetch ใน Server Components

Server Component เป็น async function จึง await fetch หรือ query database ได้ ตรง ๆ ในตัว component ไม่ต้องมี useEffect ไม่ต้องมี loading state และไม่ต้องยิงจาก client ไปหา API

ใน App Router component รันบน server เรา fetch ตรงจุดที่ render และ data ไม่หลุดเข้า client bundle จนกว่าจะส่ง HTML ออกไป

app/products/page.tsx
export default async function ProductsPage() {
// Runs on the server — the API key and response stay server-side
const res = await fetch('https://api.example.com/products')
const products = await res.json()
return <ProductList products={products} />
}

เพราะ code รันบน server เราจึงคุย database หรือ ORM ได้ตรง ๆ ไม่ต้องมี API route คั่นกลาง

app/users/page.tsx
import { db } from '@/lib/db'
export default async function UsersPage() {
// Query the database straight from the Server Component
const users = await db.user.findMany()
return <UserList users={users} />
}

ถ้า await request แรกแล้วค่อย await ตัวถัดไป ตัวที่สองจะเริ่มไม่ได้จนกว่าตัวแรกจะเสร็จ นั่นคือ waterfall เมื่อ request ไม่ได้ขึ้นต่อกัน ให้ยิงพร้อมกันด้วย Promise.all

app/dashboard/page.tsx
async function getUser(id: string) {
const res = await fetch(`https://api.example.com/users/${id}`)
return res.json()
}
async function getPosts(id: string) {
const res = await fetch(`https://api.example.com/users/${id}/posts`)
return res.json()
}
export default async function Dashboard() {
// Parallel: both requests fire at once, no waterfall
const [user, posts] = await Promise.all([getUser('1'), getPosts('1')])
return <Profile user={user} posts={posts} />
}

ภายใน render pass เดียว Next.js ทำ memoize fetch ที่เหมือนกันให้อัตโนมัติ เรียก URL เดิมจากสาม component แต่ network เห็นแค่ request เดียว ส่วนงานที่ไม่ใช่ fetch เช่น query database ให้ห่อด้วย cache() ของ React เพื่อ dedupe แบบ per-request เหมือนกัน

lib/user.ts
import { cache } from 'react'
import { db } from '@/lib/db'
// Called by many components in one render — the query runs once per request
export const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } })
})

Client Component ใช้ await ในตัวเองไม่ได้ และห้ามแตะ database ตรง ๆ ต้องเข้าถึง data ผ่าน Route Handler หรือ data library ที่รันบน server แทน

app/search/search-box.tsx
'use client'
import { useState } from 'react'
export function SearchBox() {
const [results, setResults] = useState([])
async function onSearch(q: string) {
// Client components fetch through a Route Handler, never the DB
const res = await fetch(`/api/search?q=${q}`)
setResults(await res.json())
}
return <input onInput={(e) => onSearch(e.currentTarget.value)} />
}
flowchart LR
  A["Request"] --> B["await getUser()"]
  B --> C["await getPosts()"]
  C --> D["Render (late)"]
  A --> E["Promise.all"]
  E --> F["getUser() + getPosts() together"]
  F --> G["Render (sooner)"]
Sequential waterfall vs. parallel fetching
async Server Component ได้ data มายังไง?
ทำไมต้องห่อสอง fetch ที่ไม่ขึ้นต่อกันด้วย Promise.all?
cache() ของ React ให้อะไรกับ query database?
Client Component ต้องใช้ data จาก server ควรทำยังไง?