基于 Next.js 的 SSR/SSG 方案
最近在探索學(xué)習(xí)前端工程化相關(guān)內(nèi)容,在如今前后端分離的架構(gòu)下,為了提升首屏渲染速度和 SEO 效果,兜兜轉(zhuǎn)轉(zhuǎn),又回到了服務(wù)端渲染。

本文主要是講講如何使用 Next.js 框架實(shí)現(xiàn)服務(wù)端渲染,將有效提升網(wǎng)頁(yè)的 SEO 和首屏渲染速度,說(shuō)不定哪天就用上了,是吧!

一、服務(wù)端渲染(SSR)
服務(wù)端渲染(SSR,Server Side Render)與客戶(hù)端渲染(CSR,Client Side Render)的核心區(qū)分點(diǎn)簡(jiǎn)單來(lái)說(shuō)就是完整的 HTML 文檔在服務(wù)端還是瀏覽器里組裝完成。

SSR 的另一概念是同構(gòu)渲染,可以看看知乎中的討論:什么是前端的同構(gòu)渲染?[1]
同構(gòu)渲染簡(jiǎn)單來(lái)說(shuō)就是一份代碼,服務(wù)端先通過(guò)服務(wù)端渲染(
SSR),生成 HTML 以及初始化數(shù)據(jù),客戶(hù)端拿到代碼和初始化數(shù)據(jù)后,通過(guò)對(duì) HTML 的 DOM 進(jìn)行 patch 和事件綁定對(duì) DOM 進(jìn)行客戶(hù)端激活(client-side hydration),該整體過(guò)程叫同構(gòu)渲染。
SSR 的原理,本文就不再贅述了,感興趣的朋友推薦閱讀這篇文章:《徹底理解服務(wù)端渲染 - SSR原理》
二、Next.js
Next.js[2] 是一款用于生產(chǎn)環(huán)境的 React 框架,無(wú)需配置,默認(rèn)提供了生產(chǎn)環(huán)境所需所有功能的最佳開(kāi)發(fā)實(shí)踐:支持靜態(tài)渲染和服務(wù)端渲染、支持 TypeScript、智能打包、路由預(yù)加載等功能。
與此同時(shí),Next.js 還提供了如下開(kāi)箱即用的 SDK 輔助開(kāi)發(fā) Web 應(yīng)用:

閱讀過(guò) SSR 原理一文可看到配置支持服務(wù)端渲染還是挺麻煩的,但借助 Next.js,可以很輕松的上手改造支持現(xiàn)有 Web 應(yīng)用服務(wù)端渲染。
是否采用服務(wù)端渲染還得綜合考慮收益,服務(wù)端渲染畢竟會(huì)增加服務(wù)器的計(jì)算開(kāi)銷(xiāo),穩(wěn)定性相較于 CSR 差一些。
三、創(chuàng)建 Next.js 應(yīng)用
初始化一個(gè) Next.js 應(yīng)用可以直接通過(guò)腳手架快速完成:
npx?create-next-app@latest?--ts
#?or
yarn?create?next-app?--typescript
中途會(huì)要求輸入項(xiàng)目名,并自動(dòng)安裝所需的模塊

執(zhí)行 yarn dev 后需要手動(dòng)再瀏覽器打開(kāi)網(wǎng)址:http://localhost:3000 ,即可看到如下頁(yè)面:

首頁(yè)的內(nèi)容對(duì)應(yīng) ./pages/index.tsx 文件
初始的目錄結(jié)構(gòu)如下:
.
├──?pages?//?采用約定式路由(文件系統(tǒng)路由)
│???├──?_app.tsx
│???├──?api?//?API?目錄
|??????├──?hello.ts
│???└──?index.tsx?//?首頁(yè)
├──?public?//?公共資源
│???├──?favicon.ico
│???└──?vercel.svg
├──?styles?//?樣式
│???├──?Home.module.css
│???└──?globals.css
├──?next-env.d.ts?//?Next?相關(guān)的?TS?定義
├──?next.config.js?//?Next.js?自定義配置
├──?node_modules
├──?package.json
├──?tsconfig.json
├──?README.md
└──?yarn.lock
四、頁(yè)面路由
通常我們的 Web 應(yīng)用是多頁(yè)面、多路由的,因此會(huì)涉及到在各個(gè)頁(yè)面之間跳轉(zhuǎn),因此有必要熟悉 Next.js 的路由使用方式。
上述講到了 Next.js 是約定式路由,基于文件系統(tǒng),對(duì)應(yīng)到 ./pages 目錄下,當(dāng)添加頁(yè)面文件到 ./pages 目錄,Next.js 會(huì)自動(dòng)識(shí)別并將對(duì)應(yīng)文件注冊(cè)的路由上
4.1 索引路由
Next.js 會(huì)自動(dòng)將文件夾內(nèi)的 “index” 文件注冊(cè)為文件夾的主頁(yè)
| 文件路徑 | 對(duì)應(yīng)路由 |
|---|---|
pages/index.tsx | / |
pages/blog/index.tsx | /blog |
4.2 嵌套路由
Next.js 支持嵌套文件的路由,如果您創(chuàng)建嵌套文件夾結(jié)構(gòu),文件仍將自動(dòng)以相同方式路由解析。
| 文件路徑 | 對(duì)應(yīng)路由 |
|---|---|
pages/blog/first-post.tsx | /blog/first-post |
pages/dashboard/settings/username.tsx | /dashboard/settings/username |
4.3 動(dòng)態(tài)參數(shù)路由
常見(jiàn)于比如博客的文章詳情頁(yè)面,文章的 id 是動(dòng)態(tài)變化的,Next.js 中可以使用中括號(hào)解析到對(duì)應(yīng)的命名參數(shù)
| 文件路徑 | 對(duì)應(yīng)路由 | ?? |
|---|---|---|
pages/blog/[slug].js | /blog/:slug | /blog/hello-world |
pages/[username]/settings.js | /:username/settings | /foo/settings |
pages/post/[...all].js | /post/* | /post/2021/id/title |
更多關(guān)于動(dòng)態(tài)路由的解析可參閱:https://nextjs.org/docs/routing/dynamic-routes
4.4 路由跳轉(zhuǎn)
之前有提到 Next.js 中的路由預(yù)加載功能,需借助 Next.js 提供的 next/link,寫(xiě)法如下:
<Link?href="/blog/hello-world">
??<a>第一篇文章a>
Link>
應(yīng)用頁(yè)面之間的跳轉(zhuǎn),可以用 標(biāo)簽包裹。
屬性 href 的值是跳轉(zhuǎn)頁(yè)面的路徑字符串或 URL 對(duì)象:
import?Link?from?'next/link'
function?Articles({?articles?})?{
??return?(
????<ul>
??????{articles.map((article)?=>?(
????????<li?key={article.id}>
??????????<Link
????????????href={{
??????????????pathname:?'/article/[slug]',
??????????????query:?{?slug:?article.slug?},
????????????}}
??????????>
????????????<a>{article.title}a>
??????????Link>
????????li>
??????))}
????ul>
??)
}
export?default?Articles
如有需要對(duì)路由通過(guò) js 跳轉(zhuǎn),則可以通過(guò) Next.js 提供的 next/router[3] 中的 useRouter[4] Hook。
4.5 代碼拆分和預(yù)加載
通過(guò) Next.js 的路由功能,可以自動(dòng)完成頁(yè)面按需加載當(dāng)前頁(yè)面所需的代碼,同時(shí)會(huì)自動(dòng)預(yù)加載頁(yè)面中屬于自身應(yīng)用的鏈接。
這意味著在呈現(xiàn)主頁(yè)時(shí),最初不會(huì)提供其他頁(yè)面的代碼,同時(shí)可確保即使您有數(shù)百個(gè)頁(yè)面,主頁(yè)也能按需快速加載。
僅加載您請(qǐng)求的頁(yè)面的代碼也意味著頁(yè)面變得獨(dú)立,如果某個(gè)頁(yè)面拋出錯(cuò)誤,應(yīng)用程序的其余部分仍然可以工作。
在 Next.js 的生產(chǎn)版本中,每當(dāng) Link 組件出現(xiàn)在瀏覽器的視口中時(shí),Next.js 都會(huì)在后臺(tái)自動(dòng)預(yù)取鏈接頁(yè)面的代碼。當(dāng)您單擊鏈接時(shí),目標(biāo)頁(yè)面的代碼已在后臺(tái)加載,頁(yè)面轉(zhuǎn)換將近乎即時(shí)。
五、靜態(tài)資源
所有靜態(tài)資源都可以放到 ./public 目錄下,Next.js 會(huì)自動(dòng)為其中的文件注冊(cè)路由,按照文件系統(tǒng)的方式,與 Page 的路由類(lèi)似。
5.1 圖片元素
一般網(wǎng)頁(yè)中的圖片寫(xiě)法如下:
<img?src="/images/logo.png"?alt="logo"?/>
但這種寫(xiě)法會(huì)需要開(kāi)發(fā)者手動(dòng)去優(yōu)化,比如按需加載、錯(cuò)誤處理等。
Next.js 考慮到這點(diǎn),為了減輕開(kāi)發(fā)者負(fù)擔(dān),于是提供了 next/image[5],開(kāi)箱即用。
這里其實(shí)可以借鑒一下,別的項(xiàng)目中為了業(yè)務(wù)統(tǒng)一處理圖片,可以封裝一個(gè) Image 組件,提升研發(fā)效率。
import?Image?from?'next/image'
const?YourComponent?=?()?=>?(
??<Image
????src="/images/profile.jpg"?//?圖片文件路徑
????height={144}?//?具有正確縱橫比的所需尺寸
????width={144}
????alt="Image?Alt"
??/>
)
export?default?YourComponent;
5.2 Meta 數(shù)據(jù)
網(wǎng)頁(yè)的 Meta 數(shù)據(jù),也就是在 html->head 標(biāo)簽中的內(nèi)容
Next.js 提供了 next/head[6] 用于聲明式編寫(xiě)網(wǎng)頁(yè)的 head 內(nèi)容。
import?Link?from?'next/link'
import?Head?from?'next/head'
export?default?function?FirstPost()?{
??return?(
????<>
??????<Head>
????????<meta?charset="UTF-8"?/>
????????<title>First?Posttitle>
????????<link?rel="shortcut?icon"?href="/favicon.ico"?/>
????????<meta?name="keywords"?content="網(wǎng)頁(yè)關(guān)鍵詞"?/>
????????<meta?name="description"?content="網(wǎng)頁(yè)描述"?/>
????????<meta?name="author"?content="DYBOY,[email protected]"?/>
????????<meta?name="version"?content="1.0"?/>
????????<link?rel="stylesheet"?href="http://at.alicdn.com/t/font_2319527_hng3o947ocv.css"?/>
????????<link?rel="stylesheet"?href="/style/fancybox.css"?/>
????????<link?rel="stylesheet"?href="/style/app.css"?/>
????????<script?src="/scripts/jquery.js">script>
??????Head>
??????<h1>First?Posth1>
??????<h2>
????????<Link?href="/">
??????????<a>Back?to?homea>
????????Link>
??????h2>
????>
??)
}
此外,若我們有需要修改 的訴求時(shí),可創(chuàng)建pages/_document.js 文件,并通過(guò)“自定義文檔[7]”的方式繼承并統(tǒng)一改造所有網(wǎng)頁(yè)輸出的公共內(nèi)容。
5.3 JS 腳本文件
例如我們使用了三方庫(kù) Jquery,雖然可以直接在 組件中直接寫(xiě):
<script?src="/scripts/jquery.js">script>
但是,這種方式包含腳本并不能明確說(shuō)明何時(shí)加載同一頁(yè)面上獲取的其他 JavaScript 代碼。如果某個(gè)特定腳本會(huì)阻塞渲染并且會(huì)延遲頁(yè)面內(nèi)容的加載,則會(huì)顯著影響性能。
因此,可以通過(guò) next/script[8] 來(lái)優(yōu)化
import?Link?from?'next/link'
import?Head?from?'next/head'
import?Script?from?'next/script'
export?default?function?FirstPost()?{
??return?(
????<>
??????<Head>
????????<title>First?Posttitle>
??????Head>
??????<Script
????????src="/scripts/jquery.js"
????????strategy="lazyOnload"?//?設(shè)置?js?加載的方式
????????onLoad={()?=>
??????????//?js?腳本文件加載完成后的回調(diào)函數(shù)
??????????console.log(`script?loaded?correctly`)
????????}
??????/>
??????<h1>First?Posth1>
??????<h2>
????????<Link?href="/">
??????????<a>返回首頁(yè)a>
????????Link>
??????h2>
????</>
??)
}
5.4 CSS 文件
Next.js 已經(jīng)內(nèi)置支持了 CSS 和 SASS,允許開(kāi)發(fā)者引入 .css 和 .sass 文件方式引入樣式文件,同時(shí)還支持 Tailwind CSS。
需要手動(dòng)安裝 SASS 模塊
yarn?add?sass
默認(rèn)還支持 CSS-in-JS,借助 styled-jsx[9] 這個(gè)模塊,可以直接在 React 組件中直接寫(xiě) CSS,同時(shí)限制作用域,不會(huì)影響其他組件。
如果需要 CSS 模塊化[10],那么 CSS 文件命名應(yīng)當(dāng)為 *.module.css 格式。
import?styles?from?'./layout.module.css'
export?default?function?Layout({?children?})?{
??return?<div?className={styles.container}>{children}div>
}
全局 CSS 注入,則在根目錄的 ./styles 目錄編寫(xiě),同時(shí)也僅在 ./pages/_app.tsx 文件中引入全局樣式文件
import?'../styles/globals.css'?//?引入全局樣式
import?type?{?AppProps?}?from?'next/app'
function?MyApp({?Component,?pageProps?}:?AppProps)?{
??return?<Component?{...pageProps}?/>
}
export?default?MyApp
Next.js 使用 PostCSS[11] 編譯 CSS,自定義配置 PostCSS 的方式可參考:【自定義 PostCSS 配置[12]】
六、預(yù)渲染和數(shù)據(jù)獲取
Next.js 支持:
在服務(wù)端預(yù)渲染 靜態(tài)頁(yè)面生成和服務(wù)端渲染 有數(shù)據(jù)和無(wú)數(shù)據(jù)的靜態(tài)生成 一些預(yù)定義的方法(生命周期函數(shù))注入數(shù)據(jù)
6.1 預(yù)渲染
默認(rèn)情況下,Next.js 預(yù)渲染每個(gè)頁(yè)面。這意味著 Next.js 會(huì)提前為每個(gè)頁(yè)面生成 HTML,預(yù)渲染可以帶來(lái)更好的性能和SEO。
每個(gè)生成的 HTML 都與該頁(yè)面所需的最少 JavaScript 代碼相關(guān)聯(lián)。當(dāng)瀏覽器加載頁(yè)面時(shí),其 JavaScript 代碼會(huì)運(yùn)行并使頁(yè)面完全交互。
預(yù)渲染和無(wú)預(yù)渲染的對(duì)比如下:


6.2 靜態(tài)生成和服務(wù)端渲染
Next.js 支持兩種形式的預(yù)渲染方式:靜態(tài)生成和服務(wù)端渲染
靜態(tài)生成: 在構(gòu)建時(shí)生成 HTML 的預(yù)渲染方法。然后在每個(gè)請(qǐng)求上重用預(yù)渲染的 HTML。 服務(wù)器端渲染: 在每個(gè)請(qǐng)求上生成 HTML 的預(yù)渲染方法。


6.3 獲取數(shù)據(jù)
(1)靜態(tài)生成時(shí)獲取數(shù)據(jù)
在服務(wù)端構(gòu)建生成靜態(tài)頁(yè)面之前,有時(shí)候需要獲取一些數(shù)據(jù),可以借助 getStaticProps 方法。
在頁(yè)面組件內(nèi),同時(shí)導(dǎo)出一個(gè) getStaticProps 方法:
export?default?function?HomePage(props)?{?...?}
//?導(dǎo)出異步獲取數(shù)據(jù)方法
export?async?function?getStaticProps()?{
??//?獲取數(shù)據(jù),例如從數(shù)據(jù)庫(kù)、API、文件等
??const?data?=?...
??//?返回的參數(shù)將會(huì)按照?key?值賦值到?HomePage?組件的同名入?yún)⒅?/span>
??return?{
????props:?...
??}
}
注意,僅在頁(yè)面組件內(nèi)導(dǎo)出該方法
(2)服務(wù)端渲染時(shí)獲取數(shù)據(jù)
比如用戶(hù)的個(gè)人中心頁(yè)面,該頁(yè)面時(shí)不需要 SEO 優(yōu)化的,其數(shù)據(jù)通常需要實(shí)時(shí)更新獲取,因此采用 SSR 的方式,而 SSR 在服務(wù)端獲取數(shù)據(jù)可以借助 getServerSideProps 方法
和構(gòu)建時(shí)獲取數(shù)據(jù)方法類(lèi)似:
export?default?function?HomePage(props)?{?...?}
//?導(dǎo)出異步獲取數(shù)據(jù)方法
export?async?function?getServerSideProps()?{
??//?獲取數(shù)據(jù),例如從數(shù)據(jù)庫(kù)、API、文件等
??const?data?=?...
??//?返回的參數(shù)將會(huì)按照?key?值賦值到?HomePage?組件的同名入?yún)⒅?/span>
??return?{
????props:?...
??}
}
(3)客戶(hù)端渲染時(shí)獲取數(shù)據(jù)
如果不需要“預(yù)渲染”時(shí)候獲取數(shù)據(jù),即不需要“靜態(tài)生成”和“服務(wù)端渲染”的時(shí)候獲取數(shù)據(jù),則可以在對(duì)應(yīng)頁(yè)面組件代內(nèi),編寫(xiě)網(wǎng)絡(luò)請(qǐng)求相關(guān)代碼。
Next.js 團(tuán)隊(duì)提供了一個(gè)基于 React Hooks 的 useSWR 鉤子,推薦使用,該鉤子會(huì)處理緩存、重新驗(yàn)證、焦點(diǎn)跟蹤、間隔重新獲取等。
一個(gè)簡(jiǎn)單的示例如下:
import?useSWR?from?'swr'
function?Profile()?{
??const?{?data,?error?}?=?useSWR('/api/user',?fetch)
??if?(error)?return?failed?to?load</div>
??if?(!data)?return?loading...div>
??return?hello?{data.name}!</div>
}
和一些封裝的請(qǐng)求 Hooks 類(lèi)似,useSWR 還支持自定義請(qǐng)求庫(kù),默認(rèn)使用的是 fetch 的 pollyfill 模塊(unfetch[13]),提供的中文官方的文檔也非常清晰,地址:https://swr.vercel.app/zh-CN/docs/getting-started
七、動(dòng)態(tài)路由
上面講到了預(yù)渲染,如果是動(dòng)態(tài)路由的預(yù)渲染該如何處理?這里需要依賴(lài)方法 getStaticPaths 獲得動(dòng)態(tài)路由需要生成頁(yè)面列表。
//?./pages/post/[id].tsx
import?Layout?from?'../../components/layout'
export?default?function?Post({id,?article})?{
??return?(
??????<Layout>
??????????<Head>
??????????????<title>{article.title}title>
??????????Head>
??????????{article.title}
??????????<br?/>
??????????{id}
??????????<br?/>
??????????{article.date}
??????Layout>
???)
}
export?async?function?getStaticPaths()?{
??//?返回所有可能的文章?id?所對(duì)應(yīng)的列表
????const?paths?=?[
??????{
????????params:?{
??????????id:?'ssg-ssr'
????????}
??????},
??????{
????????params:?{
??????????id:?'pre-rendering'
????????}
??????}
????]
????
????return?{
????????paths,
????????fallback:?false,?//?如果在?paths?中?id?找不到對(duì)應(yīng)值,則指向?404?頁(yè)面
????}
}
export?async?function?getStaticProps({?params?})?{
??//?通過(guò)?params.id?獲取必要的文章數(shù)據(jù)?
??//?parmas?即路由中的參數(shù)對(duì)象
??const?article?=?getContentById(parmas.id);
??return?{
??????props:?{
??????????id,
??????????artcile,
??????}
??}
}
關(guān)于 fallback 可以參閱:fallback props[14]
自建一個(gè) 404 頁(yè)面,文件路徑為:./pages/404.tsx
export?default?function?Custom404()?{
??return?<h1>404?-?Page?Not?Foundh1>
}
八、BFF API
在初始化的目錄結(jié)構(gòu)中的 ./pages/api/hello.ts 文件,就是一個(gè) API 頁(yè)面,他的路由和頁(yè)面路由相同
import?type?{?NextApiRequest,?NextApiResponse?}?from?'next'
type?Data?=?{
??name:?string
}
export?default?function?handler(
??req:?NextApiRequest,
??res:?NextApiResponse
)?{
??res.status(200).json({?name:?'John?Doe'?})
}
在 ./pages/api/ 目錄下,前端開(kāi)發(fā)者編寫(xiě)人意的 API 應(yīng)用,也就是被稱(chēng)為 Serverless Functions,類(lèi)似于字節(jié)的“輕服務(wù)[15]”
九、部署
官方推薦使用 Vercel[16] 來(lái)完成一鍵自動(dòng)化構(gòu)建部署
首先執(zhí)行構(gòu)建,構(gòu)建時(shí)候會(huì)自動(dòng)做相關(guān)優(yōu)化
yarn?build
//?實(shí)際執(zhí)行
next?build
然后是啟動(dòng)服務(wù):
yarn?start
//?實(shí)際執(zhí)行
next?start?-p?8080

在生產(chǎn)環(huán)境,再用 PM2[17] 管理守護(hù)進(jìn)程
然后使用 Nginx 作為網(wǎng)關(guān),配置域名,SSL,映射到本地 8080 端口即可。
拓展更多
Next.js 還有更多細(xì)節(jié)和 API,需要深入了解的小伙伴可以參閱:Next.js API文檔[18]
除了 Next.js,還有 Razzle.js[19] 也可以學(xué)習(xí)下。
推薦閱讀:
《徹底理解服務(wù)端渲染 - SSR原理》 《魅族官網(wǎng)基于 next.js 重構(gòu)實(shí)踐總結(jié)與分享[20]》 《SWR - 用于數(shù)據(jù)請(qǐng)求的 React Hooks 庫(kù)[21]》 《react 服務(wù)端(ssr) 框架next.js開(kāi)發(fā)個(gè)人網(wǎng)站分享[22]》 《Next.js 應(yīng)用開(kāi)發(fā)實(shí)踐[23]》
總結(jié)
通過(guò)對(duì) Next.js 的初步上手使用,SSR 確實(shí)有助于提升用戶(hù)的體驗(yàn),比如一些文檔網(wǎng)站、官網(wǎng)、營(yíng)銷(xiāo)網(wǎng)頁(yè),個(gè)人非常推薦這種方式,但其缺點(diǎn)也很明顯,服務(wù)端的穩(wěn)定性會(huì)有所降低,穩(wěn)定性可以通過(guò)增加成本提高,相較于其優(yōu)點(diǎn),還是值得投入的!
Next.js 把一些生產(chǎn)配置初始化就構(gòu)建完成,對(duì)于開(kāi)發(fā)者來(lái)說(shuō),開(kāi)箱即用的感覺(jué)真的太棒了!
參考資料
[1]什么是前端的同構(gòu)渲染?: https://www.zhihu.com/question/325952676
[2]Next.js: https://nextjs.org/
[3]next/router: https://nextjs.org/docs/api-reference/next/link
[4]useRouter: https://nextjs.org/docs/api-reference/next/router#userouter
[5]next/image: https://nextjs.org/docs/api-reference/next/image
[6]next/head: https://nextjs.org/docs/api-reference/next/head
[7]自定義文檔: https://nextjs.org/docs/advanced-features/custom-document
[8]next/script: https://nextjs.org/docs/api-reference/next/script
[9]styled-jsx: https://github.com/vercel/styled-jsx
[10]CSS 模塊化: https://nextjs.org/docs/basic-features/built-in-css-support#adding-component-level-css
[11]PostCSS: https://postcss.org/
[12]自定義配置 PostCSS: https://nextjs.org/docs/advanced-features/customizing-postcss-config
[13]unfetch: https://www.npmjs.com/package/unfetch
[14]fallback props: https://nextjs.org/docs/basic-features/data-fetching#the-fallback-key-required
[15]輕服務(wù): https://qingfuwu.cn/
[16]Vercel: https://vercel.com/
[17]PM2: https://pm2.keymetrics.io/
[18]Next.js API文檔: https://nextjs.org/docs/api-reference/cli
[19]Razzle.js: https://razzlejs.org/
[20]魅族官網(wǎng)基于 next.js 重構(gòu)實(shí)踐總結(jié)與分享: https://zhuanlan.zhihu.com/p/113853235
[21]SWR - 用于數(shù)據(jù)請(qǐng)求的 React Hooks 庫(kù): https://swr.vercel.app/zh-CN/docs/getting-started
[22]react 服務(wù)端(ssr) 框架next.js開(kāi)發(fā)個(gè)人網(wǎng)站分享: http://www.liuweibo.cn/p/206
[23]Next.js 應(yīng)用開(kāi)發(fā)實(shí)踐: https://nextjs-in-action-cn.taonan.lu/
瀏覽
74
