浅谈搭建现代化静态博客
回顾过去搭建
第一次搭建是在 2020 年的假期,利用电脑搭建了基于Gridea构建的博客。用着一款很简约的主题Chic,成功的搭建了第一个自己的站点。但它有个很大的缺点:只能用本地的客户端,不支持手机,也不容易云端操作。所以说它并不满足我的需求。
之后我发现了一个很好用的程序,叫做Pandoc。
Pandoc is a free and open-source document converter, widely used as a writing tool (especially by scholars)[2] and as a basis for publishing workflows.[3] It was created by John MacFarlane, a philosophy professor at the University of California, Berkeley. — From WikiPedia.
它可以转换包括 PDF, MD, HTML, 甚至是Word等十几种格式,还拥有强大的自定义功能。它微不足道的小功能就满足我的博客需求了。我参考了三日月綾香的博客,写出了完全由自己设计的网页,发布了两款主题:Clean和Console。
除了这几个偏门的方案,还有一些很流行的静态博客框架,比如 Hexo, Hugo, Jekyll 等。然而已经有了这么多好的选择,我却没有用这些!
今年 2 二月份,我看到了日本的一位前端工程师 Takuya Matsuyama 用 NextJS 制作的的个人网站,了解了其搭建过程,这使我走向了使用 React 元框架的道路。
相比普通的博客网站,用 React 元框架和 NextJS 制作的网站更加先进,现代:
- 切换页面时网站只会加载新的未加载的内容,重复利用现有资源,减少了不必要的加载时间,提升了网站性能。
- 网站重载不需要刷新,通过比如 Framer-motion 实现动画效果,有着 Native 级的浏览体验
- React元框架有着丰富的生态链,可以快速实现比如动画,暗黑模式效果。NextJS 也使得支持更多获取数据的方式,比如 CMS(See NextJS Example),可以实现获取云端内容渲染成文章,即使是没有后端的静态博客!
现在来说说如何用 Nextjs 来制作博客吧!
搭建过程
搭建过程比较复杂,请看完我的所有步骤!
准备工作(终端环境中进行)
首先你需要安装 NodeJS 环境,Windows 用户可以用 chocolatey 通过命令安装,choco install nodejs yarn。
选择一个目录并初始化项目
yarn init -y
mkdir pages
mkdir components
mkdir lib
mkdir styles
mkdir _posts
touch next.config.js
touch .eslintrc.json
touch .prettierignore
touch prettier.config.js
yarn add @mdx-js/loader gray-matter next-mdx-remote next-compose-plugins rehype remark remark-html react react-dom next eslint eslint-config-next prettier
ESlint是用来语法检查的工具。 prettier 用来整理代码。
编写文件
编写 package.json
{
"name": "Willie-homepage",
"version": "1.1.0",
"description": "Willie's website",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"prettier": "prettier --write .",
"lint": "next lint"
},
"keywords": [],
"author": "Willie",
"license": "MIT",
"dependencies": {
"@mdx-js/loader": "latest",
"date-fns": "latest",
"gray-matter": "latest",
"next": "latest",
"next-compose-plugins": "latest",
"next-mdx-remote": "latest",
"react": "latest",
"react-dom": "latest",
"rehype": "latest",
"remark": "latest",
"remark-html": "latest"
},
"devDependencies": {
"eslint": "latest",
"eslint-config-next": "latest",
"prettier": "latest"
}
}
请将 name,author 等个人信息自行修改
编写 next.config.js
module.exports = {
reactStrictMode: true,
swcMinify: true,
};
编写 .eslintrc.json
{
"root": true,
"extends": "next",
"rules": {
"no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_"
}
],
"react/display-name": 0
}
}
编写 .prettierignore
node_modules
.next
编写 prettier.config.js
const options = {
arrowParens: "avoid",
singleQuote: true,
bracketSpacing: true,
endOfLine: "if",
semi: false,
tabwidth: 2,
trailingComma: "none",
};
创建自定义样式
在 styles/global.css 里将你所需要样式填写进去
创建获取文章内容的 JS
在 lib/mdx.js 里写入以下内容
import fs from "fs";
import matter from "gray-matter";
import { serialize } from "next-mdx-remote/serialize";
import path from "path";
const root = process.cwd();
export async function getFiles(type) {
return fs.readdirSync(path.join(root, "_posts", type));
}
export async function getFileBySlug(type, slug) {
const source = slug
? fs.readFileSync(path.join(root, "_posts", type, `${slug}.mdx`), "utf-8")
: fs.readFileSync(path.join(root, "_posts", `${type}.mdx`), "utf-8");
const { data: metaData, content } = matter(source);
const contentSerialized = await serialize(content);
return {
content,
contentSerialized,
slug: slug ? slug : null,
metaData,
};
}
export async function getAllFilesFrontMatter(type) {
const files = fs.readdirSync(path.join(root, "_posts", type));
return files.reduce((allPosts, postSlug) => {
const source = fs.readFileSync(
path.join(root, "_posts", type, postSlug),
"utf-8"
);
const { data } = matter(source);
return [
{
...data,
slug: postSlug.replace(".mdx", ""),
},
...allPosts,
];
}, []);
}
这段代码获取了文件名作为路径,用 front-matter 获取了 Markdown 文件的内容和媒体,感兴趣的可以在 NextJS 文档中找到相关介绍!
编写 pages/_app.js
import "../styles/global.css";
export default function App({ Component, pageProps }) {
return <Component {...pageProps} />;
}
编写 components/BlogPost/index.js
import NextLink from "next/link";
const BlogPost = ({ title, date, excerpt, slug }) => {
return (
<NextLink href={`/${slug}`} passHref>
<li>
<div className="postdate">
<span>{date}</span>
</div>
<h2>{title}</h2>
<p className="excerpt">{excerpt}</p>
</li>
</NextLink>
);
};
export default BlogPost;
该代码生成文章列表,根据自己需求编写。
导航栏 Nav 的例子,编写 components/Nav/index.js
const Nav = () => {
return (
<nav>
<h1 className="title">WLL‘s blog</h1>
<div>
<p id="icon">☽</p>
</div>
</nav>
);
};
export default Nav;
编写 pages/index.js
import BlogPost from "../components/BlogPost";
import { getAllFilesFrontMatter } from "../lib/mdx";
import Head from "next/head";
import Footer from "../components/Footer";
import Nav from "../components/Nav";
const Blog = ({ posts }) => {
const filteredBlogPosts = posts.sort(
(a, b) => Number(new Date(b.date)) - Number(new Date(a.date))
);
return (
<>
<Head>
<title>WLL‘s blog</title>
<meta name="description" content="WLL‘s blog" />
</Head>
<Nav />
<div className="post-list">
<h2>※ My Articles</h2>
<ul>
{filteredBlogPosts.map((post) => (
<BlogPost key={post.title} {...post} />
))}
</ul>
</div>
<Footer />
</>
);
};
export default Blog;
export const getStaticProps = async () => {
const posts = await getAllFilesFrontMatter("");
return {
props: {
posts,
},
};
};
代码中的 Footer 可以参考 Nav 的编写。
编写 pages/[slug].js
import { MDXRemote } from "next-mdx-remote";
import Head from "next/head";
import Footer from "../components/Footer";
import Nav from "../components/Nav";
import { getFileBySlug, getFiles } from "../lib/mdx";
const BlogPost = ({ post }) => {
const { metaData, contentSerialized } = post;
return (
<div>
<Head>
<title>{metaData.title}</title>
<meta name="description" content="{metaData.title}" />
</Head>
<Nav />
<header>
<div className="card">
<h1>{metaData.title}</h1>
<h3 className="date"> ∇ {metaData.date} </h3>
<p className="author"> ¶ Willie</p>
<div>
<p className="postexcerpt">{metaData.excerpt}</p>
</div>
</div>
</header>
<div className="post">
<MDXRemote compiledSource={contentSerialized.compiledSource} />
</div>
<Footer />
</div>
);
};
export default BlogPost;
export const getStaticPaths = async () => {
const posts = await getFiles("");
return {
paths: posts.map((p) => ({
params: {
slug: p.replace(/\.mdx/, ""),
},
})),
fallback: false,
};
};
export const getStaticProps = async ({ params }) => {
const slug = params?.slug;
if (slug) {
try {
const post = await getFileBySlug("", slug);
return {
props: {
post,
},
};
} catch (error) {
console.error(error);
}
}
return {
notFound: true,
};
};
这里是文章的界面!感谢看到这一步,至此已经大功告成了!
尝试编写文章
整理一下之前写的代码
yarn run prettier
在
_posts中编写以 .mdx 后缀的 Markdown 文件
例子
---
title: "Something About Me"
date: "2021-07-18"
excerpt: "About..."
---
# 关于我
还有很多要改善的地方!
# 站点
> 除非另有说明,本博客所有文章均采用`CC BY-NC-SA 4.0 许可协议`。 转载请在文中明显位置注明出处
现在用 yarn run dev 预览吧!
写这篇文章花费了很长时间,也希望网站搭建爱好者们可以参考本文,制作一个完全自己 DIY 的博客。可以参考这个博客!
—-未完待续
© Willie 2022.RSS