少年,看到大佬们的个人博客,是否渴望拥有自己的博客?别担心,next会出手!
背景
这段时间有去看大佬们的博客,看到的有大佬们对各种知识的理解,有大佬们积极乐观的人生态度和多彩的生活,博客不只是博客,还是大佬们的内心世界。作为一只momo小兵,俺也想构建俺滴一亩三分地——momo世界
。
作为博客,我希望他能够尽量轻便,难道还得去特地进行前后端分离吗?不,不行,太累了~
那有什么好的解决方案?这阵子不是流行一个next
吗,走,咱瞧瞧!
不看不知道,一看吓一跳!next
在掘金的文章早在4年前就有,很多公司也在自己的项目中对next
进行落地。
围绕着快速开发
的原则,momo世界
使用next
+tailwindcss
+daisyui
+gray-matter
+next-mdx-remote
+Docker
进行构建部署。在开发过程next
和tailwindcss
的带来了很多方便,例如md获取、例如媒体查询适配。
要点解析
文章解析和渲染
大伙都喜欢使用markdown
记录笔记,而从掘金搬运获取,获取的也是md
格式的文章,因此,我的需求是解析md并添加相关参数
。为了想要添加更多的参数,我使用了mdx
作为文件名,实际上mdx
非常强大。mdx
官网是这么说的:
得益于next
我们可以在项目中使用node
相关模块的服务,例如在这我们就可以使用fs
对文章进行获取:
import path from "path";
import fs from "fs";
import matter from "gray-matter";
import { serialize } from "next-mdx-remote/serialize";
import rehypeHighlight from "rehype-highlight/lib";
// 定义文章信息
export interface FileSketch {
id: string;
title: string;
date: string;
filePath: string;
description: string;
tags: Array<string>;
cover:string;
[key: string]: any;
}
// 文件路径
export const postPath = path.join(process.cwd(), "/posts/computer");
// 获取全部文件信息
export const findMdxFiles = () => {
// 获取文件信息的函数
const findFileDetail = (
dir = postPath,
fileList = [] as Array<string>,
sketchList = [] as Array<FileSketch>
) => {
const files = fs.readdirSync(dir);
files.forEach((file) => {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
// 递归处理子目录
findFileDetail(filePath, fileList, sketchList);
} else if (path.extname(file) === ".mdx") {
// 找到 .mdx 文件,将其进行解析
try {
// 借助gray-matter解析mdx文件的二进制流
const { data } = matter(fs.readFileSync(filePath, "utf-8"));
sketchList.push({ ...data, filePath } as FileSketch);
} catch (e) {
// 埋点
}
fileList.push(filePath);
}
});
return sketchList;
};
// 返回文件信息
return findFileDetail();
};
// 寻找对应id的文章
export const getFileById = async (id: string) => {
const [targetBlog] = findMdxFiles().filter((blog) => blog.id === id);
const fileContent = fs.readFileSync(targetBlog.filePath, "utf-8");
const { data: frontmatter, content } = matter(fileContent);
// 读取文章
const mdxSource = await serialize(content, {
mdxOptions: {
// 代码主题设置
rehypePlugins: [rehypeHighlight],
},
});
return {
frontmatter,
mdxSource,
id,
};
};
每次的生成,都会执行一次findMdxFiles
方法,虽然查询的数量很少,虽然node
对io
是友好的,但是不优化说不过去。从代码上,可以维护一个文章盒子,对外进行交流,具体实现这里不延申:
对于文章的渲染来说,next-mdx-remote
可以对返回的mdx
数据进行处理:
import React from "react";
import { MDXRemoteSerializeResult, MDXRemote } from "next-mdx-remote";
import Image from "next/image";
type BlogContentType = {
source: MDXRemoteSerializeResult;
};
// 我们可以根据自己爱好进行自定义样式,但是daisyui也给我们开启了排版服务,因此代码量显著减少。
const components = {
img: (props: any) => (
<Image
alt={props.alt}
width={150}
height={50}
layout="responsive"
{...props}
/>
),
};
export default function BlogContent({ source }: BlogContentType) {
return (
<article className="w-full mx-auto my-5 prose lg:prose-xl">
// source就是上面经过文章id获取文章后的信息,component是自定义组件
{source && <MDXRemote {...source} components={components}></MDXRemote>}
</article>
);
}
更新、添加文章肿么办
next
给我们提供了多种渲染方式,对于博客项目ssg
也许是合适的。ssg
在build
的过程中就对页面进行了内容生成。那么我们如果想要更新文章、添加文章,肿么办?答案很简单,看官网,设置参数,开启ISR
!
可以通过使用getStaticProps
开启ssg
,在动态路由文件中需要使用getStaticPaths
获取需要生成的的路由。
getStaticProps
的fallback
属性有3种返回值类型:
- false:仅仅会动态生成
getStaticPaths
给出的路径代表的页面,如果访问的路径不在给定范围内,就显示404界面 - true:会动态生成
getStaticPaths
给出的路径代表的页面,如果访问其他路径,会拿到显示fallback版本
,再进行页面生成 - blocking:会动态生成
getStaticPaths
给出的路径代表的页面,如果访问其他路径,会有加载的loading
,再进行页面生成
想要添加文章,只需要借助docker共享数据卷,共享到上文的文章获取的目录即可!
开启ISR
,设置参数revalidate
,值为时间,当到达时间间隔的时候,重新生成页面。
import { getAllId, getFileById } from "@/utils/mdxUtil";
import { serializeUtil } from "@/utils";
import React from "react";
import SingleBlog from "@/component/Blogs/SingleBlog";
import { GetStaticPaths, GetStaticProps, InferGetStaticPropsType } from "next";
import { useRouter } from "next/router";
export default function SingBlog({
source,
formatter,
}: InferGetStaticPropsType<GetStaticProps>) {
const router = useRouter()
if(router.isFallback){
return <div>
加载中!
</div>
}
return <SingleBlog source={source} formatter={formatter}></SingleBlog>;
}
export const getStaticPaths: GetStaticPaths = async () => {
const paths = getAllId().map((slug) => {
return {
params: {
slug: slug,
},
};
});
return {
paths: paths,
// 设置fallback的参数进行生成
fallback: true,
};
};
export const getStaticProps: GetStaticProps = async (context) => {
const { params } = context || { slug: "" };
const { frontmatter, mdxSource } = await getFileById(params?.slug as string);
return {
props: {
source: serializeUtil(mdxSource),
formatter: serializeUtil(frontmatter),
},
// 开启ISR,如果有更新20s后刷新将会得到改动的效果
revalidate:20
};
};
部署注意
部署可以使用官网给出的Dockerfile
部署,需要注意的是,官网不会cv自定义的next.config
到image
内,而我们有自定义的next.config
,因此,需要解开注释。
想要加速,这里安利阿里云的镜像服务,可以自行注册并在Docker daemon
中配置,嘎嘎快!
FROM node:alpine AS deps might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
FROM node:alpine AS builder
WORKDIR /app
COPY . .
COPY --from=deps /app/node_modules ./node_modules
RUN yarn build && yarn install --production --ignore-scripts --prefer-offline
FROM node:alpine AS runner
WORKDIR /app
ENV NODE_ENV production
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# 下面第一行需要注意,官网这里是直接注释的,但是我们需要用到我们自定义的next.config,因此需要解开
COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node_modules/.bin/next", "start"]
效果图展示
/
首页展示:
/about
关于页面:
/blogs
文章列表:
/blogs/[slug]
具体文章:
最后
感谢某大佬的ui布局参考!作为设计大佬,还使用next graphql
并写出那么优雅的代码,在技术上实在是值得致敬!
加油,动起来,create your world! Glad to say hello world