• 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: "xxxx@outlook.com",
                },
                author: {
                    name: "xxx",
                    email: "xxxx@outlook.com",
                },
            })
        }))
    
    
        return {
            doTrigger
        }
    };
    

    作者:hicc,腾讯高级前端开发工程师。

    欢迎访问Webify官网:https://webify.cloudbase.net/

    个人站点扶持计划,免费领300元无门槛代金券:https://webify.cloudbase.net/blog/personal-site-plan

  • 相关阅读:
    Loadrunner将字符串存为参数
    loadrnner添加C语言代码的几种方式
    Jmeter分布式
    Java jmx的使用
    Jmeter关联之正则表达式提取器(完整版)
    性能测试基础概念
    Jmeter实现百分比业务比例
    js闭包与java内部类
    程序员的足球
    虚拟机下Linux读取USB设备的问题虚拟机下Linux无法读取USB设备的解决方案
  • 原文地址:https://www.cnblogs.com/CloudBase/p/15252646.html
Copyright © 2020-2023  润新知