琦宝儿 發表於 2021-4-27 17:36:00

CloudBase CMS + Next.js:轻松构建一个内容丰富的站点

<h2 id="项目背景"><strong>项目背景</strong></h2>
<p>试想一下,如果你现在要为你自己或者你所在的组织创建一个强内容的站点,同时要求好的 SEO(搜素引擎优化),比如博客,你会怎么做呢?</p>
<p>由 vite 或者 create-react-app 等脚手架构建的普通 SPA 应用是不行的,因为这样数据都是通过 AJAX 返回的。你暂时不了解这些概念也没关系,你只需要知道,这种方式下,搜索引擎是无法很好地了解你的网站是干什么的,所以就算大众在搜索引擎搜索你的站点的相关内容,搜索引擎也很难把你的站点排在搜索结果前列。</p>
<p>那么为每个页面都编写一个静态的 html 页面呢?比如,为每篇文章都编写一个 html 文件,然后放在服务器上,这样只要客户端请求某篇文章,服务器就把对应的文章页面直接返回。这样也不好,太麻烦了,如果每次更改内容,都要用硬编码的方式去应对,那就把事情弄得太复杂了。如果有一种后台系统,能让管理员通过后台系统的简单操作,就能修改网站呈现的内容就好了。</p>
<p><img src="https://main.qcloudimg.com/raw/ee0b736c9464b90c45cc0cb7dc9742b9.png"><br>
<img src="https://main.qcloudimg.com/raw/b8a085defb1e8de83a0160e61426dbc0.png"></p>
<p>本文就将带领你采用一种新颖的、便捷的开发方式——通过结合 CloudBase CMS 和 Next.js,去构建内容管理方便,利于 SEO 且响应快速的站点。</p>
<p>CMS 是云开发 CloudBase 推出的一款无头(headless)内容管理系统,提供给开发者方便地管理内容资源的能力。所谓无头,意思就是内容管理系统只负责管理你的内容,比如文章内容和作者列表。这些内容可以在客户端或者服务端通过 SDK 或者 API 的方式去取得。而如何去展示这些内容,则由开发者自行全权掌控。</p>
<p>Next.js 是一款生产级的 React 框架,它提供了静态生成的功能。静态生成的意思是,在构建的过程中,Next.js 就会自动执行数据拉取的逻辑,并把数据和 UI 渲染为一个个的静态 HTML 页面,这意味着,我们的站点将响应迅速,而且利于 SEO。</p>
<p><img src="https://main.qcloudimg.com/raw/80aeecdfad6b325b021323cd05628515.png"></p>
<p>本文将通过带领你构建一个个人博客,以展示如何结合这两者,并最终在腾讯云云开发上部署站点。</p>
<center>
<p>Demo在线预览:<br>
<img src="https://main.qcloudimg.com/raw/1c3bc5daecfa64a854a9afe245e908de.png"></p>
</center>
<h2 id="开启环境和项目"><strong>开启环境和项目</strong></h2>
<h4 id="1-开通-cloudbase-cms"><strong>1. 开通 CloudBase CMS</strong></h4>
<p>首先,你要在腾讯云控制台开通你的第一个云开发环境。云开发环境是云开发中的一个概念,每个云环境都集成了应用开发需要的基础能力,比如云数据库、云函数,开发者可以方便地组合、使用它们,为应用开发赋能。TCB-CMS 也是建立在云环境之上的。</p>
<p>创建环境时,你可以直接选择空模板并勾选免费资源选项即可,最后将环境命名为 my-blog。</p>
<p><img src="https://main.qcloudimg.com/raw/58331423d6d0517fbe32b3c3af4541a1.png"></p>
<p>可以看到,环境已经在创建中了。</p>
<p>环境创建完毕后,进入扩展应用模块,可以看到“CMS内容管理系统”,可以在这里安装它。设置都按照默认就可以了,唯一要注意的是,务必记住自己设置的<strong>管理员账号</strong>和<strong>密码</strong>。</p>
<p>等安装完毕后,可以在已安装应用一栏中进入应用。点击访问地址后,我们可以直接访问应用。进入应用并输入管理员账号和密码,然后可以看到以下页面:</p>
<p><img src="https://main.qcloudimg.com/raw/6d4a6a18238f37518b019bf7a7ffa320.png"></p>
<p>现在,系统中还没有任何项目,点击创建一个名为 MyBlog 的新项目,创建完毕后进入项目,可以看到内容模型和内容集合,拿数据库作类比,这两者就是数据库表和数据库表内容的关系,这两者就是我们要设置和管理的内容。已经有准备好的数据可以直接导入,分别就在 项目源码仓库 中的 ./schema 和 ./data 文件夹中。点击导入按钮,然后选择导入文件即可。</p>
<p>好了,现在 CMS 已经成功开通了,我们也往其中加入了内容。接下来,就可以着手 Next.js 应用的编写了。</p>
<h4 id="2-启动-nextjs-项目"><strong>2. 启动 Next.js 项目</strong></h4>
<p>Next.js 是构建于 React 之上的生产级前端框架。相比于原本的 React,Next.js 提供了静态生成、服务端渲染等特性,同时自带前端路由,我们这次就主要用到 Next.js 的静态生成功能。使用 Next.js 编写前端应用,和使用 create-react-app 脚手架编写 SPA 应用非常类似,而且更加便捷、开箱即用。</p>
<p>直接在命令行执行以下命令可以创建 Next.js 的样板项目并启动它:</p>
<pre><code>npx create-next-app
npm run dev
</code></pre>
<p>访问 localhost:3000 可以看到以下效果:</p>
<p><img src="https://main.qcloudimg.com/raw/77e4b7c53dc2f60627b0acc47d12a372.png"></p>
<p>接下来,我们就将基于这个样板项目开发网站。</p>
<h2 id="首页">首页</h2>
<p>这里,我们将在首页放置文章列表。</p>
<p>首先,打开项目下的 ./pages/index.js,发现页面导出了一个函数式的 React 组件。在 Next.js 中,pages 目录下,除了 api 文件夹下的内容和 _app.js,其他每个 js 文件导出的 React 组件都对应着一个或者一种页面,并且由 Next.js 直接生成对应的路由,index.js 导出的函数式组件就直接对应着我们进入网站后的第一个页面,而其他 js 文件于 ./pages 的相对地址就是 Next.js 为其自动生成的路由。</p>
<p>Next.js 在应用构建期,就会对每个页面执行数据拉取的逻辑,并根据 React 组件构建的 UI,渲染出最后的 HTML 页面,接下来,我们要做的就是,构建主页的 UI,以及为主页编写拉取数据的逻辑。</p>
<h4 id="ui-编写">UI 编写</h4>
<p>接下来对主页的 UI 进行修改:</p>
<pre><code>import Head from 'next/head'
import Link from 'next/link'
import styles from '../styles/Home.module.css'

