server component / client component
在 Rendering 的策略上,Next.js 13 提供了一個新的概念,server component 和 client component 這兩個可以讓我們決定哪些 component 是要在 server 上 render 還是在 client。
實際做法是在檔案的最上方新增 "use client"
或 "use server"
directives 來做區分。
Next.js 預設情況下都是 server component。
'use client'; // directives
import { useState } from 'react';
export default function LikeButton() {
const [likes, setLikes] = useState(0);
function handleClick() {
setLikes(likes + 1);
}
return <button onClick={handleClick}>Like ({likes})</button>;
}
<Link> component
<Link>
是 Next.js 對原生 <a>
tag 的功能擴充,額外提供 prefetching 及 client-side navigation 的功能。
prefetch
- 預設是 true,當
<Link>
在使用者的 viewport 中被看見都會先 prefetch,意思就是頁面會在 background 先被載入 。
Routes
Next.js 採用的是 file-system based router,也就是 folder structure 最終會對應到 URL Path。
圖片來源:https://nextjs.org/docs/app/building-your-application/routing/defining-routes#creating-routes
而在資料夾底下一定要包含一支名為
page.js
的檔案才能讓該 URL Path 被公開看見。圖片來源:https://nextjs.org/docs/app/building-your-application/routing/defining-routes#creating-routes
Dynamic Routes
建立動態路由的寫法就是在 segment 外面加上 [ ]
,例如:[id]
或 [slug]
。
而之後r會傳入 layout
或 page
的 params
裡面。
export default function Page({ params }) {
return <div>My Post: {params.slug}</div>
}
Route | Example URL | params |
app/blog/[slug]/page.js | /blog/a | { slug: 'a' } |
app/blog/[slug]/page.js | /blog/b | { slug: 'b' } |
app/blog/[slug]/page.js | /blog/c | { slug: 'c' } |
Catch-all Segments
可以捕捉到指定路由之下的所有動態路由 segment,寫法是 [...folderName]
。
Route | Example URL | params |
app/shop/[...slug]/page.js | /shop/a | { slug: ['a'] } |
app/shop/[...slug]/page.js | /shop/a/b | { slug: ['a', 'b'] } |
app/shop/[...slug]/page.js | /shop/a/b/c | { slug: ['a', 'b', 'c'] } |
Route Groups
通常 folder structure 會對應到 URL Path,但如果你想要將 router 進行分類而又不影響 URL Path 的話,可以在 folder 外面加上 ()
,例如 (folderName)
。
此外,在 router group 底下建立 layout.js
還可以達到 different nested layout 的需求。
可以看到 /account
和 /cart
會共享 (shop)
layout 與外部的 app
layout。而 /checkout
只會有 app
layout。
Fetching Data on the Server with fetch
Next.js 13 將原生
fetch API
做功能上的擴充,例如 Caching Data(資料不用每次都去請求一次)、Revalidating Data(設定何時重新抓取資料,以確保資料的最新狀態)可以在 server component 使用
fetch API
搭配async / await
。Request Memoization:React 會在 component tree 之間,記憶相同的 fetch request 。
圖片來源:https://nextjs.org/docs/app/building-your-application/caching#request-memoization
async function getData() {
const res = await fetch('https://api.example.com/...')
if (!res.ok) {
throw new Error('Failed to fetch data')
}
return res.json()
}
export default async function Page() {
const data = await getData()
return <main></main>
}
generateStaticParams
此方法可以讓 dynamic routes 結合 static rendering,也就是讓這些 dynamic routes 是在 build time 就被建立,而不是 request time。
dynamicParams
此 Route Segment Config 可以設定 dynamic segment 沒有出現在 generateStaticParams
的時候,在請求階段自動產生頁面,預設是 true
,如果是 false
就會回傳 404。
export const dynamicParams = true // true | false
notFound
- 呼叫
notFound
function 會去該 segment 內 rendernot-found.js
。
Loading UI & Streaming
在同一個資料夾內建立一個
loading.js
,Next.js 會自動將page.js
包在<Suspense>
boundary。畫面一開始載入會先顯示
loading.js
,等 rendering 完畢之後再顯示page.js
。
export default function Loading() {
// You can add any UI inside Loading, including a Skeleton.
return <LoadingSkeleton />
}
streaming:Loading UI 不只可以用在整個 page.js
,還可以套用在 child component。
import { Suspense } from 'react'
import { PostFeed, Weather } from './Components'
export default function Posts() {
return (
<section>
<Suspense fallback={<p>Loading feed...</p>}>
<PostFeed />
</Suspense>
<Suspense fallback={<p>Loading weather...</p>}>
<Weather />
</Suspense>
</section>
)
}