使用|使用 React 和 Next.js 构建博客

Next.js是由 Vercel 创建和维护的基于 React 的应用程序框架。本教程将从零开始学习如何使用 Next.js构建一个小型的博客网站:

  • 基本页面创建
  • Markdown文件生成的动态路由
  • 静态生成(在构建时渲染)
  • 服务器端渲染(在请求时渲染)
文章涉及的代码仓库地址: https://github.com/QuintionTang/react-blog
Next.js 适合博客吗?
本教程将通过创建一个简单的博客来展示 Next.js功能,那么 Next.js适合这样的博客的开发吗?先来了解一下一般博客都需要什么?
使用|使用 React 和 Next.js 构建博客
文章图片

  • WordPress是一个内容管理系统 (CMS),它为三分之一的网站提供支持,通过在每次请求时将可编辑的数据库内容渲染到 PHP 模板中来为页面提供服务。它非常适合定期更新的内容,但性能、安全性和数据备份需要一定的自定义设置。
  • 静态站点生成器 (SSG),例如 Eleventy或 Gatsby创建预渲染文件,无需服务器端或数据库即可快速构建静态站点,在版本控制、性能和安全性都非常出色,但构建步骤和以开发人员为中心的过程可能会减慢发布速度,尤其是在大型网站上。
Next.js是一个基于 React 的应用程序框架,它几乎没有特定于博客功能。但是,它可以提供了一种实现机制:
  1. 在可能的情况下, Next.js生成静态内容,如 SSG,这些页面加载速度非常快,可以被搜索引擎快速收录,并且可以在任何有或没有 JavaScript 的设备上阅读。
  2. 在第一次加载后, Next.js应用程序的行为类似于单页应用程序 (SPA),后续页面和代码会以渐进式下载,无需刷新整页。
  3. Next.js为每个请求提供服务器端渲染 (SSR),为个人用户提供实时 CMS 更新或自定义内容变得更加容易。
如果网站可能会从基本博客迭代为更复杂的网站,例如在线商店、新闻聚合服务、社交媒体平台等,可以考虑使用 Next.js
开始
本教程正在构建的内容,可以在 GitHub 上找到完整的代码。可以通过在终端中输入以下命令,在 WindowsmacOSLinux上下载、安装和启动它:
git clone [email protected]:QuintionTang/react-blog.git cd react-blog npm i npm run dev

然后在浏览器中输入 localhost:3000打开主页。
从头开始构建 Next.js提供了一个 create-next-app工具来快速开始使用应用程序模板。本教程将展示如何从头开始构建站点:如添加静态资源或者页面。
安装 Next.js 和 React 与其他 Node.js或者 VUE 项目一样,首先创建一个目录并初始化 package.json文件:
mkdir react-blog cd react-blog npm init

然后安装 Next.jsReact作为依赖项:
npm install next react react-dom --save

添加开发构建脚本设置,如下所示,在 package.json文件的 scripts属性中添加:
"scripts": { "dev": "next dev", "build": "next build", "start": "next start" }

创建第一个页面 Next.js有一个基于文件系统的路由器。在项目的 pages目录中创建的任何 React 组件文件都会自动呈现为一个页面。
要创建一个页面,需要在 pages目录中添加一个 index.js文件。将以下代码添加到 ./pages/index.js文件中,返回 JSX代码的功能性 React 组件:
export default function Home() { return ( <> Next.js 博客网站这个博客网站将使用 Next.js。
); }

JSX 必须在单个包含元素(例如 < div> )中返回。 < > ... < /> 表示法定义了一个文档片段,因此不需要额外的容器。
要启动 Next.js开发服务器,从项目根目录在终端中运行 npm run dev(可以使用 npx next dev),然后在浏览器中打开 http://localhost:3000/
使用|使用 React 和 Next.js 构建博客
文章图片

Next.js已经确定页面可以预渲染,所以它在开发模式下显示一个 闪电图标。
可以在自动路由的页面目录中创建类似的文件,如下:
  • pages/index.js用于呈现博客主要
  • pages/about.js呈现一个 /about页面
增加链接 在 JSX 代码中使用标准 HTML < a> 标签创建指向另一个页面的超链接。如果该页面位于同一个 Next.js站点内,浏览器将会刷新整个页面。可以使用 next/link中的 < Link> 组件实现页面跳转。在根页面 /index.js上创建指向 /about页面的链接,代码如下:
import Link from "next/link"; export default function Home() { return ( <> Next.js 博客网站这个博客网站将使用 Next.js。
更多内容请点击{" "} 关于我们...
); }

当点击 关于我们...链接时, Next.js将使用 Ajax请求下载 /about的内容一次并缓存,然后再页面中呈现。
增加元素 可以使用 next/head中的 < Head> 组件来更改页面标题和元标记,如下:
import Head from "next/head"; import Link from "next/link"; export default function Home() { return ( <>Next.js网站 - 锐客网 Next.js 博客网站这个博客网站将使用 Next.js。
更多内容请点击{" "} 关于我们...
); }

