Kyle Mo
@React 小聚
Exploring
React Server Components
From Next.js 13
Kyle Mo
Software Engineer (FE, JS Infra)
Agenda
Basic Introduction Of React Server Components
How Next.js v13 Integrate Server Components ?
Recap & Conclusions
App Directory, Server & Client Components, Data Fetching, Streaming & Suspense, Limits, Future Roadmap...
Basic INTRODUCTION OF REACT SERVER COMPONENTS
Server Components (.server.js(/ts/jsx/tsx))
Client Components (.client.js(/ts/jsx/tsx))
Share Components (.js(/ts/jsx/tsx))
Types Of Components
Hybrid Rendering
Server Component
Client Component
How The Hybrid Rendering Work ?
- Render Root
- Request For Server Components
- React Runtime Rendering
function Root() {
const [data, setData] = useState({});
// 向伺服器發送請求
const componentResponse = useServerResponse(data);
return (
<DataContext.Provider value={[data, setData]}>
componentResponse.render();
</DataContext.Provider>
);
}
Request For Server Components
Server Component Root
Server
Component 1
Client
Component 1
Server
Component 2
Client
Component 2
Request For Server Components
M1:{"id":"./src/SearchField.client.js","chunks":["client5"],"name":""}
M2:{"id":"./src/EditButton.client.js","chunks":["client1"],"name":""}
S3:"react.suspense"
M4:{"id":"./src/SidebarNote.client.js","chunks":["client6"],"name":""}
J0:["$","div",null,{"className":"main","children":[["$","section",null,{"className":"col sidebar","children":[["$","section",null,{"className":"sidebar-header","children":[["$","img",null,{"className":"logo","src":"logo.svg","width":"22px","height":"20px","alt":"","role":"presentation"}],["$","strong",null,{"children":"React Notes"}]]}],["$","section",null,{"className":"sidebar-menu","role":"menubar","children":[["$","@1",null,{}],["$","@2",null,{"noteId":null,"children":"New"}]]}],["$","nav",null,{"children":["$","$3",null,{"fallback":["$","div",null,{"children":["$","ul",null,{"className":"notes-list skeleton-container","children":[["$","li",null,{"className":"v-stack","children":["$","div",null,{"className":"sidebar-note-list-item skeleton","style":{"height":"5em"}}]}],["$","li",null,{"className":"v-stack","children":["$","div",null,{"className":"sidebar-note-list-item skeleton","style":{"height":"5em"}}]}],["$","li",null,{"className":"v-stack","children":["$","div",null,{"className":"sidebar-note-list-item skeleton","style":{"height":"5em"}}]}]]}]}],"children":["$","ul",null,{"className":"notes-list","children":[["$","li","0",{"children":["$","@4",null,{"id":0,"title":"Meeting Notes","expandedChildren":["$","p",null,{"className":"sidebar-note-excerpt","children":"This is an example note. It contains Markdown!"}],"children":["$","header",null,{"className":"sidebar-note-header","children":[["$","strong",null,{"children":"Meeting Notes"}],["$","small",null,{"children":"10:04 PM"}]]}]}]}],["$","li","1",{"children":["$","@4",null,{"id":1,"title":"Make a thing","expandedChildren":["$","p",null,{"className":"sidebar-note-excerpt","children":"It's very easy to make some words bold and other words italic with Markdown. You can even link to React's..."}],"children":["$","header",null,{"className":"sidebar-note-header","children":[["$","strong",null,{"children":"Make a thing"}],["$","small",null,{"children":"10:04 PM"}]]}]}]}],["$","li","2",{"children":["$","@4",null,{"id":2,"title":"A note with a very long title because sometimes you need more words","expandedChildren":["$","p",null,{"className":"sidebar-note-excerpt","children":"You can write all kinds of amazing notes in this app! These note live on the server in the notes..."}],"children":["$","header",null,{"className":"sidebar-note-header","children":[["$","strong",null,{"children":"A note with a very long title because sometimes you need more words"}],["$","small",null,{"children":"10:04 PM"}]]}]}]}],["$","li","3",{"children":["$","@4",null,{"id":3,"title":"I wrote this note today","expandedChildren":["$","p",null,{"className":"sidebar-note-excerpt","children":"It was an excellent note."}],"children":["$","header",null,{"className":"sidebar-note-header","children":[["$","strong",null,{"children":"I wrote this note today"}],["$","small",null,{"children":"10:04 PM"}]]}]}]}]]}]}]}]]}],["$","section","null",{"className":"col note-viewer","children":["$","$3",null,{"fallback":["$","div",null,{"className":"note skeleton-container","role":"progressbar","aria-busy":"true","children":[["$","div",null,{"className":"note-header","children":[["$","div",null,{"className":"note-title skeleton","style":{"height":"3rem","width":"65%","marginInline":"12px 1em"}}],["$","div",null,{"className":"skeleton skeleton--button","style":{"width":"8em","height":"2.5em"}}]]}],["$","div",null,{"className":"note-preview","children":[["$","div",null,{"className":"skeleton v-stack","style":{"height":"1.5em"}}],["$","div",null,{"className":"skeleton v-stack","style":{"height":"1.5em"}}],["$","div",null,{"className":"skeleton v-stack","style":{"height":"1.5em"}}],["$","div",null,{"className":"skeleton v-stack","style":{"height":"1.5em"}}],["$","div",null,{"className":"skeleton v-stack","style":{"height":"1.5em"}}]]}]]}],"children":["$","div",null,{"className":"note--empty-state","children":["$","span",null,{"className":"note-text--empty-state","children":"Click a note on the left to view something! ð¥º"}]}]}]}]]}]
module.exports = {
tag: 'Server Root',
props: {...},
children: [
{ tag: "Client Component1", props: {...}: children: [] },
{ tag: "Server Component1", props: {...}: children: [
{ tag: "Server Component2", props: {...}: children: [] },
{ tag: "Server Component3", props: {...}: children: [] },
]}
]
}
React Runtime Rendering
React Runtime
M1
Client
J0
Server
Render
Request JS Bundle
Response Text (Streaming)
Browser
Client Root
Server
App
Server
Component 1
Client
Component 1
React Runtime
Render
Response Text
(Streaming)
Advantages Of React Server Components
Reduce Bundle Size
Server Side Capabilities
Automatic Code Splitting
import ClientComponent1 from './ClientComponent1';
function ServerComponent() {
return (
<div>
// Client Component 會自動被 Code Splitting
<ClientComponent1 />
</div>
)
}
Server Components VS SSR
SSR Navigation
RSC Navigation
How Next.js v13 Integrate Server Components ?
Page Directory -> App Directory
Colocation
File Conventions
App Directory also bring...
Layouts
Server Components
Streaming
Support for Data Fetching
Easily share UI between routes while preserving state and avoiding expensive re-renders.
Making server-first the default for the most dynamic applications.
Display instant loading states and stream in units of UI as they are rendered.
async Server Components and extended fetch API enables component-level fetching.
Server & Client Components
Server Component
Client Component
When to use which ?
Limits & Recommendations
Client Components can't import Server Components directly
Props need to be serializable
Server-Only & Client-Only
Moving Client Components to the Leaves
Data Fetching In Server Components
The fetch() API
Why Fetch Data In Server Components
Component-Level Data Fetching & Caching
Parallel & Sequential Data Fetching
Static & Dynamic Data Fetching
The fetch() API
async function getData() {
const res = await fetch('https://api.example.com/...');
// The return value is *not* serialized
// You can return Date, Map, Set, etc.
// Recommendation: handle errors
if (!res.ok) {
// This will activate the closest `error.js` Error Boundary
throw new Error('Failed to fetch data');
}
return res.json();
}
export default async function Page() {
const data = await getData();
return <main></main>;
}
Automatic Request Deduplication
Provide a richer options object so that each request can separately set caching and revalidating rules
Data Fetching In Server Components
對於 backend data resources 有直接的存取權,例如 database 或是 file system
在 Server Side 可以避免一些敏滿資訊洩漏到 Client Side,例如 access token, API key...
讓資料的抓取與渲染在同一個環境中進行,以減少 client side 與 server side 間 back-and-forth 的溝通,可以盡量降低 client side 的工作量
減少 client-server 間的 waterfall 請求
How about data fetching in Client Components ?
import { use } from 'react';
export function Data({ url }) {
const data = use(fetch(url).thrn(res => res.json()))
return <pre>{JSON.stringify(data, null, 2)}</pre>
}
Components-Level Data Fetching
function Page({ data }) {
// Render data...
}
// This gets called on every request
export async function getServerSideProps() {
// Fetch data from external API
const res = await fetch(`https://.../data`)
const data = await res.json()
// Pass data to the page via props
return { props: { data } }
}
export default Page
async function getData() {
const res = await fetch('https://api.example.com/...');
// The return value is *not* serialized
// You can return Date, Map, Set, etc.
// Recommendation: handle errors
if (!res.ok) {
// This will activate the closest `error.js` Error Boundary
throw new Error('Failed to fetch data');
}
return res.json();
}
export default async function Page() {
const data = await getData();
return <main></main>;
}
// child component...
export default async function Child() {
const data = await getData();
return <p>child</p>
}
Caching
Cache Function (RFC)
The fetch() API is wrapped with cache
How about GraphQL POST endpoint ?
Parallel & Sequential Data Fetching
Parallel Data Fetching
Parallel Data Fetching (Better UX)
export default async function Page({ params: { username } }) {
// Initiate both requests in parallel
const artistData = getArtist(username);
const albumData = getArtistAlbums(username);
// Wait for the artist's promise to resolve first
const artist = await artistData;
return (
<>
<h1>{artist.name}</h1>
{/* Send the artist information first,
and wrap albums in a suspense boundary */}
<Suspense fallback={<div>Loading...</div>}>
<Albums promise={albumData} />
</Suspense>
</>
);
}
// Albums Component
async function Albums({ promise }) {
// Wait for the albums promise to resolve
const albums = await promise;
return (
<ul>
{albums.map((album) => (
<li key={album.id}>{album.name}</li>
))}
</ul>
);
}
Sequential Data Fetching
Static & Dynamic Data Fetching
fetch() API - Static By Default
減少 Server 與 DB 的負擔
降低頁面的 Loading Time
Per Page
Per Request
Streaming & Suspense
Without Streaming & Suspense
- First, all data for a given page is fetched on the server.
- The server then renders the HTML for the page.
- The HTML, CSS, and JavaScript for the page are sent to the client.
- A non-interactive user interface is shown using the generated HTML, and CSS.
- Finally, React hydrates the user interface to make it interactive.
Streaming
Suspense
Limits
CSS-In-JS (ref)
Third Party Libraries
Mutation (ref)
Hard to know is Server or Client Components
Mutation
import Todo from './todo';
interface Todo {
id: number;
title: string;
completed: boolean;
}
async function getTodos() {
const res = await fetch('https://api.example.com/todos');
const todos: Todo[] = await res.json();
return todos;
}
export default async function Page() {
const todos = await getTodos();
return (
<ul>
{todos.map((todo) => (
<Todo key={todo.id} {...todo} />
))}
</ul>
);
}
Mutation
"use client";
import { useRouter } from 'next/navigation';
import { useState, useTransition } from 'react';
interface Todo {
id: number;
title: string;
completed: boolean;
}
export default function Todo(todo: Todo) {
const router = useRouter();
const [isPending, startTransition] = useTransition();
const [isFetching, setIsFetching] = useState(false);
// Create inline loading UI
const isMutating = isFetching || isPending;
async function handleChange() {
setIsFetching(true);
// Mutate external data source
await fetch(`https://api.example.com/todo/${todo.id}`, {
method: 'PUT',
body: JSON.stringify({ completed: !todo.completed }),
});
setIsFetching(false);
startTransition(() => {
// Refresh the current route and fetch new data from the server without
// losing client-side browser or React state.
router.refresh();
});
}
return (
<li style={{ opacity: !isMutating ? 1 : 0.7 }}>
<input
type="checkbox"
checked={todo.completed}
onChange={handleChange}
disabled={isPending}
/>
{todo.title}
</li>
);
}
Roadmap
Conclusion
Conclusion
Thank You
React Server Component Introduction feat Next.js
By oldmo860617
React Server Component Introduction feat Next.js
- 638