免費不用抽的ssr,不進來看看麼

服務端渲染大揭祕

前言

還在替弟弟學業操心的我聽到一聲:SSR!姐我 SSR 了!javascript

我:???css

???

小學生都會 SSR 了?讓我清醒一下。html

哦不,是這個啊~~前端

不知火

忙於 coding 的你必定據說過 SSGSSR 這些名詞,到底是啥一塊兒來揭祕。java

那什麼是 SSR?Super Super Rare?

SSG 全名 Static-Site Generator,靜態站點生成,聽着名字就曉得是靜態的、在構建的時候就生成了,那麼想要更新網站內容就要從新構建,這適合企業官網 or 我的博客等,沒有頻繁更新的訴求。優勢想一想就很明顯,速度快(連 api 都沒確定快了),部署方便(就靜態文件丟上去就好使),安全(純靜態也沒有 sql 注入).. SSR SSR 全名 Server-side rendering (SSR),服務端渲染,node

CSR 的優勢很明顯,那缺陷也很明顯(這不是廢話),即便你作了 dynamic importsplit chunk, 但也免不了 bundle,一個作了 3 年的大應用 bundle 大小 1m 是很常見的,瀏覽器首次構建 HTML 拿到的是空的,要等 js 執行完了再開始動態渲染改變 DOM 樹,這個期間還會請求 api,據獲取數據將數據渲染到頁面,完成顯示,想一想時間就很長。 因爲服務器(針對任何頁面)提供的初始 HTML 不包含任何特定於應用程序的 HTML,搜索引擎將該網站視爲空白,沒有任何內容。所以,儘管你的網站有巨大的流量或相關內容,但它可能不會出如今搜索結果的頂端。react

大體腦海裏知道 SSR 快,究竟什麼快? 若是在服務器上執行 jsHTML 在服務端就能夠裝配好了,返回給瀏覽器渲染,頁面就能夠有初步的展現,可是在服務端是沒有 Window 無法進行綁定的,所以在客戶端還要執行一遍腳本,執行生命週期方法,對事件就行綁定,對 DOM 進行 diff,在這段事件完成以前, 服務端和客戶端要執行一套代碼,就是同構,以 react 爲例,hydrate 不會再從新渲染 HTML.webpack

要解決的問題

同構(Isomorphic rendering),就是服務端和客戶端一套代碼,服務端去渲染,客戶端來負責交互。nginx

路由的同構與數據預取web

客戶端使用 BrowserRouter,服務端使用 staticRouter,在 node 端沒有 history 對象,只是根據請求的路由返回匹配的 React.createElementmatchRoutes 方法實現路由匹配 數據的預取 聲明路由的時候把數據請求方法關聯到路由中,好比定一個 loadData 方法,而後在查找到路由後就能夠判斷是否存在 loadData 這個方法。

// routes.ts
const routes = [
  {
    path: "/",
    component: loadable(() => import("./Com")),
    loadData: () => getData(),
  },
];
const loadData = () => {
  const promises: Promise<unknown>[] = [];
  routes.some((route) => {
    const match = matchPath(ctx.request.path, route);
    // 調用定義的獲取數據的方法
    if (match && route.loadData) promises.push(route.loadData());
    return match;
  });
  return Promise.all(promises).then(() => {
    return Promise.resolve(
      <StaticRouter> <App /> </StaticRouter>
    );
  });
};

// 預取的數據寫入HTML(ejs)
複製代碼

動態加載以及資源獲取

使用 loadable 庫 主要緣由是獲取資源映射,當路由匹配到 key 獲取 value 資源 塞到 HTML 返回字符串給客戶端。而且 dynamic import 在客戶端能夠用 React.lazy,但在 18 以前不能用,若是用 React.lazyjs 加載並執行以後才能加載對應頁面的 bundle,增長了 TTI(首次交互)的時間,即便 react18 能夠了資源映射仍是須要本身來獲取( loadable 逃不掉了),@loadable/webpack-plugin 能夠打出來資源映射的 map,交給 ChunkExtractor,思路是首先匹配路由,根據匹配到的路由取相應的映射資源,加載資源

// webpack配置
const LoadablePlugin = require('@loadable/webpack-plugin');

module.exports = {
  module: {
    rules: [],
  },
  plugins: [
    new LoadablePlugin(),
    ...
  ],
};
// 資源映射
const statsFile = path.resolve(__dirname, '../dist/asset/loadable-stats.json'); // 上面的loadaer默認打出來這個名字
...
const extractor = new ChunkExtractor({
  statsFile,
  publicPath: '/',
});
// extractor.getLinkTags(), extractor.getStyleTags(), ...
複製代碼

渲染同構

React.hydrate 水合 這個 api ,對節點進行對比,客戶端執行生命週期方法,不會再從新渲染 HTML,比對客戶端和服務端的 HTML 節點作 diff,比對結果不一致的時候,HTML 上的屬性不會被替換,會把不同的子節點替換,會拋出指向出錯節點 warning,須要手動處理。