点击浏览器查看源代码,可以看到相关 HTML 标签。
增加静态资源 public目录用于存放静态资源,如图标、 robots.txt或其它更新频率低的文件。可以增加自己的文件或从初始项目存储库复制 favicon.ico和图像子目录。
创建模板 Next.js使用 React 组件来实现模板化,接下来项目根目录下创建一个新的 components文件夹,然后添加 layout.js来定义一个新的 < Layout> 组件:
import Header from "./header"; import Footer from "./footer"; export default function Layout({ children, title }) { return ( <>
{children}
); }

任何使用此组件的页面都会传递一个 props对象,该对象包含作为子值 children的内容。 < Layout> 还将引用了另外两个组件,分别是 component/header.js中的 < Header> ,主要呈现一个 < header> ,包含主页链接、内联 SVG Logo和 默认为 /images/header.jpg的图像:
import Link from "next/link"; export default function Header({ title }) { const headerImg = "/images/" + (title || "header.jpg"); return (Next.js 博客
使用|使用 React 和 Next.js 构建博客
文章图片

使用动态路由查看博客内容 使用 JSX 创建内容并不是特别实用,尤其是对于常规博客文章,对于开发者比较喜欢 Markdown 的方式写博客。Next.js 可以使用任何来源创建页面。这些可以在构建时静态生成,并使用动态路由将数据映射到 URL。
【使用|使用 React 和 Next.js 构建博客】在继续之前,先来创建一个文章目录,用于存放博客的 Markdown 文件。例如: articles/article-01.md
--- title: 使用 React 和 Next.js 构建博客 description: Next.js 是由 Vercel 创建和维护的基于 React 的应用程序框架。本教程将从零开始学习如何使用 Next.js 构建一个小型的博客网站。 date: 2022-01-22 tags: - HTML - CSS - REACT ---使用 React 和 Next.js 构建博客## 摘要Next.js 是由 Vercel 创建和维护的基于 React 的应用程序框架。本教程将从零开始学习如何使用 Next.js 构建一个小型的博客网站。本教程将通过创建一个简单的博客来展示 Next.js 功能,那么 Next.js 适合这样的博客的开发吗?先来了解一下一般博客都需要什么?-WordPress 是一个内容管理系统 (CMS),它为三分之一的网站提供支持,通过在每次请求时将可编辑的数据库内容渲染到 PHP 模板中来为页面提供服务。它非常适合定期更新的内容,但性能、安全性和数据备份需要一定的自定义设置。 -静态站点生成器 (SSG),例如 Eleventy 或 Gatsby 创建预渲染文件,无需服务器端或数据库即可快速构建静态站点,在版本控制、性能和安全性都非常出色,但构建步骤和以开发人员为中心的过程可能会减慢发布速度,尤其是在大型网站上。Next.js 是一个基于 React 的应用程序框架,它几乎没有特定于博客功能。但是,它可以提供了一种实现机制:1. 在可能的情况下,Next.js 生成静态内容,如 SSG,这些页面加载速度非常快,可以被搜索引擎快速收录,并且可以在任何有或没有 JavaScript 的设备上阅读。 2. 在第一次加载后,Next.js 应用程序的行为类似于单页应用程序 (SPA),后续页面和代码会以渐进式下载,无需刷新整页。 3. Next.js 为每个请求提供服务器端渲染 (SSR),为个人用户提供实时 CMS 更新或自定义内容变得更加容易。 如果网站可能会从基本博客迭代为更复杂的网站,例如在线商店、新闻聚合服务、社交媒体平台等,可以考虑使用 Next.js。

内容的模板以 ---来定义博客的标题、发布时间等元数据, ---后面的为博客的正文。接下来需要安装解析内容的依赖,包括: front-matterremarkremark-html,执行一下命令:
npm install front-matter remark remark-html --save

要读取和解析 Markdown 文件,需要添加相关逻辑,代码所在文件 libs/posts-md.js
import { promises as fsp } from "fs"; import path from "path"; import fm from "front-matter"; import { remark } from "remark"; import remarkhtml from "remark-html"; import * as dateformat from "./dateformat"; const fileExt = "md"; // 获取文件夹相对路径 function absPath(dir) { return path.isAbsolute(dir) ? dir : path.resolve(process.cwd(), dir); }/** * 获取文件夹中 Markdown 文件名列表,以数组形式返回 * @param {*} dir * @returns */ export async function getFileIds(dir = "./") { const loc = absPath(dir); const files = await fsp.readdir(loc); return files .filter((fn) => path.extname(fn) === `.${fileExt}`) .map((fn) => path.basename(fn, path.extname(fn))); }/** * 获取单个 Markdown 文件的内容 * @param {*} dir * @param {*} id * @returns */ export async function getFileData(dir = "./", id) { const file = path.join(absPath(dir), `${id}.${fileExt}`), stat = await fsp.stat(file), data = https://www.it610.com/article/await fsp.readFile(file,"utf8"), matter = fm(data), html = (await remark().use(remarkhtml).process(matter.body)).toString(); // 日期格式化 const date = matter.attributes.date || stat.ctime; matter.attributes.date = date.toUTCString(); matter.attributes.dateYMD = dateformat.ymd(date); matter.attributes.dateFriendly = dateformat.friendly(date); // 计数 const roundTo = 10, readPerMin = 200, numFormat = new Intl.NumberFormat("en"), count = matter.body .replace(/\W/g, " ") .replace(/\s+/g, " ") .split(" ").length, words = Math.ceil(count / roundTo) * roundTo, mins = Math.ceil(count / readPerMin); matter.attributes.wordcount = `${numFormat.format( words )} words, ${numFormat.format(mins)}-minute read`; return { id, html, ...matter.attributes, }; }

以上代码涉及日期格式化代码,文件路径 libs/dateformat.js
// 时间格式化 const toMonth = new Intl.DateTimeFormat("en", { month: "long" }); // 格式化为 YYYY-MM-DD export function ymd(date) { return date instanceof Date ? `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart( 2, "0" )}-${String(date.getUTCDate()).padStart(2, "0")}` : ""; }// 格式化为 DD MMMM, YYYY export function friendly(date) { return date instanceof Date ? `${date.getUTCDate()} ${toMonth.format( date )}, ${date.getUTCFullYear()}` : ""; }

Next.js 使用包含在 [id ]中的标识符的文件名来识别动态(生成)路由。创建一个名为 pages/articles/[id].js的文件: Next.js将使用 id作为参数在 /articles/article-01等路由处生成页面,即博客的详情页路由。
pages/articles/[id].js中定义函数 getStaticPaths,该函数返回构建时要呈现的路径信息。
/** * 获取博客路径信息 * @returns [ { params: { id: 'article-01' } } ] */ export async function getStaticPaths() { const paths = (await getFileIds(postsDir)).map((id) => { return { params: { id } }; }); console.log(paths); return { paths, fallback: false, }; }

设置 fallback: false会在找不到路径时出现 404 页面。
接下来创建函数 getStaticProps,函数在构建时获取特定 ID 的数据以进行静态生成。它在 params对象中传递了一个 id属性,调用 libs/posts-md.js中的 getFileData()函数解析 Markdown 文件。
/** * 解析路由获取详细内容 * @param {*} param0 * @returns */ export async function getStaticProps({ params }) { return { props: { postData: await getFileData(postsDir, params.id), }, }; }

pages/articles/[id].js除了解析博客内容外,还需将内容导出为一个 React 组件,组件将 postData渲染到前面创建的模板中:
export default function Article({ postData }) { // 解析markdown内容 const html = ` ${postData.title}
${postData.wordcount}
${postData.html} `; return ( {postData.title} - 锐客网 ); }

dangerouslySetInnerHTML属性确保 HTML 不被编码。
创建博客列表页 创建文件 pages/articles/index.js,这个页面需要实现的功能是解析博客列表,并返回为一个 React 组件。在实现这个页面功能之前,先来创建一个链接组件 Pagelink
Pagelink组件实现博客列表中单篇博客的布局,创建文件 components/pagelink.js,代码如下:
import Link from "next/link"; export default function Pagelink(props) { const link = `/${props.postsdir}/${props.id}`; return ( {props.title} 发布时间:
{props.description}
); }

完成单个博客布局后,来看看博客列表页,代码如下:
import { getAllFiles } from "../../libs/posts-md"; import Layout from "../../components/layout"; import Pagelink from "../../components/pagelink"; import Head from "next/head"; const postsDir = "articles"; export default function ArticleIndex({ postData }) { return ( 博客列表 - 锐客网 博客列表{postData.map((post) => ())} ); }/** * 获取所有文章文章的数组 * @returns */ export async function getStaticProps() { return { props: { postData: await getAllFiles(postsDir), }, }; }

创建导航 一个完整的博客站点,需要一个导航菜单,方便内容切换。接下来创建一个导航组件,创建文件 components/navs.js,导出一个 < Navs> 组件,代码如下:
import { useRouter } from "next/router"; import Link from "next/link"; // menu name and link const menu = [ { text: "网站首页", link: "/" }, { text: "关于我们", link: "/about" }, { text: "博客列表", link: "/articles" }, ]; export default function Navs() { const router = useRouter(), currentPage = router.pathname; return ( ); }function NavItem({ text, link, currentpage }) { if (link === currentpage) { return (
  • {text}
  • ); } else { return (
  • {text}
  • ); } }

    下面将 Navs组件加入到组件 Header 中,代码如下:
    import Link from "next/link"; import Navs from "./navs"; // 导航菜单export default function Header({ title }) { const headerImg = "/images/" + (title || "cover.png"); return (Next.js 博客
    使用|使用 React 和 Next.js 构建博客
    文章图片

    使用|使用 React 和 Next.js 构建博客
    文章图片

    使用|使用 React 和 Next.js 构建博客
    文章图片

      推荐阅读