SSR 元件 (Server-Side Rendering)
*是指在伺服器上預先處理並生成 HTML,然後將完整的 HTML 發送到用戶端。
在 ssr 元件中取得遠端資料方法,直接將元件轉為 async function ,在內部使用 fetch 取的資料,並用 await 等待資料。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const { API, API_PATH } = process.env; export default async function Products() { const res = await fetch(`${API}/v2/api/${API_PATH}/products/all`); const json = await res.json(); const data = json.products; return ( <> <div> 產品列表 </div> { data.map(product => { return ( <div key={product.id}> <Link href={`/products/${product.id}`}> { product.title } </Link> </div> ) })} </> ) }
|

在 server 元件中處理 loading 和 error 的方法是分別在資料夾內加入 loading.jsx 和 error.jsx
CSR 元件
在 CSR 元件中取得遠端資料的方法和一般在寫 REACT 時一樣,在 useEffect 內發出ajax 取得資料即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| export default function UserClient() { const [users, setUsers] = useState({}); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); useEffect(() => { async function fetchUser() { try { const res = await fetch('https://jsonplaceholder.typicode.com/users'); if (!res.ok) throw new Error('Fail to fetch users') const data = await res.json(); setUsers(data); } catch(err) { if (err instanceof Error) { setError(err.message) } else { setError('An UnKnown Error') } } finally { setLoading(false); } } fetchUser(); }, []);
if (loading) return <div>Loading...</div> if (error) return <div>{ error }</div> return ( <ul className="space-y-4 p-4"> { users.map(user => { return ( <li key={user.id} className="p-4 bg-white shadow-md rounded-lg text-gray-700"> <p className="font-bold">{user.name}</p> <div className="text-sm"> <p>UserName: {user.username}</p> <p>Email: {user.email}</p> <p>Phone: {user.phone}</p> </div> </li> ) })} </ul> ) }
|
資料依序取得 (Sequential Fetching)
Sequential Fetching 指的是資料按順序取得,比如先取得貼文資料,在依照每筆貼文的使用者 id 去取得使用者資料。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| export default async function page() { const res = await fetch('https://jsonplaceholder.typicode.com/posts'); const posts = await res.json(); const filteredPosts = posts.filter(post => post.id % 10 === 1) return ( <div className="p-4 max-w-7xl mx-auto"> <h1 className="text-3xl font-bold mb-8">Blog Posts</h1> <div className="grid grid-cols-1 md:grid-cols-2 gap-8"> { filteredPosts.map(post =>( <div key={post.id} className="bg-white shadow-md rounded-lg p-5"> <h2 className="text-2xl font-bold mb-3 text-gray-800"> { post.title } </h2> <p className="text-gray-600 mb-4"> { post.body } </p> <Suspense fallback={<div>Loading authors</div> }> <Author userId={post.userId}/> </Suspense> </div> ))} </div> </div> ) }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| export default async function Author({ userId }) { const res = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`) const user = await res.json(); return ( <div className='text-sm text-gray-500'> Written by: <span className='font-semibold text-gray-700 hover:text-gray-700'> { user.name } </span> </div> ) }
|
上面範例中,會先取得 posts 資料後, Author 元件內在依照傳入的 userId 取得 user 資料。
資料同時取得
如果頁面中有不相關的兩筆資料,可以用同時取得的方式減少等待時間。下面範例中使用 Promise.all 同時取得 post 和 album 資料。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| export async function getUsersPosts(userId) { const res = await fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`); return res.json(); }
export async function getUsersAlbums(userId) { const res = await fetch(`https://jsonplaceholder.typicode.com/albums?userId=${userId}`); return res.json(); } export default async function page({ params }) { const { id } = await params; const postsData = getUsersPosts(id); const albumsData = getUsersAlbums(id); const [posts, albums] = await Promise.all([postsData, albumsData]);
return ( <div className="p-4 max-w-7xl mx-auto"> <h1 className="text-3xl font-extrabold mb-8">User Profile</h1> <div className="grid grid-cols-1 md:grid-cols-2 gap-8"> <div> <h2 className="text-2xl font-bold mb-4">Posts</h2> <div className="space-y-4"> {posts.map((post) => ( <div key={post.id} className="bg-white shadow-md rounded-lg p-6"> <h3 className="text-lg font-bold mb-3 text-gray-800 leading-tight"> {post.title} </h3> <p className="text-gray-600 mb-4 leading-relaxed"> {post.body} </p> </div> ))} </div> </div> <div> <h2 className="text-2xl font-bold mb-4">Albums</h2> <div className="space-y-4"> {albums.map((album) => ( <div key={album.id} className="bg-white shadow-md rounded-lg p-6"> <p className="text-gray-700">{album.title}</p> </div> ))} </div> </div> </div> </div> ) }
|