WechatIMG106120-tuya

數據同構

吐槽一下比較蛋疼的解釋(國內大多數文章會出現的概念),從數據層面,把數據放到 Window 上叫注水,把數據從 Window 取出來叫脫水,屬實比較難理解,簡單理解就是服務端將數據寫到 ejs 的模版裏,做爲全局變量,服務端就從 Window 上取這個變量,實現數據的同構。

///rendux的數據
// ejs模版
<body>
  <div id="root"><%- html %></div>
  <script type="text/javascript"> window.REDUX_PRELOAD_DATA = <%- preloadState %> </script>
  <%- reload -%>
  <%- scriptTags %>
</body>

// server/app.ts
ejs.renderFile(
    template,
    {
      ...
      // 將preloadState變量寫入ejs模版
      preloadState: JSON.stringify(ctx.store.getState()),
      ...
    },
    {},
    (err, str) => {
      ...
    },
  );
});
複製代碼

取到路徑,key(路由路徑) value(頁面所需的資源)

loadable 如何知道的頁面路徑?

const jsx = extractor.collectChunks(reactApp);其實這塊是建立 provider,而後下面的 loadable(() => import(''))至關於 consumer

image-20210809230313872

紙巾一擦,咱繼續,繼續,接着 wu,接着 tiao。

性能監控

監控 nodejs v8 堆內存,內存超出 80%進行服務降級,以及在時間範圍內(可能半小時檢查一次,試業務狀況而定),將不健康的容器部署到其餘實例。 process.memoryUsage() 返回一個對象,描述 Node.js 進程的內存使用量(以字節爲單位)。

import { memoryUsage } from "process";

console.log(memoryUsage());
// 打印:
// {
// rss: 4935680,
// heapTotal: 1826816,
// heapUsed: 650472,
// external: 49879,
// arrayBuffers: 9386
// }
複製代碼
  • heapTotalheapUsed 指的是 V8 的內存使用狀況。
  • external 指的是綁定到 V8 管理的 JavaScript 對象的 C++ 對象的內存使用。
  • rssResident Set Size,是進程在主內存設備(即總分配內存的一個子集)中佔用的空間量,包括全部 C++JavaScript對象和代碼。
  • arrayBuffers 是指爲 ArrayBufferSharedArrayBuffer 分配的內存,包括全部 Node.js Buffer。 這也包含在 external 值中。 當 Node.js 用做嵌入式庫時,此值多是 0,由於在這種狀況下可能不會跟蹤 ArrayBuffer 的分配。

拋開一切,咱們用 Next.js 吧

上面提出的問題,Next.js 均可以完美解決,通用級 SSR 解決方案,雖然上面說了一堆原理,但做爲企業級解決方案依然不夠,Next.js 大量的代碼在處理各類兼容性的問題,做爲體量較大的react項目,選取通用性方案更爲推薦。

混合渲染

咱們在實際業務中經常是部分頁面須要 SSR,其他的依舊 CSR。 對於咱們本身搭的簡易 SSR 能夠在配置白名單,作請求匹配的時候, 位於白名單的吐空的 HTML 字符串(僅有 css、js 資源的);或者在 nginx 一層作攔截,白名單轉發到 CSR 的地址。 Next.js 爲這種混合渲染提供了更爲簡單的方式,提供 getStaticProps 靜態生成的 api,在這裏面的請求會被在構建的時候請求好,寫入數據到Window。若是沒有 export getServerSideProps 方法,就會默認走 SSG 渲染,getServerSideProps 是隻會在服務端執行的 api,所以在靜態生成的時候不會導出getServerSideProps,下面是靜態生成的代碼。

export async function getStaticProps() {
  // Call an external API endpoint to get posts.
  // You can use any data fetching library
  const res = await fetch("https://.../posts");
  const posts = await res.json();

  // By returning { props: { posts } }, the Blog component
  // will receive `posts` as a prop at build time
  return {
    props: {
      posts,
    },
  };
}
複製代碼

服務降級

node 服務器不健康的時候,達到毫秒級 CSR,即不須要依賴 對於咱們本身搭的框架,可採起 serverclient 分開打包,給 client 打包產出加上 HTML 文件,就能夠單獨託管了。下圖是 nginx 配置樣例及優雅降級的原理圖,解釋下就是用戶請求到 nginx,若是服務器正常就會轉發到 node 渲染服務器,若是異常返回異常狀態碼,攔截異常狀態碼,並重寫成 200,轉發到 HTML 靜態文件服務器。 服務降級

天然而然優雅的服務降級

