上一篇 前端福音:Serverless 和 SSR 的天做之合,詳細介紹了 SSR 相關知識,同時也提到了 Serverless 給 SSR 方案帶來的福利。但它只是將 Next.js 應用部署到 Serverless 服務上而已,並不適合實際生產業務。爲此本篇專門針對 Next.js 的 SSR 方案進行了探索和優化,一步一步帶你們瞭解,如何基於 Serverless 架構部署一個實際的線上業務。css
搶先體驗:serverless-cnodehtml
本文主要內容:前端
因爲本人對 Serverless Framework 開發工具比較熟悉,而且長期參與相關開源工做,因此本文均使用 Serverless Components 方案進行部署,請在開始閱讀本文以前,保證當前開發環境已經全局安裝 serverless
命令行工具。
本文依然上一篇中介紹的 Next.js 組件 來幫助快速部署 Next.js 應用到騰訊雲的 Serverless 服務上。node
咱們先快速初始化一個 Serverless Next.js 項目:git
$ serverless create -u https://github.com/serverless-components/tencent-nextjs/tree/master/example -p serverless-nextjs $ cd serverless-nextjs
該項目模板已經默認配置好 serverless.yml
,能夠直接執行部署命令:程序員
$ serverless deploy
大概 30s
左右就能夠部署成功了,以後訪問生成的 apigw.url
連接 https://service-xxx-xxx.gz.apigw.tencentcs.com/release/
就能夠看到首頁了。github
Next.js 組件,會默認幫助咱們建立一個 雲函數
和 API 網關
,而且將它們關聯,實際咱們訪問的 是 API 網關,而後觸發雲函數,來得到請求返回結果,流程圖以下:express
解釋:咱們在執行部署命令時,因爲一個簡單的 Next.js 應用除了業務代碼,還包括龐大的
node_modules
文件夾,這就致使打包壓縮的代碼體積大概20M
左右,因此大部分時間消耗在代碼上傳上。這裏的速度也跟開發環境的網絡環境有關,而實際上咱們雲端部署是很快的,這也是爲何須要30s
左右的部署時間,並且網絡差時會更久,固然後面也會提到如何提升部署速度。npm
相信你已經體會到,藉助 Serverless Components 解決方案的便利,它確實能夠幫助咱們的應用高效的部署到雲端。並且這裏使用的 Next.js 組件,針對代碼上傳也作了不少優化工做,來保證快速的部署效率。api
接下來將介紹如何基於 Next.js 組件,進一步優化咱們的部署體驗。
使用過 API 網關的小夥伴,應該都知道它能夠配置自定義域名,以下圖所示:
可是這個手動配置仍是不夠方便,爲此 Next.js 組件也提供了 customDomains
來幫助開發者快速配置自定義域名,因而咱們能夠在項目的 serverless.yml
中新增以下配置:
org: orgDemo app: appDemo stage: dev component: nextjs name: nextjsDemo inputs: src: dist: ./ hook: npm run build exclude: - .env region: ap-guangzhou runtime: Nodejs10.15 apigatewayConf: protocols: - https environment: release enableCORS: true # 自定義域名相關配置 customDomains: - domain: test.yuga.chat certificateId: abcdefg # 證書 ID # 這裏將 API 網關的 release 環境映射到根路徑 pathMappingSet: - path: / environment: release protocols: - https
因爲這裏使用的是 https
協議,因此須要配置託管在騰訊雲服務的證書 ID,能夠到 SSL 證書控制檯 查看。騰訊雲已經提供了申請免費證書的功能,固然你也能夠上傳本身的證書進行託管。
以後咱們再次執行部署命令,會獲得以下輸出結果:
這裏因爲自定義域名時經過 CNAME 映射到 API 網關服務,因此還須要手動添加輸出結果中紅框部分的 CNAME 解析記錄。等待自定義域名解析成功,就能夠正常訪問了。
Next.js 應用,有兩種靜態資源:
Webpack
打包處理輸出到 .next/static
目錄,好比 .next/static/css
樣式文件目錄。public
文件夾,經過靜態文件服務返回,而後項目中能夠直接經過 url 的方式引入(官方介紹)。第一種的資源很好處理,Next.js 框架直接支持在 next.config.js
中配置 assetPrefix
來幫助咱們在構建項目時,將提供靜態資源託管服務的訪問 url 添加到靜態資源引入前綴中。以下:
// next.config.js const isProd = process.env.NODE_ENV === "production"; const STATIC_URL = "https://serverless-nextjs-xxx.cos.ap-guangzhou.myqcloud.com"; module.exports = { assetPrefix: isProd ? STATIC_URL : "", };
上面配置中的 STATIC_URL
就是靜態資源託管服務提供的訪問 url,示例中是騰訊雲對應的 COS 訪問 url。
那麼針對第二種資源咱們如何處理呢?這裏就須要對業務代碼進行稍微改造了。
首先,須要在 next.config.js
中添加 env.STATIC_URL
環境變量:
const isProd = process.env.NODE_ENV === "production"; const STATIC_URL = "https://serverless-nextjs-xxx.cos.ap-guangzhou.myqcloud.com"; module.exports = { env: { // 3000 爲本地開發時的端口,這裏是爲了本地開發時,也能夠正常運行 STATIC_URL: isProd ? STATIC_URL : "http://localhost:3000", }, assetPrefix: isProd ? STATIC_URL : "", };
而後,在項目中修改引入 public
中靜態資源的路徑,好比:
<!-- before --> <head> <title>Create Next App</title> <link rel="icon" href="/favicon.ico" /> </head> <!-- after --> <head> <title>Create Next App</title> <link rel="icon" href={`${process.env.STATIC_URL}/favicon.ico`} /> </head>
最後,在 serverless.yml
中新增靜態資源相關配置 staticConf
,以下:
org: orgDemo app: appDemo stage: dev component: nextjs name: nextjsDemo inputs: src: dist: ./ hook: npm run build exclude: - .env region: ap-guangzhou runtime: Nodejs10.15 apigatewayConf: # 此處省略.... # 靜態資源相關配置 staticConf: cosConf: # 這裏是建立的 COS 桶名稱 bucket: serverless-nextjs
經過配置 staticConf.cosConf
指定 COS 桶,執行部署時,會默認自動將編譯生成的 .next
和 public
文件夾靜態資源上傳到指定的 COS。
修改好配置後,再次執行 serverless deploy
進行部署:
$ serverless deploy serverless ⚡framework Action: "deploy" - Stage: "dev" - App: "appDemo" - Instance: "nextjsDemo" region: ap-guangzhou # 此處省略...... staticConf: cos: region: ap-guangzhou cosOrigin: serverless-nextjs-xxx.cos.ap-guangzhou.myqcloud.com bucket: serverless-nextjs-xxx
瀏覽器訪問,打開調試控制檯,能夠看到訪問的靜態資源請求路徑以下:
上圖能夠看出,靜態資源均經過訪問 COS 獲取,如今雲函數只須要渲染入口文件,而不須要像以前,靜態資源所有經過雲函數返回。
備註:以前因爲都是將 .next 部署到了雲函數,因此無法訪問頁面後,頁面中的靜態資源,如圖片,都須要再次訪問雲函數,而後獲取。因而看似咱們請求了一次雲函數,而實際上雲函數單位時間併發數,會根據頁面靜態資源請求數而增長,從而形成冷啓動問題。
上面咱們已經將靜態資源都部署到 COS 了,頁面訪問也快了不少。可是對於生產環境,還須要給靜態資源配置 CDN 的。經過 COS 控制檯已經能夠很方便的配置 CDN 加速域名了。可是仍是須要手動去配置,做爲一名懶惰的程序員,我仍是不能接受的。 而 Next.js 組件正好提供了給靜態資源配置 CDN 的能力,只須要在 serverless.yml
中新增 staticConf.cdnConf
配置便可,以下所示:
# 此處省略.... inputs: # 此處省略.... # 靜態資源相關配置 staticConf: cosConf: # 這裏是建立的 COS 桶名稱 bucket: serverless-nextjs cdnConf: domain: static.test.yuga.chat https: certId: abcdefg
這裏使用 https
協議,因此也添加了 https
的 certId
證書 ID 配置。此外靜態資源域名也須要修改成 CDN 域名,修改 next.config.js
以下:
const isProd = process.env.NODE_ENV === "production"; const STATIC_URL = "https://static.test.yuga.chat"; module.exports = { env: { STATIC_URL: isProd ? STATIC_URL : "http://localhost:3000", }, assetPrefix: isProd ? STATIC_URL : "", };
配置好後,再次執行部署,結果以下:
$ serverless deploy serverless ⚡framework Action: "deploy" - Stage: "dev" - App: "appDemo" - Instance: "nextjsDemo" region: ap-guangzhou apigw: # 省略... scf: # 省略... staticConf: cos: region: ap-guangzhou cosOrigin: serverless-nextjs-xxx.cos.ap-guangzhou.myqcloud.com bucket: serverless-nextjs-xxx cdn: domain: static.test.yuga.chat url: https://static.test.yuga.chat
注意:這裏雖然添加了 CDN 域名,可是仍是須要手動配置 CNAME
static.test.yuga.chat.cdn.dnsv1.com
解析記錄。
到這裏,Serverless Next.js 應用體驗已經優化了不少,咱們可使用 Lighthouse
進行性能測試,來驗證下咱們的收穫。測試結果以下:
優化前:
優化後:
先後對比,能夠明顯看出優化效果,固然這裏主要是針對靜態資源進行了優化處理,減小了冷啓動。爲了更好地遊湖體驗,咱們還能夠作的更多,這裏就不展開討論了。
隨着咱們的業務變得複雜,項目體積會愈來愈大,node_modules 文件夾也會變得原來越大,而如今每次部署都須要將 node_modules 打包壓縮,而後上傳,跟業務代碼一塊兒部署到雲函數。在實際開發中, node_modules
大部分時候是不怎麼變化的,可是當前每次都須要上傳,這必然會浪費不少部署時間,尤爲在網絡狀態很差的狀況下,代碼上傳就更慢了。
既然 node_modules
文件夾是不怎麼變動的,那麼咱們能不能只有在它變化時才上傳更新呢?
藉助 Layer 的能力是能夠實現的。
在這以前,先簡單介紹下 Layer:
藉助 Layer,能夠將項目依賴放在 Layer 中而無需部署到雲函數代碼中。函數在執行前,會先加載 Layer 中的文件到
/opt
目錄下(雲函數代碼會掛載到/var/user/
目錄下),同時會將/opt
和/opt/node_modules
添加到NODE_PATH
中,這樣即便雲函數中沒有node_modules
文件夾,也能夠經過require('abc')
方式引入使用該模塊。
正好 Layer 組件 能夠幫助咱們自動建立 Layer
。
使用時只須要在項目下添加 layer
文件夾,而且建立 layer/serverless.yml
配置以下:
org: orgDemo app: appDemo stage: dev component: layer name: nextjsDemo-layer inputs: region: ap-guangzhou name: ${name} src: ../node_modules runtimes: - Nodejs10.15 - Nodejs12.16
配置說明:
region:地區,須要跟雲函數保持一致
name:Layer 名稱,在雲函數綁定指定 Layer 時須要指定
src:指定須要上傳部署到 Layer 的目錄
runtimes:支持的雲函數運行環境
執行部署 Layer 命令:
$ serverless deploy --target=./layer serverless ⚡framework Action: "deploy" - Stage: "dev" - App: "appDemo" - Instance: "nextjsDemo-layer" region: ap-guangzhou name: nextjsDemo-layer bucket: sls-layer-ap-guangzhou-code object: nextjsDemo-layer-1594356915.zip description: Layer created by serverless component runtimes: - Nodejs10.15 - Nodejs12.16 version: 1
從輸出能夠清晰看到 Layer 組件已經幫助咱們自動建立了一個名稱爲 nextjsDemo-layer
,版本爲 1
的 Layer。
接下來咱們如何自動和咱們的 Next.js 雲函數綁定呢?
參考 serverless components outputs 說明文檔 ,能夠經過引用一個基於 Serverless Components 部署成功的實例的 outputs
(這裏就是控制檯輸出對象內容),語法以下:
# Syntax ${output:[stage]:[app]:[instance].[output]}
那麼咱們只須要在項目根目錄的 serverless.yml
文件中,添加 layers
配置就能夠了:
org: orgDemo app: appDemo stage: dev component: nextjs name: nextjsDemo inputs: src: dist: ./ hook: npm run build exclude: - .env - "node_modules/**" region: ap-guangzhou runtime: Nodejs10.15 layers: - name: ${output:${stage}:${app}:${name}-layer.name} version: ${output:${stage}:${app}:${name}-layer.version} # 靜態資源相關配置 # 此處省略....
注意:不一樣組件部署實例結果的依賴使用,須要保證 serverless.yml 中
org,app,stage
三個配置是一致的。
因爲 node_modules
已經經過 Layer 部署,因此還須要在 src.exclude
中添加忽略部署該文件夾。
以後再次執行部署命令 serverless deploy
便可, 你會發現此次部署時間大大縮減了,由於咱們不在須要每次壓縮上傳 node_moduels
這個龐大的文件夾了 (▽)
基於以上方案,我部署了一個完整的 Cnode 項目,serverless-cnode,歡迎感興趣的小夥伴,提交寶貴的 ISSUE/PR。
關於 Serverless SSR 的方案,我也在不斷嘗試和探索中,若是你有更好的方案和建議,歡迎評論或者私信來撩~
3 秒你能作什麼?喝一口水,看一封郵件,仍是 —— 部署一個完整的 Serverless 應用?
複製連接至 PC 瀏覽器訪問:https://serverless.cloud.tencent.com/deploy/express
3 秒極速部署,當即體驗史上最快的 Serverless HTTP 實戰開發!
傳送門:
- GitHub: github.com/serverless
- 官網:serverless.com
歡迎訪問:Serverless 中文網,您能夠在 最佳實踐 裏體驗更多關於 Serverless 應用的開發!