export default function Home({ posts }) {
return (
    &lt;div className={styles.container}&gt;
      &lt;Head&gt;
      &lt;title&gt;云开发小站&lt;/title&gt;
      &lt;link rel="icon" href="https://main.qcloudimg.com/raw/3b942431a6ef465d3b8369969e861c0f/favicon.png" /&gt;
      &lt;/Head&gt;

      &lt;main className={styles.main}&gt;
      &lt;h1 className={styles.title}&gt;
          Welcome to &lt;a href="https://www.cloudbase.net/"&gt;云开发 CloudBase!&lt;/a&gt;
      &lt;/h1&gt;


      &lt;div className={styles.grid}&gt;
          {posts.map(post =&gt; (
            &lt;Link href={`/post/${post._id}`}&gt;
            &lt;a href="https://nextjs.org/docs" className={styles.card}&gt;
                &lt;h3&gt;{post.title} &amp;rarr;&lt;/h3&gt;
                &lt;p&gt;{post.author}&lt;/p&gt;
            &lt;/a&gt;
            &lt;/Link&gt;
          ))}
      &lt;/div&gt;
      &lt;/main&gt;

      &lt;footer className={styles.footer}&gt;
      &lt;a
          href="https://www.cloudbase.net/"
          target="_blank"
          rel="noopener noreferrer"
      &gt;
          Powered by{' '}
          &lt;img src="https://main.qcloudimg.com/raw/3b942431a6ef465d3b8369969e861c0f/favicon.png" alt="TCB Logo" className={styles.logo} /&gt;
      &lt;/a&gt;
      &lt;/footer&gt;
    &lt;/div&gt;
)
}
</code></pre>
<p>可以看到,修改后的 Home 组件,接受了一个 posts 为参数,这个 post 就是文章列表数据,我们将它在组件中渲染出来。</p>
<p>那么 post 从哪里来呢?在同一个 js 文件下,需要再导出一个 getStaticProps 函数。</p>
<pre><code>export async function getStaticProps() {
return {
    props: undefined,
}
}
</code></pre>
<p>这个函数返回的对象的 props 属性,就是导出的函数式组件用到的参数。所以,只需要在 getStaticProps 函数中得到数据并返回即可。</p>
<h4 id="拉取数据">拉取数据</h4>
<p>先安装拉取数据要用到的 SDK:</p>
<pre><code>npm install --save @cloudbase/node-sdk
</code></pre>
<p>然后,我们再创建 env.js 文件,在其中填入云环境相关信息:</p>
<pre><code>export const tcbConfig = {
    env: '',
    secretId: '',
    secretKey: ''
};
</code></pre>
<p>其中环境ID(env)可以直接在环境主页中看到,API 密钥(secretId,secretKey)则可以在 访问管理 中获取。</p>
<p>最后,我们创建 ./lib/api.js,然后填入以下内容,将数据拉取的逻辑全部集中在这个文件中。</p>
<pre><code>import tcb from '@cloudbase/node-sdk';
import { tcbConfig } from '../env';

