點關注不迷路雏蛮,建議收藏慢慢讀……
在開始之前我們需要先來搞清楚一個問題:什么是服務端渲染 痛黎?
在以往的概念里,渲染的工作更多的是放在客戶端進行的薛匪,那么為什么現(xiàn)在我們要讓服務端來做這個工作月匣?
服務端渲染和客戶端渲染有什么不同之處嗎钻洒?
其實服務端渲染的工具有很多,看著手冊很快就能上手锄开,并沒有什么難度航唆,關鍵在于,我們什么場景下需要使用服務端渲染院刁,什么樣的渲染方案更適合我們的項目糯钙;知其然,知其所以然退腥,我們需要先搞清楚服務端渲染的基本概念和原理任岸,服務端渲染為什么會出現(xiàn),到底解決了我們的什么問題狡刘,掌握整體的渲染邏輯和思路享潜,我們才能在學習工具使用時,輕松自在嗅蔬,而即便以后工具有了變化和更新剑按,我們也能得心應手,不會再說 “學不動” 了澜术;
這個邏輯就是所謂的道艺蝴、法、術鸟废、器的概念猜敢;不要僅僅停留在工具的使用和一些工具的奇技淫巧中,更多的要向法、道的層面成長缩擂;
什么是 SSR 鼠冕?
現(xiàn)代化的前端項目,大部分都是單頁應用程序胯盯,也就是我們說的 SPA 懈费,整個應用只有一個頁面,通過組件的方式博脑,展示不同的頁面內(nèi)容憎乙,所有的數(shù)據(jù)通過請求服務器獲取后,在進行客戶端的拼裝和展示趋厉;這就是目前前端框架的默認渲染邏輯,我們稱為:客戶端渲染方案( Client Side Render 簡稱: CSR )胶坠;
加載渲染過程如下: HTML/CSS 代碼 --> 加載 JavaScript 代碼 --> 執(zhí)行 JavaScript 代碼 --> 渲染頁面數(shù)據(jù)
SPA 應用的客戶端渲染方式君账,最大的問題有兩個方面:
1:白屏時間過長,用戶體驗不好沈善;
2:HTML 中無內(nèi)容乡数,SEO 不友好;
這個問題的原因在于闻牡,首次加載時净赴,需要先下載整個 SPA 腳本程序,瀏覽器執(zhí)行代碼邏輯后罩润,才能去獲取頁面真正要展示的數(shù)據(jù)玖翅,而 SPA 腳本的下載需要較長的等待和執(zhí)行時間,同時割以,下載到瀏覽器的 SPA 腳本是沒有頁面數(shù)據(jù)的金度, 瀏覽器實際并沒有太多的渲染工作,因此用戶看到的是沒有任何內(nèi)容的頁面严沥,不僅如此猜极,因為頁面中沒有內(nèi)容,搜索引擎的爬蟲爬到的也是空白的內(nèi)容消玄,也就不利于 SEO 關鍵字的獲雀;
相較于傳統(tǒng)的站點翩瓜,瀏覽器獲取到的頁面都是經(jīng)過服務器處理的有內(nèi)容的靜態(tài)頁面受扳,有過后端編程經(jīng)驗的可能會比較熟悉一些,頁面結構和內(nèi)容兔跌,都是通過服務器處理后辞色,返回給客戶端;
全宇宙首發(fā)動圖,全流程展現(xiàn)
兩相比較我們會發(fā)現(xiàn)相满,傳統(tǒng)站點的頁面數(shù)據(jù)合成在后臺服務器层亿,而 SPA 應用的頁面數(shù)據(jù)合成在瀏覽器,但是無論那種立美,最終的渲染展示匿又,還是交給瀏覽器完成的,所以建蹄,不要誤會碌更,我們這里所說的 服務端渲染 和 客戶端渲染,指的是頁面結構和數(shù)據(jù)合成的工作洞慎,不是瀏覽器展示的工作痛单;
那么能不能借助傳統(tǒng)網(wǎng)站的思路來解決 SPA 的問題又能夠保留SPA的優(yōu)勢呢?不管是白屏時間長還是 SEO 不友好劲腿,實際都是首屏的頁面結構先回到瀏覽器旭绒,然后再獲取數(shù)據(jù)后合成導致的問題,那么焦人,首屏的頁面結構和數(shù)據(jù)挥吵,只要像傳統(tǒng)站點一樣,先在服務端合成后再返回花椭,同時將 SPA 腳本的加載依然放到首屏中忽匈,此時返回的頁面就是結構和數(shù)據(jù)都有的完整內(nèi)容了,這樣瀏覽器在展示首頁數(shù)據(jù)的同時也能加載 SPA 腳本矿辽,搜索引擎的爬蟲同樣也能獲取到對應的數(shù)據(jù)丹允,解決 SEO 的問題;為了更好的理解這個邏輯袋倔,我畫了一個流程圖:
沒錯嫌松,這就是我們所說的 服務端渲染的基本邏輯,服務端渲染也就是 SSR (Server Side Rendering) 奕污;
白屏時間過長的問題得以解決萎羔,因為首次加載時,服務器會先將渲染好的靜態(tài)頁面返回碳默,在靜態(tài)頁面中再次加載請求 SPA 腳本贾陷;
基本原理:首頁內(nèi)容及數(shù)據(jù),在用戶請求之前生成為靜態(tài)頁面嘱根,同時加入 SPA 的腳本代碼引入髓废,在瀏覽器渲染完成靜態(tài)頁面后,請求 SPA 腳本應用该抒,之后的頁面交互依然是客戶端渲染慌洪;
明白了其中的原理,也就是到了道、法的境界冈爹,接下來涌攻,讓我們下凡進入術、器的應用層面感受一下频伤;
其中 Vue 框架和 React 框架都有對應的比較成熟的 SSR 解決方案恳谎,React對應的是 Next.js 框架,Vue 對應的就是 Nuxt.js憋肖,當然因痛,如果你對這些都不感興趣,也可以自己實現(xiàn)一個 SSR 的服務端應用岸更,我自己之前也寫過一個鸵膏,如果你感興趣,想看看我實現(xiàn)的代碼怎炊,可以留言給我谭企,回頭做成教程發(fā)出來;
我們以 React 對應的 Next.js 為例结胀,來具體感受服務端渲染赞咙;
Next.js 框架
中文官網(wǎng)首頁责循,已經(jīng)介紹的非常清楚了:https://www.nextjs.cn/
基本應用
安裝部署
項目安裝命令: npx create-next-app
或 npm init next-app my-next-project
運行:npm run dev
命令糟港,啟動項目
同時,也可以查看 ./package.json
文件中的腳本配置
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
這些腳本涉及開發(fā)應用程序的不同階段:
dev
- 運行next dev
院仿,以開發(fā)模式啟動 Next.jsbuild
- 運行next build
秸抚,以構建用于生產(chǎn)環(huán)境的應用程序start
- 運行next start
,將啟動 Next.js 生產(chǎn)環(huán)境服務器
訪問 http://localhost:3000
即可查看我們的應用程序了歹垫。
頁面路由
在 Next.js 中剥汤,頁面是被放置在 pages 文件夾中的 Reac 組件,這是框架定義好的排惨;
組件需要被默認導出吭敢;組件文件中不需要引入 React;頁面地址與文件地址是對應的關系暮芭;
頁面(page) 根據(jù)其文件名與路由關聯(lián)鹿驼。例如,pages/about.js
被映射到 /about
辕宏。
在你的項目中的 pages
目錄創(chuàng)建一個 about.js
畜晰。
為 ./pages/about.js
文件填充如下內(nèi)容:
// 類組件需要引入 react 繼承
import React from 'react'
class AboutPage extends React.Component {
render(){
return <h3>class Component -- About Page</h3>
}
}
export default AboutPage
// 函數(shù)組件不需要引入 React
function AboutPage(){
return <h3>Function Component -- About Page</h3>
}
export default AboutPage
頁面跳轉
Link 組件默認使用 Javascript 進行頁面跳轉,即SPA形式的跳轉 如果瀏覽器中 Javascript 被禁用瑞筐,則使用鏈接跳轉 Link組件中不應添加除 href 屬性以外的屬性凄鼻,其余屬性添加到a標簽上 Link組件通過 預取(在生產(chǎn)中)功能自動優(yōu)化應用程序以獲得最佳性能
// 引入組件
import Link from 'next/link'
// 函數(shù)組件不需要引入 React
function AboutPage() {
return (
<div>
{/* Link 中必須要寫 a 標簽,href 必須寫在 Link 中 */}
<Link href="/list"><a>Go to list Page</a></Link>
<h3>Function Component</h3>
</div>
)
}
export default AboutPage</pre>
靜態(tài)資源
應用程序根目錄中的 public 文件夾用于存放靜態(tài)資源; 通過以下形式進行訪問: public/img/1.png->/img/1.png
public/css/style.css->/css/style.css
import React from 'react'
class ListPage extends React.Component {
render(){
return <div>
<h3>ListPage</h3>
{/* 引入圖片文件 */}
<img src="/img/1.png" />
</div>
}
}
export default ListPage</pre>
CSS樣式
內(nèi)置 styled-jsx
https://github.com/vercel/styled-jsx#readme
在 Next.js中內(nèi)置了 styled-jsx 块蚌,它是一個CSS-in-JS庫闰非,允許在 React 組件中編寫 CSS,CSS 僅作用于當前組件內(nèi)部匈子;
import React from 'react'
class ListPage extends React.Component {
render(){
return <div>
<h3 >ListPage</h3>
<p className="pra"> 這是一個p標簽里面的內(nèi)容 </p>
<style jsx>
{`
.pra{
color: red;
}
`}
</style>
</div>
}
}
export default ListPage</pre>
CSS模塊
通過使用CSS模塊功能河胎,允許將組件的 CSS 樣式編寫在單獨的 CSS 文件中 CSS 模塊約定樣式文件的名稱必須為: 組件文件名稱.module.css
創(chuàng)建 ./pages/list.module.css
文件并填寫如下內(nèi)容:
.prag{
color:brown;
font-size: 20px;
}
import React from 'react'
// 引入樣式文件
import ListStyle from './list.module.css'
class ListPage extends React.Component {
render(){
return <div>
<h3 >ListPage</h3>
{/* 使用樣式 */}
<p className={ListStyle.prag}> 這是一個p標簽里面的內(nèi)容 </p>
</div>
}
}
export default ListPage</pre>
全局樣式文件
1:在 pages 文件夾中新建 _ app. js
文件(文件名固定) 2:在項目根目錄下創(chuàng)建 styles 文件夾,并在其中創(chuàng)建 global.css 3:在 _app.js
中通過 import 引入 global.css
global.css 中的樣式虎敦,將會全局起作用
/pages/_app.js
文件中的內(nèi)容如下:
import '../styles/globals.css'
// 固定代碼
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp
在新版框架中游岳,已經(jīng)幫我們做好相關內(nèi)容及文件和代碼
服務端渲染方法
getServerSideProps() 這個方法是服務端渲染的方法,適合頁面數(shù)據(jù)實時變化的應用其徙;getServerSideProps() 方法接收一個參數(shù)胚迫,是當前的 HTTP 客戶端請求對象;
import React from 'react'
// 類組件
class ListPage extends React.Component {
render(){
return <div>
<h3 >ListPage</h3>
<h4>服務器數(shù)據(jù):</h4>
{/* 類組件的方式展示數(shù)據(jù)內(nèi)容 */}
<p> {this.props.data[0].name} </p>
</div>
}
}
// // 函數(shù)組件
// function ListPage({data}){
// return (
// <div>
// <h3 >ListPage</h3>
// <h4>服務器數(shù)據(jù):</h4>
// {/* 類組件的方式展示數(shù)據(jù)內(nèi)容 */}
// <p> {data[1].name} </p>
// </div>
// )
// }
// Node 環(huán)境下執(zhí)行
// 文件讀寫唾那,數(shù)據(jù)庫鏈接访锻,網(wǎng)絡通信
// export async function getStaticProps(){
export async function getServerSideProps(){
const res = await fetch('http://localhost:80/');
const backData = await res.json();
const data = JSON.parse(backData);
console.log(data);
return {
props:{data}
}
}
export default ListPage
項目構建與運行
項目構建:npm run build
啟動運行項目: npm run start
靜態(tài)站點生成
next.js 不僅提供服務端渲染的方式,同時還提供了靜態(tài)站點生成的解決方案闹获;
靜態(tài)站點生成也被稱為 SSG(Static Site Generators)期犬,就是將應用中用到的所以頁面,全部生成靜態(tài)文件的方案避诽;
next 官方建議在大多數(shù)情況下使用靜態(tài)站點生成龟虎,靜態(tài)站點生成方案,更適合 CDN沙庐、緩存鲤妥、內(nèi)容數(shù)據(jù)無變化的頁面,比如:宣傳頁拱雏、博客文章棉安、幫助文檔、新聞頁面铸抑、電商產(chǎn)品列表等眾多應用場景贡耽;
Next.js 中的 getStaticProps 、 getStaticPaths 就是靜態(tài)站點生成鹊汛;是在構建時生成 HTML 的方法蒲赂,以后的每個請求都共用構建時生成好的 HTML;
Next.js 建議大多數(shù)頁面使用靜態(tài)生成柒昏,因為頁面都是事先生成好的凳宙,一次構建,反復使用职祷,訪問速度快氏涩。 服務器端渲染訪問速度不如靜態(tài)生成快届囚,但是由于每次請求都會重新渲染,所以適用數(shù)據(jù)頻繁更新的頁面或頁面內(nèi)容隨請求變化而變化的頁面是尖;
在 next.js 中意系,靜態(tài)生成分為 無數(shù)據(jù)和有數(shù)據(jù)兩種情況; 如果組件不需要在其他地方獲取數(shù)據(jù)饺汹,默認直接進行靜態(tài)生成蛔添,如果組件需要在其他地方獲取數(shù)據(jù),在構建時 Next.js 會預先獲取組件需要的數(shù)據(jù)兜辞,然后再對組件進行靜態(tài)生成
我們來對比一下迎瞧,開發(fā)環(huán)境不會打包靜態(tài)文件,生產(chǎn)環(huán)境打包逸吵,默認生成靜態(tài)文件
那么凶硅,有數(shù)據(jù)的情況應該怎么做呢?
有數(shù)據(jù)的靜態(tài)生成
getStaticProps() 這個方法官方翻譯為 靜態(tài)生成扫皱。是把組件提前編譯成 html 文件足绅,然后把整個 html 文件響應到客戶端,從而達到預渲染的目的韩脑。
getStaticProps() 方法是個異步方法氢妈,在 Node 環(huán)境下執(zhí)行(構建時執(zhí)行),因此可以進行文件讀寫段多,數(shù)據(jù)庫鏈接首量,網(wǎng)絡通信等一些列操作
對于這個方法的使用,先看 demo:
import React from 'react'
import Axios from "axios"
// 類組件
class ListPage extends React.Component {
render(){
return <div>
<h3 >ListPage</h3>
<h4>服務器數(shù)據(jù):</h4>
{/* 類組件的方式展示數(shù)據(jù)內(nèi)容 */}
<p> {this.props.data[0].name} </p>
</div>
}
}
// // 函數(shù)組件
// function ListPage({data}){
// return (
// <div>
// <h3 >ListPage</h3>
// <h4>服務器數(shù)據(jù):</h4>
// {/* 類組件的方式展示數(shù)據(jù)內(nèi)容 */}
// <p> {data[1].name} </p>
// </div>
// )
// }
// Node 環(huán)境下執(zhí)行
// 文件讀寫衩匣,數(shù)據(jù)庫鏈接蕾总,網(wǎng)絡通信
export async function getStaticProps(){
const d3 = await Axios.get('http://localhost:80/');
const data = JSON.parse(d3.data);
console.log(data)
// 返回的 Props 屬性的值會傳遞給組件
return {
props:{data}
}
}
export default ListPage
getStaticProps 方法內(nèi)部必須返回一個對象粥航,這個對象中的 props 屬性講傳遞到組件中 琅捏。
getStaticPaths() 這個方法也是靜態(tài)生成。與 getStaticProps 共同使用递雀,會根據(jù)不同的請求參數(shù)生成不同的靜態(tài)頁面柄延,它的使用方式比較特殊,代碼文件要放在一個目錄中缀程,同時代碼文件的文件名搜吧,要使用 可選項 文件名的形式,如\pages\props\[id].js
的形式杨凑,在項目構建時滤奈,next 會根據(jù)不同的 ID 值,生成不同的對應的 靜態(tài)文件撩满,如下代碼
import React from 'react'
import Axios from "axios"
// 類組件
class ListPage extends React.Component {
render() {
return <div>
<h3 >ListPage - Class</h3>
<p>{this.props.backData.name}</p>
</div>
}
}
// 根據(jù)客戶端參數(shù)生成靜態(tài)頁面
export async function getStaticPaths() {
return {
// 匹配客戶端請求的 id 值
paths: [{ params: { id: "1" } }, { params: { id: "2" } }],
fallback: false
}
}
// 自動調用當前方法蜒程,將客戶端參數(shù)傳入; { params } 接受到的客戶端參數(shù)
export async function getStaticProps({ params }) {
const d3 = await Axios.get('http://localhost:80/');
const data = JSON.parse(d3.data);
console.log(params)
// 循環(huán)服務器數(shù)據(jù)绅你,獲取
for (let i = 0; i < data.length; i++) {
// console.log(data[i].id)
if (params.id == data[i].id) {
// 返回對應數(shù)據(jù)
const backData = data[i];
return {
props: { backData }
}
}
}
}
export default ListPage
最終構建后,會生成不同的靜態(tài)頁面:
靜態(tài)站點導出
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"export": "next build && next export"
},
執(zhí)行命令 npm run export
昭躺,進行構建和導出操作忌锯,生成 out 文件夾,獲取靜態(tài)站點資源领炫;
除此之外偶垮,還有專門針對 React 的 SSG 靜態(tài)站點生成方案:Gatsby https://www.gatsbyjs.cn/ ,感興趣的可以自己去看看
當然帝洪,你 React 有的似舵,我 Vue 怎么可能沒有呢:Gridsome https://www.gridsome.cn/
斗爭吧,前端框架們……