既然自帶 SSG,那麼咱們的服務降級即可採起他的靜態生成,官方提供 next export 命令,能夠直接生成 SSG 產出,將每一個路由都打出一個 HTML 文件,裏面會引入所須要的 cssjs,須要注意的是想要一套代碼就須要種植環境變量,由於 SSG 要求是不能暴露getServerSideProps,兼容處理代碼以下

// .sh
export NEXT_SSG = SSG
// .tsx
let getServerSideProps =
  process.env.NEXT_SSG === "SSG"
    ? undefined
    : async () => {
        const res = await fetch(`url`);
        const post = await res.json();
        return { props: { name: post?.data?.token } };
      };

export { getServerSideProps };
複製代碼

將打出來的產出丟到 nginx,以下配置

server {
root /www/data;

    location / {
        try_files $uri ;
    }

}
複製代碼

若是取到了返回路徑下的 HTML,客戶端拿到 HTML 字符串以後再 hydrate,實際上仍是 CSR,實現了不走 node 服務器的優雅降級。

WeChat2a1bf8dbab6316ddef9eeb79e1b15ecd-tuya

自帶的性能分析

對於 web 應用的性能分析老是繞不開Web Vitals的幾大指標,Next.js Analytics 爲咱們提供了很是方便的 api 來獲取這些指標數據。 對於部署在託管在Vercel的項目,在其Analytics tab 頁籤中就能夠看到可視化的指標數據。 對於自託管的項目能夠經過也是能夠進行 web 性能分析的。 僅僅只須要建立一個_app.js 在其中暴露一個名爲reportWebVitals的方法。

Next.js 會在完成任何一個指標計算的時候調用該函數。

//_app.js
export function reportWebVitals(metric) {
  console.log(metric);
}
// 打印
// {
// id: "1628518848412-9295257969280",
// label: "web-vital",
// name: "TTFB",
// startTime: 0,
// value: 815.5,
// }
複製代碼
  • id:指標惟一的標識符;
  • label: 是指標類型,分別是web-vitalscustom
  • name:指標名稱;
  • startTime: 以毫秒爲單位,全部記錄該指標的時間戳;
  • value: 以毫秒爲單位,指標的值或者持續的時間。

web-Vitals

是谷歌提出的用來統一衡量web頁面用戶體驗和質量的指標。Next.js爲咱們提供了一下五種指標數據:

  • 首字節時間TTFB
  • 首次內容繪製FCP
  • 衡量加載性能LCP
  • 衡量可交互性FID
  • 衡量視覺穩定性CLS

custom

這是Next.js提供的獨有的指標,用來衡量 hydraterender 時間

  • Next.js-hydration:頁面開始和完成hydrate所需的時間(以毫秒爲單位)
  • Next.js-route-change-to-render:頁面在路由改變後到開始渲染的時間(以毫秒爲單位)
  • Next.js-render: 路由更改後到頁面完成渲染的時間(以毫秒爲單位)

經過這個函數咱們就能夠建立本身的性能分析報告,這還不香麼!

src=http___i0.hdslb.com_bfs_article_2229244e224ca19d6753fe495d37055b48b47d72.jpg&refer=http___i0.hdslb-tuya

其餘

動態加載、動態路由匹配等 next10 均已經支持,須要的能夠移步文檔哦~ www.nextjs.cn/docs/gettin…

試狀況選擇 serverless

什麼是 serverless 呢,廣義來講,自動擴容 按需計費 noops 無需運維符合這些就算是了,原本 serverless 就是很抽象的概念,大體步驟是 SSR 應用放在函數中,serverless 有觸發器,選用 http 觸發器,觸發器中能夠添加路由,接收到的路由傳遞給 Next.js,再返回 HTML 給客戶端。

Serverless 能解決什麼問題?

Serverless 可使應用在服務端免運維。在沒有流量的時候縮容爲 0,節省流量。能夠節省很多開支。是性價比高的方案。 對於落地頁可能在特定狀況流量增多,以及邊緣服務等,使用 Serverless+SSR 可謂是完美配合~。 啥叫免運維呢,將一個服務 部署在服務商給咱們提供的 運行環境中,不須要關心運維相關的東西,只須要關心業務代碼,咱們也不須要維護物理機 虛擬機 之類的 Linux。 各大雲服務廠商有封裝好的 Next.js 服務,能夠簡單操做快速部署 Next.js

有須要能夠自行搜索哦,就不貼了~

總結

介紹了 ssr、ssg 是啥 解決了什麼問題、原理以及性能監控,通用級別 ssr 框架 nextjs 如何作優雅服務降級,(是否是已經躍躍欲試想實操),nextjs 依舊在持續更新中並在幾天前發佈了 11,支持 module federation,微前端也能夠用 nextjs(手動狗頭)。

番外

我是萱醬,是個 lo 娘 FE(鼓勵師劃掉),路過的朋友給個三連叭~你的支持是萱醬創做的動力(說的我都感動了),我會持續更新~

相關文章
相關標籤/搜索