const { Author, Article } = (() =&gt; {
    const db = tcb.init(tcbConfig).database();
    return {
      Author: db.collection('author'),
      Article: db.collection('article'),
    };
})();

export const getHomePosts = async () =&gt; {
    const posts = (await Article
      .where({})
      .orderBy('_updateTime', 'desc')
      .limit(10)
      .get()).data;
    for (const post of posts) {
      const { name } = (await Author
            .where({ _id: post.author })
            .get()).data;
      post.author = name;
    }
    return {
      posts
    }
};
</code></pre>
<p>在 api.js 中,根据环境信息,对 SDK 进行了初始化,并创建了用于查询文章和作者的实例 Author 和 Ariticle。在 getHomePosts 函数中,我们获取了展示用的文章。具体的逻辑如果不懂也暂时不必深究,现在只需要知道:通过执行 getHomePosts 我们能从云环境的 CMS 系统中拉取文章列表。</p>
<p>接着,再来修改 ./pages/index.js 文件:</p>
<pre><code>import Head from 'next/head'
import Link from 'next/link'
import styles from '../styles/Home.module.css'
import { getHomePosts } from '../lib/api'

...

export async function getStaticProps() {
return {
    props: await getHomePosts()
};
}
</code></pre>
<p>然后,再访问 localhost:3000,可以看到如下效果:</p>
<p><img src="https://main.qcloudimg.com/raw/8da5584a7cf004ce160e54ba1a95fe80.png"></p>
<p>这标志着:我们成功从 CMS 中获取数据并能够渲染出静态页面来返回给客户端啦!</p>
<h2 id="文章页面">文章页面</h2>
<p>接下来,就要着手编写文章页面了,基本流程差不多,但值得注意的是,文章页面和主页不同,只有一个主页,但是文章页面可能有无数个,而Next.js 提供了能力,能让我们只编写一个 js 文件,并加以细微的改动,就能渲染出无数的文章页面 。</p>
<h3 id="拉取用以渲染页面的文章内容">拉取用以渲染页面的文章内容</h3>
<p>先准备好需要的样式。首先创建 ./styles/Post.module.css 文件,具体样式内容可以参考:https://github.com/LRCong/nextjs-tcbcms-app/blob/main/styles/Post.module.css</p>
<p>接着,创建 ./pages/post/.js 文件,这个文件对应的,就是路由形如 /post/{id} 的所有页面,而 id 的作用就是匹配文章的 _id。这样通过访问 URL:/post/{id1}.js,就能访问到文章id等于 id1 的文章对应页面。</p>
<p>我们先往 .js 文件中填入以下的内容:</p>
<pre><code>import Head from 'next/head';
import Link from 'next/link';
import styles from '../../styles/Post.module.css';

