千金一刻莫空度,老大无成空自伤。这篇文章主要讲述使用 React 和 Next.js 构建博客相关的知识,希望能为你提供帮助。
Next.js
是由 Vercel 创建和维护的基于 React 的应用程序框架。本教程将从零开始学习如何使用 Next.js
构建一个小型的博客网站:
- 基本页面创建
- 从
Markdown
文件生成的动态路由 - 静态生成(在构建时渲染)
- 服务器端渲染(在请求时渲染)
Next.js 适合博客吗?
本教程将通过创建一个简单的博客来展示
Next.js
功能,那么 Next.js
适合这样的博客的开发吗?先来了解一下一般博客都需要什么?文章图片
- WordPress 是一个内容管理系统 (CMS),它为三分之一的网站提供支持,通过在每次请求时将可编辑的数据库内容渲染到 php 模板中来为页面提供服务。它非常适合定期更新的内容,但性能、安全性和数据备份需要一定的自定义设置。
- 静态站点生成器 (SSG),例如 Eleventy 或 Gatsby 创建预渲染文件,无需服务器端或数据库即可快速构建静态站点,在版本控制、性能和安全性都非常出色,但构建步骤和以开发人员为中心的过程可能会减慢发布速度,尤其是在大型网站上。
Next.js
是一个基于 React 的应用程序框架,它几乎没有特定于博客功能。但是,它可以提供了一种实现机制:- 在可能的情况下,
Next.js
生成静态内容,如 SSG,这些页面加载速度非常快,可以被搜索引擎快速收录,并且可以在任何有或没有 javascript 的设备上阅读。 - 在第一次加载后,
Next.js
应用程序的行为类似于单页应用程序 (SPA),后续页面和代码会以渐进式下载,无需刷新整页。 Next.js
为每个请求提供服务器端渲染 (SSR),为个人用户提供实时 CMS 更新或自定义内容变得更加容易。
Next.js
。开始
本教程正在构建的内容,可以在 GitHub 上找到完整的代码。可以通过在终端中输入以下命令,在
Windows
、macOS
或 Linux
上下载、安装和启动它:git clone git@github.com: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.js
和 React
作为依赖项: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 博客网站
<
p>
这个博客网站将使用 <
a rel="nofollow" href="https://nextjs.org/">
Next.js<
/a>
。
<
/p>
<
/>
);
要启动
Next.js
开发服务器,从项目根目录在终端中运行 npm run dev
(可以使用 npx next dev
),然后在浏览器中打开 http://localhost:3000/
:文章图片
Next.js
已经确定页面可以预渲染,所以它在开发模式下显示一个闪电图标。【使用 React 和 Next.js 构建博客】可以在自动路由的页面目录中创建类似的文件,如下:
pages/index.js
用于呈现博客主要pages/about.js
呈现一个/about
页面
&
lt;
a&
gt;
标签创建指向另一个页面的超链接。如果该页面位于同一个 Next.js
站点内,浏览器将会刷新整个页面。可以使用 next/link
中的 &
lt;
Link&
gt;
组件实现页面跳转。在根页面 /index.js
上创建指向 /about
页面的链接,代码如下:import Link from "next/link";
export default function Home()
return (
<
>
Next.js 博客网站
<
p>
这个博客网站将使用 <
a rel="nofollow" href="https://nextjs.org/">
Next.js<
/a>
。
<
/p>
<
p>
更多内容请点击" "
<
Link href="https://www.songbingjia.com/about">
<
a>
关于我们...<
/a>
<
/Link>
<
/p>
<
/>
);
当点击
关于我们...
链接时,Next.js
将使用 Ajax
请求下载 /about
的内容一次并缓存,然后再页面中呈现。增加 < head> 元素可以使用
next/head
中的 &
lt;
Head&
gt;
组件来更改页面标题和元标记,如下:import Head from "next/head";
import Link from "next/link";
export default function Home()
return (
<
>
<
Head>
<
title>
Next.js网站<
/title>
<
meta
name="description"
content="这是一个由 Next.js 驱动的网站"
/>
<
/Head>
Next.js 博客网站
<
p>
这个博客网站将使用 <
a rel="nofollow" href="https://nextjs.org/">
Next.js<
/a>
。
<
/p>
<
p>
更多内容请点击" "
<
Link href="https://www.songbingjia.com/about">
<
a>
关于我们...<
/a>
<
/Link>
<
/p>
<
/>
);
点击浏览器查看源代码,可以看到相关 HTML 标签。
增加静态资源
public
目录用于存放静态资源,如图标、robots.txt
或其它更新频率低的文件。可以增加自己的文件或从初始项目存储库复制 favicon.ico
和图像子目录。创建模板
Next.js
使用 React 组件来实现模板化,接下来项目根目录下创建一个新的 components
文件夹,然后添加 layout.js
来定义一个新的 &
lt;
Layout&
gt;
组件:import Header from "./header";
import Footer from "./footer";
export default function Layout( children, title )
return (
<
>
<
Header title=title />
<
main>
children<
/main>
<
Footer />
<
/>
);
任何使用此组件的页面都会传递一个
props
对象,该对象包含作为子值 children
的内容。&
lt;
Layout&
gt;
还将引用了另外两个组件,分别是component/header.js
中的 &
lt;
Header&
gt;
,主要呈现一个 &
lt;
header&
gt;
,包含主页链接、内联 SVG Logo和 默认为 /images/header.jpg
的图像:import Link from "next/link";
export default function Header( title )
const headerImg = "/images/" + (title || "header.jpg");
return (
<
header>
<
p className="logo">
<
Link href="https://www.songbingjia.com/">
<
a>
<
svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20">
<
path d="M10.394 2.08a1 1 0 00-.788 0l-7 3a1 1 0 000 1.84L5.25 8.051a.999.999 0 01.356-.257l4-1.714a1 1 0 11.788 1.838L7.667 9.088l1.94.831a1 1 0 00.787 0l7-3a1 1 0 000-1.838l-7-3zM3.31 9.397L5 10.12v4.102a8.969 8.969 0 00-1.05-.174 1 1 0 01-.89-.89 11.115 11.115 0 01.25-3.762zM9.3 16.573A9.026 9.026 0 007 14.935v-3.957l1.818.78a3 3 0 002.364 0l5.508-2.361a11.026 11.026 0 01.25 3.762 1 1 0 01-.89.89 8.968 8.968 0 00-5.35 2.524 1 1 0 01-1.4 0zM6 18a1 1 0 001-1v-2.065a8.935 8.935 0 00-2-.712V17a1 1 0 001 1z">
<
/path>
<
/svg>
Next.js 博客
<
/a>
<
/Link>
<
/p>
<
figure>
<
img
src=https://www.songbingjia.com/android/headerImg/>
<
/figure>
<
/header>
);
第二个组件是
component/footer.js
中的 &
lt;
Footer&
gt;
,呈现 &
lt;
footer&
gt;
内容,其中包含指向 GitHub 存储库和内联 SVG 的链接:export default function Footer()
return (
<
footer>
<
p className="github">
<
a rel="nofollow" href="https://github.com/QuintionTang/react-blog">
<
svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512">
<
path d="M256 32C132.3 32 32 134.9 32 261.7a229.3 229.3 0 00153.2 217.9 17.6 17.6 0 003.8.4c8.3 0 11.5-6.1 11.5-11.4l-.3-39.1a102.4 102.4 0 01-22.6 2.7c-43.1 0-52.9-33.5-52.9-33.5-10.2-26.5-24.9-33.6-24.9-33.6-19.5-13.7-.1-14.1 1.4-14.1h.1c22.5 2 34.3 23.8 34.3 23.8 11.2 19.6 26.2 25.1 39.6 25.1a63 63 0 0025.6-6c2-14.8 7.8-24.9 14.2-30.7-49.7-5.8-102-25.5-102-113.5 0-25.1 8.7-45.6 23-61.6a84.6 84.6 0 012.2-60.8 18.6 18.6 0 015-.5c8.1 0 26.4 3.1 56.6 24.1a208.2 208.2 0 01112.2 0c30.2-21 48.5-24.1 56.6-24.1a18.6 18.6 0 015 .5 84.6 84.6 0 012.2 60.8 90.3 90.3 0 0123 61.6c0 88.2-52.4 107.6-102.3 113.3 8 7.1 15.2 21.1 15.2 42.5 0 30.7-.3 55.5-.3 63 0 5.4 3.1 11.5 11.4 11.5a19.4 19.4 0 004-.4A229.2 229.2 0 00480 261.7C480 134.9 379.7 32 256 32z" />
<
/svg>
https://github.com/QuintionTang/react-blog
<
/a>
<
/p>
<
/footer>
);
接下来更新
pages/index.js
,将使用自定义的 &
lt;
Layout&
gt;
组件:import Layout from "../components/layout";
import Head from "next/head";
import Link from "next/link";
export default function Home()
return (
<
Layout>
<
Head>
<
title>
Next.js网站<
/title>
<
meta
name="description"
content="这是一个由 Next.js 驱动的网站"
/>
<
/Head>
Next.js 博客网站
<
p>
这个博客网站将使用 <
a rel="nofollow" href="https://nextjs.org/">
Next.js<
/a>
。
<
/p>
<
p>
更多内容请点击" "
<
Link href="https://www.songbingjia.com/about">
<
a>
关于我们...<
/a>
<
/Link>
<
/p>
<
/Layout>
);
然后对
pages/about.js
和创建的任何其他页面执行相同操作。import Layout from "../components/layout";
import Head from "next/head";
export default function Home()
return (
<
Layout title="share.png">
<
Head>
<
title>
关于我们<
/title>
<
/Head>
关于我们
<
p>
DevPoint 是 WEB
开发的分享中心,用自己的热情来分享互联网的点滴,以此激励自己加强学习提升自我。
<
/p>
<
/Layout>
);
更新后的效果如下:
文章图片
使用动态路由查看博客内容使用 JSX 创建内容并不是特别实用,尤其是对于常规博客文章,对于开发者比较喜欢 Markdown 的方式写博客。Next.js 可以使用任何来源创建页面。这些可以在构建时静态生成,并使用动态路由将数据映射到 URL。
在继续之前,先来创建一个文章目录,用于存放博客的 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-matter
、remark
和 remark-html
,执行一下命令:npm install front-matter remark remark-html --save
要读取和解析 Markdown 文件,需要添加相关逻辑,代码所在文件
libs/posts-md.js
。importpromises as fspfrom "fs";
import path from "path";
import fm from "front-matter";
importremarkfrom "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.songbingjia.com/android/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) =>
returnparams:id;
);
console.log(paths);
return
paths,
fallback: false,
;
接下来创建函数
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
<
p class="time">
<
time datetime="$postData.dateYMD">
$postData.dateFriendly<
/time>
<
/p>
<
p class="words">
$postData.wordcount<
/p>
$postData.html
`;
return (
<
Layout title="share.png">
<
Head>
<
title>
postData.title<
/title>
<
meta name="description" content=postData.description />
<
/Head>
<
article dangerouslySetInnerHTML= __html: html/>
<
/Layout>
);
创建博客列表页创建文件
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 (
<
article>
<
h2>
<
Link href=https://www.songbingjia.com/android/link>
<
a>
props.title<
/a>
<
/Link>
<
/h2>
<
p className="time">
发布时间:
<
time dateTime=props.datefriendly>
props.dateymd<
/time>
<
/p>
<
p>
props.description<
/p>
<
/article>
);
完成单个博客布局后,来看看博客列表页,代码如下:
importgetAllFilesfrom "../../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 (
<
Layout title="share.png">
<
Head>
<
title>
博客列表<
/title>
<
meta
name="description"
content="A list of articles published on this site."
/>
<
/Head>
博客列表<
aside className="pagelist">
postData.map((post) =>
(
<
Pagelink
key=post.id
postsdir=postsDir
id=post.id
title=post.title
description=post.description
dateymd=post.dateYMD
datefriendly=post.dateFriendly
/>
))
<
/aside>
<
/Layout>
);
/**
* 获取所有文章文章的数组
* @returns
*/
export async function getStaticProps()
return
props:
postData: await getAllFiles(postsDir),
,
;
创建导航一个完整的博客站点,需要一个导航菜单,方便内容切换。接下来创建一个导航组件,创建文件
components/navs.js
,导出一个 &
lt;
Navs&
gt;
组件,代码如下:importuseRouterfrom "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 (
<
nav>
<
ul>
menu.map((item) =>
(
<
NavItem
key=item.link
text=item.text
link=item.link
currentpage=currentPage
/>
))
<
/ul>
<
/nav>
);
function NavItem( text, link, currentpage )
if (link === currentpage)
return (
<
li className="active">
<
strong>
text<
/strong>
<
/li>
);
else
return (
<
li>
<
Link href=https://www.songbingjia.com/android/link>
<
a>
text<
/a>
<
/Link>
<
/li>
);
下面将
Navs
组件加入到组件 Header 中,代码如下:import Link from "next/link";
import Navs from "./navs";
// 导航菜单export default function Header( title )
const headerImg = "/images/" + (title || "cover.png");
return (
<
header>
<
p className="logo">
<
Link href="https://www.songbingjia.com/">
<
a>
<
svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20">
<
path d="M10.394 2.08a1 1 0 00-.788 0l-7 3a1 1 0 000 1.84L5.25 8.051a.999.999 0 01.356-.257l4-1.714a1 1 0 11.788 1.838L7.667 9.088l1.94.831a1 1 0 00.787 0l7-3a1 1 0 000-1.838l-7-3zM3.31 9.397L5 10.12v4.102a8.969 8.969 0 00-1.05-.174 1 1 0 01-.89-.89 11.115 11.115 0 01.25-3.762zM9.3 16.573A9.026 9.026 0 007 14.935v-3.957l1.818.78a3 3 0 002.364 0l5.508-2.361a11.026 11.026 0 01.25 3.762 1 1 0 01-.89.89 8.968 8.968 0 00-5.35 2.524 1 1 0 01-1.4 0zM6 18a1 1 0 001-1v-2.065a8.935 8.935 0 00-2-.712V17a1 1 0 001 1z">
<
/path>
<
/svg>
Next.js 博客
<
/a>
<
/Link>
<
/p>
<
Navs />
<
figure>
<
img src=https://www.songbingjia.com/android/headerImg/>
<
/figure>
<
/header>
);
到此一个简单的博客站点功能已经实现。
添加样式Next.js 提供了一系列样式选项,包括 Sass、Less、PostCSS、Styled JSX、CSS 模块和普通的CSS。 Sass 易于使用,因为不需要任何配置,按照依赖:
npm install sass --save
根目录下创建文件夹
styles
,所有的样式文件都放在这个文件夹下。样式的入口未见为 master.scss
。然后在文件夹
pages
下创建文件 _app.js
,将样式文件导入,完整代码如下:import "../styles/master.scss";
export default function App( Component, pageProps )
return <
Component ...pageProps />
;
最终效果如下:
文章图片
文章图片
文章图片
推荐阅读
- 微服务架构 | *2.4 Nacos 获取配置与事件订阅机制的源码分析
- 浅谈全方位查找
- 生产环境定时任务解注释
- Zabbix深度应用之NMap端口探测
- Windows Server Core 2022--安装PPTP/L2TP
- 简单的ctime,atime和mtime区别说明
- kubernetes 部署 traefik2.5
- (服务运维)NFS和LAP组合实验
- 如何开发基于云的SaaS应用程序()