# Next.js 13 - App router
## Next 如何渲染 Server Component?
> render 的工作會根據 router 和 suspense 切分成 chunk 來載入
### chuck 執行兩個步驟
1. 取得 React Server Component Payload:React 先 render Server Component 成 RSC Payload
> RSC Payload(壓縮的二進位內容)說明 rendered 的 React Server Components tree,會包含:
> 1. Server Components 的 rendered result
> 2. 紀錄哪裡會有 Client Component
> 3. Server Components 要傳給 Client Component 的 props
>
> 這也是為什麼 Server Component 只能包含可序列化的資料,不能包含 function。
2. 在 server 渲染出 HTML:Next 根據 RSC Payload,並把其他的 Client Component (其他的 JavaScript chunk) 在 server 進行 render,轉成初始的 HTML
### client 端 (接收內容的 browser)
1. 得到 initial HTML 產生 router 的初始畫面 (initial page load only)。
2. 有 RSC Payload 後,React 能 reconcile Server component 與 Client component 來建構 Component tree,並操作 DOM 完成更新。接著再根據發 request 來取得 Client component 要做互動性的 chunk。
> [React source code:Component tree 裡不同 tag 的內容是如何被判斷要進行什麼處理](https://github.com/facebook/react/blob/9d17b562ba427adb1bced98f4231406ec573c422/packages/react-client/src/ReactFlightClientStream.js#L44-L60)
3. streaming 得到 Client component chunk,進行 hydrate 來更新 Client component 的互動性到 DOM,讓 Application 能完整運作。
言下之意,Server Component 與 SSR 都會在網頁原始碼上顯示,都有利於爬蟲。但主要差異在於產生 initial page 的 HTML 可能是不一樣的。
舉例來說,一般而言 page 都會預設是 static render,所以無論是 RSC 或 RCC 的初始內容都會被放到 initial HTML。
但 RSC 可以以 async 讀取 fetch 的資料,也就是到了 NextJS v13 的 App Router 後,單純使用 fetch 就可以表達出資料是以 getStaticProps 還是 getServerSideProps。然而,如果是 Client component,仍然需要搭配 useEffect 來 render API data 在畫面,在 initial HTML 是不會有 useEffect 產生的資料。
所以,如果是 NextJS v13 的 Client component 沒有接收 Server component 傳下來的 props,而資料來源是透過 useEffect 再更新到 DOM 上,那麼初始狀態不會包含這些 API data,所以在 loading Client component 的時候,可能都只包含了 skeleton。
## Render Pattern
> TL;DR;
>
> render pattern 是指 client routes 在什麼階段下進行 render HTML。由於 Next13 有 Server Component 的特色,所以進行 cache 主要可以分為 full-route cache 和 data cache 的處理。(RSC Payload 和 Fetching Data 是分開處理)
>
> Nextjs 會自動根據一個 route 的 Component + Fetching method 來決定要使用 static rendering 和 dynamic rendering。開發者需要決定的內容是:
> 1. 使用的 Component 類型
> 2. Fetching data
1. 完全 cached (SSG)
2. revalidate > 0 (ISG)
3. revalidate = 0 (SSR)
> 3. 是否使用到 dynamic function
### Static rendering (預設) > 採用 CDN 進行 cached
1. 在 build time 已經完成 (SSG)
2. data revalidation 完成後在 background 執行 (ISG)
如果 page.js 沒有加上 "use client" 去改變預設頁面以 Server Component 進行處理與其他調整,則該頁的內容會被判定為 SSG
### Dynamic rendering
1. 在每次 request 時進行 (SSR)
Next 有提供 dynamic function,如果有使用的話,該 route 會自動變成 dynamic rendering。
#### Dynamic Functions
- `cookies()` + `headers()`:
1. 在 Server Component 使用
2. 使用會讓整頁會變成 SSR
- `useSearchParams()`:
1. 在 Client Component 使用
2. 自動尋找一個最靠近有使用 Suspense 的 parnet (官方推薦使用的話可以把這個 Client 抽出來,並直接包上 <Suspense /> 避免其他內容失去 static rendering)
- searchParams:
1. Page props
2. 使用會讓整頁會變成 SSR
### Streaming
Next13 的 Client component chunk 本身是使用 streamnig 的方式來顯示 UI,所以 hydrate 也可以變成 chunk,因為網站的 TTI 也能提升。
### 實際使用
承接上面描述,根據設定 fetch API 的 options,Next.js 會自動判斷要使用什麼 render pattern。
- SSR 產生邊境牧羊犬的圖片 (`app/border-collie/page.tsx`)
```ts
import Image from 'next/image';
async function getBorderCollie() {
const response = await fetch(
'https://dog.ceo/api/breed/collie/border/images/random',
{
cache: 'no-store',
},
);
const data = await response.json();
return data.message;
}
export default async function BorderCollie() {
const dog = await getBorderCollie();
return (
<div className="text-lg">
<Image src={dog} alt="" />
</div>
);
}
```
- SSG 生成鬆獅犬的圖片 (`app/chow/page.tsx`)
```ts
import Image from 'next/image';
async function getChow() {
const response = await fetch('https://dog.ceo/api/breed/chow/images/random');
const data = await response.json();
return data.message;
}
export default async function Chow() {
const dog = await getChow();
return (
<div className="text-lg">
<Image src={dog} alt="" />
</div>
);
}
```
- 執行 next build 的結果
```bash
Route (app) Size First Load JS
┌ ○ / 137 B 78.6 kB
├ λ /border-collie 178 B 83.6 kB
└ ○ /chow 178 B 83.6 kB
+ First Load JS shared by all 78.4 kB
├ chunks/596-c294a7d39d9fe754.js 26.1 kB
├ chunks/fd9d1056-a99b58d3cc150217.js 50.5 kB
├ chunks/main-app-89ee0ba7722c6c8b.js 219 B
└ chunks/webpack-28022adcc3d465a2.js 1.64 kB
λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps)
○ (Static) automatically rendered as static HTML (uses no initial props)
```
### 可以針對最新 n 秒的資料產生 static page,避免全部 route 都變成 dynamic
- render pattern 可以是 layout (包含 head), page (只有 body), fetching data (部分 component),又稱作 [Route Segment Config](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config)
- 在 page 使用 generateStaticParams 來記錄,讓部分 dynamic routes 可以有 static 產生(感覺也適合做多國語言)
- 不包含在 generateStaticParams 的 dynamic routes,則可以搭配在 layout 設定 dynamicParams (Boolean),來決定要 SSR 產生 page,還是其他 fallback (default: 404) > 類似之前寫 getServerSideProps 或 getStaticProps 設定 fallback。
> [完整的 functions 介紹](https://nextjs.org/docs/app/api-reference/functions)
> [Next.js 13 SSG, SSR & ISR | Nextjs 13 tutorial](https://www.youtube.com/watch?v=E1HzFvXgrCs)
## Fetching Data
### React 的 Fetch API 有對 GET 進行快取
React 的 Fetch 有進行過一層封裝,所以在 React 延伸的框架中, Fetch API 中的 GET 自帶 Data Cache 的機制。([RSC Payload 和 Fetched Data 是分開處理](https://nextjs.org/docs/app/building-your-application/caching#react-cache-function))
- 有 Server Component 後,跟 db 直接互動的語法也可以使用(以前端來說是 firebase)
```ts=
import { cache } from 'react'
import db from '@/lib/db'
export const getItem = cache(async (id: string) => {
const item = await db.item.findUnique({ id })
return item
})
```
### Next 封裝 Web 的 Fetch API
- `options.cache`
- `force-cache`:Next.js 如果有找到對應的 request 請求會直接拿 cache data;反之,則會向 endpoint 拿最新的資料,並儲存成 cache data
- `no-store`:Next.js 不考慮有無 cache data 都會直接發請求
1. 決定 fetch 會如何與 Next 的 cache data 互動
2. 如果沒有使用 dynamic function,則 default 都是採用 force-cache。
- `options.next.revalidate`
1. 以「秒」為單位決定 cache data 的使用期間
2. 有三種狀況可以設定
1. false:表示不需要 revalidate,直接拿 cache data
2. 0 (number):不會把請求拿到的資料寫成 cache
3. \> 0 (number):在多久期間內都先使用 cache data
3. 如果同一個 route 對同一支 endpoint 設定不同 revalidate,會取 lower revalidate
> 建議:可以按照 revalidate 的設定再決定使用的 cache,不然蠻容易寫出 error
- `options.next.tags`
1. 紀錄 cache data,協助判斷一個 fetcher 是否需要進行 revalidate
## Cache:Route & Data
在 NextJS v13 後,將快取分拆成針對 router 跟 data 進行,加大了頁面與資料的取用彈性。綜合 render pattern 和 fetching data 的知識,主要可以分為幾個層面來處理快取:
1. fetcher 的執行:不同 component 拿資料的狀況
2. static router 是 static、特定時間 revalidate、永遠產生全新 HTML
3. dynamic router 是否有 static 內容 和 其他同 static router 的快取設定
## 分拆 Server Component 與 Client Component 的技巧
### 組合不同 component 的限制
主要閱讀[這篇官方文件](https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns),說明兩者在搭配時,會遇到一些使用限制:
1. Server Component 不能寫 event handler
2. Server Component 也不能使用 hook (也意味不能共用 context data)
```ts
export const ServerC = () => {
// ❌ ReactServerComponentsError: You're importing a component that needs useState. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.
const [msg, setMsg] = useState('')
return (
<div className="text-lg">
{/* ❌Error: Event handlers cannot be passed to Client Component props */}
<button onClick={() => setMsg('Hello!')}>
{msg}
</button>
</div>
);
};
```
3. utils 要特別區分
官方建議只在 server side 會用到的共用 script,可以安裝 `server-only` 這個 package 來協助在 build time 發現錯誤。
```ts
import 'server-only'
export async function getData() {
const res = await fetch('https://external-service.com/data', {
headers: {
authorization: process.env.API_KEY,
},
})
return res.json()
}
```
### 使用 children 傳遞:解決不能在 Client Component 中 import Server Component
### JSX.Element 可以是 props:解決 Server Component 無法傳 common component 給 Client Component (e.g. image, inline svg)
```ts
// client component
'use client';
const ClientC = (props: { compoent: () => JSX.Element }) => {
return (
<div>
<props.compoent />
</div>
);
};
export { ClientC };
// server component
import { TokenIcon } from '@/app/AccessFaucetForm/styles';
import { ClientC } from './ClientC';
const CommonComponent = (props: { Compoent: () => JSX.Element }) => {
return (
<div id="hello">
<props.Compoent />
</div>
);
};
export const ServerC = () => {
return (
<>
{/* ❌ Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server". */}
<ClientC compoent={TokenIcon} />
{/* ✅ 可以直接傳 function 給 server component */}
<CommonComponent Compoent={TokenIcon} />;
</>
);
};
```
---
## 實際查看專案
> 分析案例:[MindChat - Batch#20 前端作品](https://www.mindchat.me/)
在 `</body>` 前透過 async 的方式載入 `<script>`,藉此在 HTML parse 完成後產生 DOM tree,透過 React Runtime 去理解 RSC Payload,並確認哪些內容可以直接以 JavaScript 解讀 RSC Payload(JSON 文檔)來完成 DOM 更新。接著其餘的 Client Component 則還需要向 Server 請求 JavaScript 的 chunk,以 hydrate 的方式慢慢 render 到畫面上,來完成互動性。
`預設為 SSG`
首頁以 Server Component 載入,如果無其他設定 (fetch option、dynamic function),預設為 SSG。

`使用 "use client"`
在 page.tsx 加上 "use client",整頁會變成 CSR,也因此會把整頁產生 Component 的方式變成一個大的 .js。如:signin | signup | map 都是這個模式,所以只有一個大的 .js 在各自的 dir 底下,裡面會記錄如何 hydrate 一個 element 的互動性。
像是如果在 page 或 layout 也有這類型的 chunk,是因為有些 Next 提供的 component,也是 client component (像是 <Link />),它們的 hydrate 內容會同樣紀錄在這。

`<Link /> 有支援 prefetch 資源`
hover Logo 跟 SIGN IN 時,如果沒有 RSC Payload 的 cache data,則會再拿了一次 RSC Payload;反之如果網頁沒有特別設定,預設走 SSG,這些內容都會被 cache 起來。除非改變 revalidate 的時間,或是 SSR 載入頁面,不然都會拿到重複的 HTML 和 RSC Payload。
<img src="https://hackmd.io/_uploads/r1gVQJqt1p.png"/>
---
## App Router 生態系支援度
### Netlify:App Router 在 Vercel 的支援度比較好
`Features 支援度`
目前比較多的 issue 是關於 Suspense、Navigator、Serverless functions
> - [針對 NextJS v13 的 runtime 集合串](https://github.com/netlify/next-runtime/discussions/1724)
> - [<Link /> 有實現了 prefetch 能在 client side 先拿到資源](https://github.com/netlify/next-runtime/pull/1855)
`Deployment`
目前佈署的生態系整合還不夠完善,可能只有 Vercel 是目前處理最好。舉例來說,像是直接在瀏覽器輸入特定網址,會有 Server error,原因是初始的環境變數 __NEXT_PRIVATE_PREBUNDLED_REACT 預設造成。其他的設定還有關 trailing slash 和 prefetch 都還需要額外的設定才能順利讓 App Router 運行。
> [My experience deploying a Next.js application on Netlify (using Next App Router)](https://dev.to/alvesjessica/my-experience-deploying-a-nextjs-application-on-netlify-using-next-app-router-3k1b)
### UI library
- `MUI` - 發現 MUI 也在為了 Next13 的 App Router 做準備,這支 PR 為 UI component 與 type 定義加上 ‘use client’ ([PR#37656](https://github.com/mui/material-ui/pull/37656))。同時也更新文件,說明如何在 App Router 中使用 MUI,也提供 Next 相關文件的引導,讓之後的 migrate 可以更 smooth。([MUI GitHub: Guide - Next.js App Router](https://github.com/mui/material-ui/blob/master/docs/data/material/guides/next-js-app-router/next-js-app-router.md))
## 問題紀錄
- [Windows 上建置 Next13.4.x 專案除錯紀錄](https://medium.com/@liaoweil05621/windows-上建置-next13-4-x-專案除錯紀錄-eea1f01a1fb8)