export default function Post({
    title,
    image,
    author,
    avator,
    contentHtml
}) {
    return (
      &lt;div className={styles.container}&gt;
            &lt;Head&gt;
                &lt;title&gt;{title}&lt;/title&gt;
                &lt;link rel="icon" href="/favicon.png" /&gt;
            &lt;/Head&gt;

            &lt;main className={styles.main}&gt;
                &lt;div className={styles.info_container}&gt;
                  &lt;img className={styles.image} src={image}&gt;&lt;/img&gt;
                  &lt;div className={styles.info}&gt;
                        &lt;h1 className={styles.title}&gt;{title}&lt;/h1&gt;
                        &lt;div className={styles.author_info}&gt;
                            &lt;div className={styles.avator} style={{ backgroundImage: `url(${avator})` }}&gt;&lt;/div&gt;
                            &lt;h2&gt;{author}&lt;/h2&gt;
                        &lt;/div&gt;
                  &lt;/div&gt;
                &lt;/div&gt;

                &lt;div className={styles.markdown} dangerouslySetInnerHTML={{ __html: contentHtml }} /&gt;

                &lt;Link href='/'&gt;&lt;h3 className={styles.back}&gt;返回&lt;/h3&gt;&lt;/Link&gt;
            &lt;/main&gt;

            &lt;footer className={styles.footer}&gt;
                &lt;a
                  href="https://www.cloudbase.net/"
                  target="_blank"
                  rel="noopener noreferrer"
                &gt;
                  Powered by{' '}
                  &lt;img src="https://main.qcloudimg.com/raw/3b942431a6ef465d3b8369969e861c0f/favicon.png" alt="TCB Logo" className={styles.logo} /&gt;
                &lt;/a&gt;
            &lt;/footer&gt;
      &lt;/div&gt;
    )
}

export async function getStaticPaths() {
    return {
      paths: undefined,
      fallback: false
    };
}

export async function getStaticProps({ params }) {
    return {
      props: undefined
    };
}
</code></pre>
<p>可以看到,相比 index.js,.js 多出了一个 getStaticProps 函数,getStaticProps 也多了一个 parms 参数。getStaticProps 函数暂时不用管,而 param.id 就是在路由中匹配到的 id,可以借助它,执行获取对应文章内容的逻辑。</p>
<p>在 api.js 中,添加以下内容:</p>
<pre><code>// 对于 image 类型的字段,直接取得的 id 需要转换为可用的 URL
const dealWithUrl = url =&gt; 'https://' + url
    .replace(`cloud://${tcbConfig.env}.`, '')
    .replace('/cloudbase-cms', '.tcb.qcloud.la/cloudbase-cms');

export const getPost = async (id) =&gt; {
    const post = (await Article.where({ _id: id }).get()).data;
    const { name, avator } = (await Author
      .where({ _id: post.author })
      .get()).data;
    post.author = name;
    post.avator = dealWithUrl(avator);
    post.image = dealWithUrl(post.image);
    return post;
};
</code></pre>
<p>然后安装 remark 以及 remark-html 两个库,我们将用它们把 markdown 转化为 html,然后修改 .js 文件中的 getStaticProps 为</p>
<pre><code>import Head from 'next/head';
import Link from 'next/link';
import styles from '../../styles/Post.module.css';
import { getPost } from '../../lib/api';
import remark from 'remark'
import html from 'remark-html'

...

