【momo·实战指南】让next构造你的世界

218 阅读5分钟

少年,看到大佬们的个人博客,是否渴望拥有自己的博客?别担心,next会出手!

背景

  这段时间有去看大佬们的博客,看到的有大佬们对各种知识的理解,有大佬们积极乐观的人生态度和多彩的生活,博客不只是博客,还是大佬们的内心世界。作为一只momo小兵,俺也想构建俺滴一亩三分地——momo世界

  作为博客,我希望他能够尽量轻便,难道还得去特地进行前后端分离吗?不,不行,太累了~

  那有什么好的解决方案?这阵子不是流行一个next吗,走,咱瞧瞧!

  不看不知道,一看吓一跳!next在掘金的文章早在4年前就有,很多公司也在自己的项目中对next进行落地。

  围绕着快速开发的原则,momo世界使用next+tailwindcss+daisyui+gray-matter+next-mdx-remote+Docker进行构建部署。在开发过程nexttailwindcss的带来了很多方便,例如md获取、例如媒体查询适配。

要点解析

文章解析和渲染

大伙都喜欢使用markdown记录笔记,而从掘金搬运获取,获取的也是md格式的文章,因此,我的需求是解析md并添加相关参数。为了想要添加更多的参数,我使用了mdx作为文件名,实际上mdx非常强大。mdx官网是这么说的: image.png 得益于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方法,虽然查询的数量很少,虽然nodeio是友好的,但是不优化说不过去。从代码上,可以维护一个文章盒子,对外进行交流,具体实现这里不延申:

image.png

对于文章的渲染来说,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也许是合适的。ssgbuild的过程中就对页面进行了内容生成。那么我们如果想要更新文章、添加文章,肿么办?答案很简单,看官网,设置参数,开启ISR

可以通过使用getStaticProps开启ssg,在动态路由文件中需要使用getStaticPaths获取需要生成的的路由。

getStaticPropsfallback属性有3种返回值类型:

  • false:仅仅会动态生成getStaticPaths给出的路径代表的页面,如果访问的路径不在给定范围内,就显示404界面
  • true:会动态生成getStaticPaths给出的路径代表的页面,如果访问其他路径,会拿到显示fallback版本,再进行页面生成
  • blocking:会动态生成getStaticPaths给出的路径代表的页面,如果访问其他路径,会有加载的loading,再进行页面生成

想要添加文章,只需要借助docker共享数据卷,共享到上文的文章获取的目录即可! image.png

开启ISR,设置参数revalidate,值为时间,当到达时间间隔的时候,重新生成页面。 image.png

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.configimage内,而我们有自定义的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"]

效果图展示

/首页展示: image.png image.png

image.png

/about关于页面: image.png

image.png

/blogs文章列表: image.png

image.png

/blogs/[slug]具体文章: image.png

image.png

最后

  感谢某大佬的ui布局参考!作为设计大佬,还使用next graphql并写出那么优雅的代码,在技术上实在是值得致敬!

  加油,动起来,create your world! Glad to say hello world image.png