Next.js酷在哪裏?
之前使用 Next.js + strapi 做了一個簡單博客站點也順道寫了一篇 Next.js 簡明教程,之後 Next 本身一直在迅猛發展。利用代 js 能力來說做到了:
- 極佳的開發體驗
- 極佳的網站最佳的”動“,“靜”平衡
從特性上來說,支持:
- SSR(Server Side Rendering)
提供 getServerSideProps 方法,在用戶訪問時請求數據,適用於實時數據頁面。 - SSG(Static Site Generation)
提供 getStaticProps,getStaticPaths 方法來預先生產靜態頁面;
而更酷的一點是:使用 fallback,revalidate 來支持一定的動態性。
這種能“動”的 SSG 自然是我所需要的,保持靜態訪問,而又能在我新增修改文章的時候,站點能夠自動更新。絕佳!!
爲什麼還需要來Webify“折騰”一番?
既然上面已經很酷了,爲什麼會有今天的文章,爲什麼還需要折騰一番?
原因也很簡單:成本略高,爲了不錯的訪問速度,你需要一臺性能不錯的虛擬機,一定的帶寬。對於一般個人博客,投入不划算。
在這裏就隆重地有請我們的解決方案:騰訊雲開發Webify,簡單來說就是類似 vercel 的 Serverless 託管服務,不過支持更多的框架,而且是國內服務商,便宜且訪問速度一流。
有圖爲證:
而且現在託管,能免費領300元無門檻代金券,香啊~感興趣的可以點擊下方鏈接瞭解一下:https://cloud.tencent.com/developer/article/1871549
CloudBase Webify實戰
對於一般文章使用類似 github 管理的就簡單了,Webify支持版本 Github、Gitlab、Gitee 服務商,聽說即將支持 Coding:
- Vue.js (vue-cli)
- React.js (create-react-app)
- Hexo
- Gatsby.js
- Angular
- Next.js SSG
- Nuxt.js SSG
- 以及自動適配框架
以本博客 next 爲例,Webify實際上使用時了 next export 的能力,構建後,直接部署靜態文件到 server。
如果你的博客文章,直接使用 md,git 管理,看到這裏就OK了,git 提交,Webify自動會重新部署你的站點。cool~~
問題是如果你的站點數據來源於類似 strapi 這種 serverless cms 怎麼辦?next export 不支持next SSG中“動”的特性(fallback,revalidate)。
Webify高階——自動化Webify
其實方法也很簡單,加一個橋接服務,讓你的 serverless cms 的更新變動到 git 就好。
具體以 strapi 爲例子:
- strapi 數據發佈
- web hook到自定義的橋接服務。
- 橋接服務更新站點git。
- Weify觸發重新部署。
當然如果後續 webify 支持更多的重新部署方式,這裏會更簡單一點。
這樣乍看,似乎又回到了原點,我們還是需要一臺服務器,這裏又要引入本文的另一個嘉賓了,tcb 雲函數。上述這種按需調用的服務,使用雲函數最合適了,你不需要一個一直開機的虛擬機,你只需要在更新文章時候才需要喚起雲函數就好,隨用隨停,成本低廉。
按照本博客的場景,我們讓橋接服務在運行的時候,自動生成站點的 sitemap 到github來一舉兩得。
- 用來sitemap生成站點地圖xml;
- 使用@octokit/rest,@octokit/plugin-create-or-update-text-file來更新github中文件。
下面是精簡過的代碼:
生成站點地圖sitemap.xml
const {
SitemapStream,
streamToPromise
} = require('sitemap')
const {
Readable,
Transform,
pipeline
} = require('stream')
const {
apiRequest,
getPostsWithGraphql
} = require('./request')
const PaginationLimit = 30
module.exports = ({
hostname,
cmsUrl
}) => {
async function getPostSitemap() {
const smStream = new SitemapStream({
hostname,
});
let page = 0;
const postStream = new Readable({
objectMode: true,
async read(size) {
const result = await getPostsWithGraphql(`${cmsUrl}/graphql`, page++, PaginationLimit);
if (result.error || !Array.isArray(result.data.posts)) {
this.push(null);
} else {
result.data.posts.forEach((item) => {
this.push(item);
});
if (result.data.posts.length < PaginationLimit) {
this.push(null);
}
}
},
});
const trans = new Transform({
objectMode: true,
transform(data, encoding, callback) {
callback(null, {
url: `/p/${data.book.slug || data.book.uuid}/${
data.slug || data.uuid
}`,
changefreq: 'daily',
priority: 1,
lastmod: new Date(data.updated_at),
});
},
});
const buffer = await streamToPromise(pipeline(postStream, trans, smStream, (e) => {
// throw e;
}))
return {
path: 'public/sitemap.xml',
content: buffer.toString()
}
}
return Promise.all([
// getHomeSitemap(),
// getBookSitemap(),
getPostSitemap()
])
}
更新Github中文件
'use strict';
const {
Octokit
} = require("@octokit/rest");
const {
createOrUpdateTextFile,
} = require("@octokit/plugin-create-or-update-text-file");
const {
throttling
} = require("@octokit/plugin-throttling");
const getSitemaps = require('./sitemap')
const MyOctokit = Octokit.plugin(createOrUpdateTextFile, throttling);
exports.main = async (event, context) => {
const {
headers: {
authorization,
'x-strapi-event': strapiEvent
},
body
} = event;
const {
model,
entry
} = JSON.parse(body)
const {
CMS_TOKEN,
GITHUB_ACCESS_TOKEN,
BLOG_URL = 'https://hicc.pro',
CMS_URL = 'https://cms.hicc.pro'
} = process.env;
// strapi 上添加密鑰來確保安全
if (CMS_TOKEN !== authorization) {
return {
doTrigger: false
}
}
let doTrigger = false // TODO: 識別真正的發佈
const siteMaps = await getSitemaps({
hostname: BLOG_URL,
cmsUrl: CMS_URL
})
const octokit = new MyOctokit({
auth: GITHUB_ACCESS_TOKEN,
throttle: {
onRateLimit: (retryAfter, options) => {
console.warn(
`Request quota exhausted for request ${options.method} ${options.url}`
);
// Retry twice after hitting a rate limit error, then give up
if (options.request.retryCount <= 2) {
console.log(`Retrying after ${retryAfter} seconds!`);
return true;
}
},
onAbuseLimit: (retryAfter, options) => {
// does not retry, only logs a warning
console.warn(
`Abuse detected for request ${options.method} ${options.url}`
);
},
},
});
await Promise.all(siteMaps.map(({
path,
content
}) => {
return octokit.createOrUpdateTextFile({
// replace the owner and email with your own details
owner: "xxx",
repo: "xxx",
path,
message: `feat: update ${path} programatically`,
content: content,
branch: 'master',
sha: '',
committer: {
name: "xxx",
email: "[email protected]",
},
author: {
name: "xxx",
email: "[email protected]",
},
})
}))
return {
doTrigger
}
};
作者:hicc,騰訊高級前端開發工程師。
歡迎訪問Webify官網:https://webify.cloudbase.net/
個人站點扶持計劃,免費領300元無門檻代金券:https://webify.cloudbase.net/blog/personal-site-plan