### SSR 讓你造成的誤解
#### 前情提要:
在 next 中說的 client component 指的是 CSR component + SSR page,要區別是否是 client component 或是 server component 在於 pre-render + hydrate 是否都在 server 端去執行,所以 SSR(getServerSideProps) 的 component 屬於 client component 範疇。
**實作 getServerSideProps 在next 中會遇到的狀況:**
1. app : 因為 next 對於 server component的解析會分辨 app 中的 component 是 client component 或是 server component,差別在於:
**server component**: 所有的 html css js 甚至是 hydrate 都在 server 完成 client 端只是負責接收 chunk response 去 render,所以你無法在 server component 中用 getServerSideProps。
**client component**: next 不會去執行或是去渲染給出 inition html,所以在 app folder 中使用 getServerSideProps 是很詭異的事情,因為不會去 trigger getServerSideProps 這個 function,同時你這個頁面也不會有原本 ssr效果,會是白頁。
** 小節:** 你不能在 app folder 中使用 getServerSideProps
2. page : 回傳 inition html 、 json payload 、css,讓client端負責下載以上內容後,在進行 hydrate,
補充:不管你是在 page 或是 app 所有 web 內容都是 serverSide,差別在於他們解析模塊的方式不同。
#### 模塊解釋:
app: chunk response,所有內容都完成瀏覽器只是負責渲染。
page: json payload ,會有inition html 、 json data、bundle js 、 css,client 端負責 hydrate。
```typescript
// chunk response
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: [] },
]}
]
}
```
總結:
1. hydrate 時機點,app大部分比page都在server完成,所以這大大減少 ttl 時間,page 可能會有等待 ttl 問題( 儘管 UI 渲染好但你的 js 目前無法 interactive)
### redirect not use in try cache
因為 redirect 本身會 throw 整個 error 出來讓 next 做出 redirect 功能,這樣導致redirect 在try cache中無法引用,因為如果包起來沒有 `throw error` `next` 在 `global` 中知道要轉頁,解決方式如 `handleSubmitBeforeAfter` 用 `isRedirectError` 判斷是不是 `redirect error` 然後 `throw error` 出去 :

https://github.com/vercel/next.js/issues/49298#issuecomment-1537433377
或是在 `try catch` 中再次 `redirect`
```typescript
export async function createCourse(form: FormData) {
try {
const data = {
id: CourseId.random().value,
name: form.get("name") as string,
duration: form.get("duration") as string,
}
const url = "mongodb://localhost:27017/next13-fullstack"
const client = MongoClientFactory.createClient("mooc", { url })
const repository = new MongoCourseRepository(client)
const course = Course.fromPrimitives(data)
await repository.save(course);
redirect("/", RedirectType.push)
} catch (error) {
let message= "Something went wrong"
console.log(error)
if (error instanceof Error) {
message = error.message
}
redirect(`/?error=${message}`, RedirectType.push)
}
}
```
## slot
react 會用 slot 方式處理 server component render 的原因,有一部分是設計規則。
```typescript
'use client'
// This pattern will **not** work!
// You cannot import a Server Component into a Client Component.
import ExampleServerComponent from './example-server-component'
export default function ExampleClientComponent({
children,
}: {
children: React.ReactNode
}) {
const [count, setCount] = useState(0)
return (
<>
<button onClick={() => setCount(count + 1)}>{count}</button>
<ExampleServerComponent />
</>
)
}
```
你不能直接在 client component 中直接引用 server component,這樣會造成 server 無數次往返,導致 react 無法正確在 client 端 render ExampleServerComponent,以上的寫法目前 server component 不能這樣做。
但 如果你需要在client component 的 children 中,加入 server component的話改成這樣寫法就ok。
```typescript
'use client'
import { useState } from 'react'
export default function ExampleClientComponent({
children,
}: {
children: React.ReactNode
}) {
const [count, setCount] = useState(0)
return (
<>
<button onClick={() => setCount(count + 1)}>{count}</button>
{children}
</>
)
}
// some page
<ExampleClientComponent>
<ExampleServerComponent/>
</ExampleClientComponent>
```
## Layout vs template
### Layout
只會渲染一次不會重新 `render` 好處是可以保留靜態資源
#### Layouts do not receive searchParams
因為 `layout` 永遠不會 `render` 所以他會在導頁時候會保留 `searchParams` 的狀態,例如底下的 `/dashboard/settings` 跟 `/dashboard/analytics` ,如果你在 `layout` 中有 `searchParams` 的 `common UI` 永遠都不會 `render`