export async function getStaticProps({ params }) {
    const post = await getPost(params.id);

    const processedContent = await remark()
      .use(html)
      .process(post.content)
    post.contentHtml = processedContent.toString()

    return {
      props: post
    };
}
</code></pre>
<h3 id="拉取所有的文章id以渲染所有文章页面">拉取所有的文章id以渲染所有文章页面</h3>
<p>只到这一步还不够,我们需要知道所有的路由可能匹配到的 id 值,Next.js 才能渲染出全部的文章页面。.js 多出的 getStaticPaths 函数正是用来返回 id 所有可能的匹配值的。</p>
<p>这就是我们只需要编写一次拉取文章数据逻辑,编写一次文章页面 UI,就能让 Next.js 生成出无数文章的静态页面的奥秘。因为,可以让 Next.js 知道所有的文章 id,然后 Next.js 就能对每个文章页面执行一次生成了。</p>
<p>修改往 api.js 中添加获取所有文章 id 的函数:</p>
<pre><code>export const getAllPostId = async () =&gt; {
    let posts = (await Article.where({}).get()).data;
    return posts.map(value =&gt; ({
      params: { id: value._id }
    }))
};
</code></pre>
<p>然后修改 getStaticPaths 函数:</p>
<pre><code>export async function getStaticPaths() {
    return {
      paths: await getAllPostId(),
      fallback: false
    };
}
</code></pre>
<p>然后,访问首页,随便进入一篇文章,就可以看到如下效果:</p>
<p><img src="https://main.qcloudimg.com/raw/7b03c10bfbe4f819093f587d1950cb48.png"></p>
<p>到这里,我们就成功完成 Next.js 项目的构建啦!</p>
<h2 id="部署">部署</h2>
<p>使用腾讯云云开发,你可以轻易地将应用部署到公共网络上。</p>
<p>我们先修改 package.json 中依赖库的配置,因为云开发环境对于依赖版本有一定限制:</p>
<pre><code>"dependencies": {
    "@cloudbase/node-sdk": "^2.5.1",
    "next": "9.5.4",
    "react": "16.13.1",
    "react-dom": "16.13.1",
    "remark": "^13.0.0",
    "remark-html": "^13.0.1"
}
</code></pre>
<p>然后,创建部署的配置文件 cloudbaserc.json,并填入以下内容:</p>
<pre><code>{
    "envId": "{{env}}",
    "version": "2.0",
    "$schema": "https://framework-1258016615.tcloudbaseapp.com/schema/latest.json",
    "functionRoot": "./functions",
    "functions": [],
    "region": "ap-shanghai",
    "framework": {
      "name": "tcbcms-nextjs",
      "plugins": {
            "client": {
                "use": "@cloudbase/framework-plugin-next",
                "inputs": {}
            }
      }
    }
}
</code></pre>
<p>其中的 env 换成你刚刚创建的云环境的 ID。</p>
<p>修改完毕后,执行命令:</p>
<pre><code>cloudbase
</code></pre>
<p>可以看到部署流程启动,等待到部署完毕后,进入云环境的“我的应用”模块,会发现应用列表多了一个“tcbcms-nextjs”,点击访问键,就能访问刚刚创建的应用,而且是通过公网 IP,这说明我们的应用已经部署成功了。</p>
<h2 id="总结"><strong>总结</strong></h2>
<p>到此,我们的博客已经成功创建并部署了。以后如果博客中要添加新文章,或者要删改原有的文章,都只需要在 CMS 上进行内容的改动,然后在本地执行 Next.js 的构建和云开发部署即可。</p>
<p><img src="https://main.qcloudimg.com/raw/654289a55803b75b60e5c0ce1762ab0f.png"></p>
<p>更多 Next.js 和云开发相关知识,可以查看官方文档:</p>
<p>Next.js 官方文档:https://nextjs.org/docs/getting-started</p>
<p>CloudBase CMS 官方文档:https://docs.cloudbase.net/cms/intro.html</p>
<p>@cloudbase/node-sdk 官方文档:https://docs.cloudbase.net/api-reference/server/node-sdk/introduction.html</p>
<h2 id="彩蛋云开发是如何支撑应用开发的">彩蛋:云开发是如何支撑应用开发的?</h2>
<p>你可能会好奇云环境的能力是如何支撑我们的系统的。</p>
<p>再次进入腾讯云控制台,进入刚刚创建的云环境主页,进入HTTP访问服务、云数据库以及云函数模块,会发现多了许多东西。实际上,CMS 系统就是由这些东西支撑着的。</p>
<p>我们每次访问 CMS 系统并操作,都会经由 HTTP访问服务,导向某个云函数,在云函数中执行后台逻辑,而系统中的数据,也都存储在云数据库中,这也是我们可以通过 @cloudbase/node-sdk 访问云数据库得到 CMS 中的数据的原因。</p>
<p>而我们刚刚部署的 Next 应用,实际上也是运行在云函数上的。</p>
<p>如果有兴趣在云开发中更进一步,欢迎前往云开发社区官网 cloudbase.net 阅读文档,加入技术交流群,一起探索云开发的更多可能性。</p><br><br>
来源:https://www.cnblogs.com/yunkaifa0/p/14710060.html
頁: [1]
查看完整版本: CloudBase CMS + Next.js:轻松构建一个内容丰富的站点