天天看點

Next.js + 雲開發Webify 打造絕佳網站

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 為例子:

  1. strapi 資料釋出
  2. web hook到自定義的橋接服務。
  3. 橋接服務更新站點git。
  4. 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

繼續閱讀