所以如果你有關於 `common searchParams UI` 你需要包一個 `ClientComponent` 如此這個 `ClientComponent` 就不會被`layout` 的 `render` 受限
```typescript
import { ClientComponent } from '@/app/ui/ClientComponent'
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<>
<ClientComponent />
{/* Other Layout UI */}
<main>{children}</main>
</>
)
}
```
`layout` 還有一個建議是不要在這邊做 `auth` 驗證,這會讓整個頁面失去 `PPR` 的優勢,因為如果在 `layout` 做驗證,因為會呼叫 `get cookie` 的 `function` 而這個 `function` 就會被 `next` 當作是 `dynamic function` 變相告訴 `next`
### template
跟 `layout` 相反他會在每次 `render page` 的時候都會觸發,所以很適合做一些需要 `reset` 的共用 `UI` 例如 `page` 的 `transition` 或是 `breadcrumb`
```typescript
"use client"
import { usePathname } from "next/navigation"
export default function Template({ children }: { children: React.ReactNode }) {
const pathname = usePathname()
return (
<div className="animate-in slide-in-from-bottom-30 duration-500 p-4 ">
<pre className="inline-block bg-muted px-2 py-1 rounded-lg">{pathname}</pre>
{children}
</div>
)
}
```
### Layout approach
1. 基於 layout 做 auth 的前提是如果只需要做單次的驗證就可以的話就可以使用這個 `pattern` 在頂層確認 `user` 的 `access token` 存在與否有就可以進入到 `page` 反之就到 `sign in` ,但這樣有一個風險會是可能在 `next` 傳送 `json payload` 給 `browser render` 的 `chunk` 會有入要燈入後才能看到的內容被包起來。
```typescript
export default function Layout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const isAuthenticated = false
if (!isAuthenticated) {
redirect('/login')
}
return (
<div>
{children}
</div>
);
}
```

### Next page rendering flow
1. `browser` 向 `nextjs` 請求 `render page` 跟 `layout`
2. `layout` 跟 `page` 的 `render` 彼此是平行的,且 `layout` 只會 `render` 一次最終 `next` 會做最後的組裝 `page` + `layout`
3. 如果 `user` 拜訪另外一個 `page` 是基於同一個 `layout` 底下的話同樣的 `layout` 不會再重新渲染,而是直接請求新的 `page` 然後再組裝離這個 `page` 的 `layout`

## 每個頁面去驗證
另一個則是在 `page` 頁面每次都重新發送請求,這樣也不會有內容外洩的疑慮,同時也可以 `dynamic` 的確保 `user` 的 `auth` 情況來決定 `context` 要是什麼。
```typescript
import React from 'react'
const geyPaidContent = (token: string) => {
if (token !== 'correct') return 'Login to view this page'
return 'subscribe to me!'
}
function PaidPage() {
const token = 'exampletoken'
const paid = geyPaidContent(token)
return (
<div>{paid}</div>
)
}
export default PaidPage
```

## Middleware
另外一個最安全且保有彈性的寫法是基於 `middleware` 去做身份驗證,透過 `middleware` 去驗證 `user token` 存在與否,同時也不像在 `layout` 只會 `render` 一次,也不會有 `senstive data` 外流的狀況

```typescript
import { unauthorized } from 'next/navigation'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
// This function can be marked `async` if using `await` inside
export function middleware(request: NextRequest) {
const sessionToken = request.cookies.get('authToken')?.value
const isPaid = false
if (!isPaid && sessionToken) {
unauthorized()
}
if (!isPaid && !sessionToken) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
// See "Matching Paths" below to learn more
export const config = {
matcher: '/middleware',
}
```
## Implement Incremental Static Regeneration
1. 在 `app router` 中可以採用底下的方式去 `static render ` 概念是我可以先 `pre-build` `10` 頁的 `blog page` 減少 `build time` 時間
2. 透過 `export const dynamic = 'force-static'` 告 `next` 如果 `user` 訪問第 `11` 頁的時候再產出第 `11` 頁的內容。
```typescript
// app/blog/[id]/page.tsx
export const revalidate = 60
export const dynamicParams = true
export const dynamic = 'force-static'
export async function generateStaticParams() {
const posts = await fetch('https://api.vercel.app/blog').then((res) =>
res.json()
) as Post[]
return posts.slice(0, 10).map((post) => ({
id: String(post.id),
}))
}
```
所以當你 `next build` 好後會在 `.next` 中預先有前 `10` 頁的內容

等到在 `request time` 需要第 `11` 頁的東西後 `next` 才會產出對應的 `html`

## Partial Prerendering
在這邊 `Toaster` 使用 `Suspense` 原因是 `async` 的 `component` ,另一個原因是在 `PPR` 中 `component` 使用 `dynamic` 的 `API` 會強制讓頁面變成`dynamic` ,`Suspense` 可以讓頁面保留靜態的頁面。
```typescript
import { cookies } from "next/headers";
export async function Toaster() {
const cookieStore = await cookies();
const toasts = cookieStore
.getAll()
.filter((cookie) => cookie.name.startsWith("toast-") && cookie.value)
.map((cookie) => ({
id: cookie.name,
message: cookie.value,
}));
return (
<div>
{toasts.map((toast) => (
<div key={toast.id} className="mt-4">
<p>{toast.message}</p>
</div>
))}
</div>
);
}
```
```typescript
import { Toaster } from "./toaster";
export default function RootLayout({
children,
}: Readonly<{
children: ReactNode;
}>) {
return (
<html lang="en">
<body className="mx-auto max-w-2xl bg-slate-100 antialiased">
{children}
<Suspense>
<Toaster />
</Suspense>
</body>
</html>
);
}
```
## Server Type Component
https://conform.guide/