• Git address
  • Online address

This is my third time refactoring a blog, and while blog apps are the easiest, why not start by refactoring a blog?

  • First edition: Use Hexo and Github Pages

    • Pros: Redeployment takes 5 minutes, content management is purely static and free locally;

    • Disadvantages: Dependent on Github, difficult to access in China;

  • React + Antd + Mysql server is the minimum configuration of Alicloud ESC

    • 1. Perceived to have no merit;

    • Disadvantages: Browser rendering, search engine can not include ESO optimization difficult, Antd component is easy to use, but the front page customization needs to cover style;

  • NextJS + TailwindCSS + Postgresql

    • Advantages: Server rendering (SSR) + static generation, access speed is very fast, new UI support skin;

TailwindCSS

TailwindCSS is more than just a super style library for atomic classes to me;

When we write styles, we often write class names. There may be style conflicts between team members. Although we can use CSS modules to avoid this, there will be fatigue in picking class names.

2, utility-first: rem unit is used by default, the variable is a multiple of 16, pX-1 is 1/4 of 16, that is 4 px, we will not write 13px, 17px and other inconsistent unit variables, just as the so-called miss is as good as a mile. With the VScode plug-in, we can follow the prompts to see the actual unit values in real time, write a highly restored high fidelity style;

Grid-cols -[1fr,700px,2fr] grid-template-columns [1FR,700px,2fr] 1fr 700px 2fr; H -[calc(1000px-4rem)] and so on, these are run-time generated styles; With the addition of purge: [‘./ SRC /**/*.{js,ts, JSX, TSX}’] to tailwind.config.js will only extract the styles used when packaging, allowing CSS to be minimized.

4. I wrote “Using CSS Variables and Tailwind CSS to achieve theme skin” and used it in my blog.

Next.js

Next. Js is a React server rendering framework. Compared to react single-page applications, web crawlers can recognize HTML semantic tags, which is better for SEO.

Next, the main NextJS API:

GetServerSideProps Server render

Here is the simplest client-side rendering code

import React, { ReactElement, useEffect, useState } from 'react'
import { useParams } from "react-router-dom";

export default function Post() :ReactElement {
    let { slug } = useParams();
    const [post, setPost] = useState({
        title:' '.content:' '
    })
    useEffect(() = > {
        fetch(`/api/post/${slug}`).then((res) = >res.json()).then((res) = >{
            setPost(res)
        })
    }, [])
    return (
        <>
            <h1>{post.title}</h1>
            <div dangerouslySetInnerHTML={{
                __html:post.content}} ></div>  
        </>)}Copy the code

The code for NextJS looks like this

// pgaes/blog/[slug].tsx
import React, { ReactElement } from 'react'

export default function Post({ post }) :ReactElement {
    return (
        <>
            <h1>{post.title}</h1>
            <div dangerouslySetInnerHTML={{
                __html:post.content}} ></div>  
        </>)}export async function getServerSideProps(context) {
  const { slug }=context.params
  const res = await fetch(`https://... /api/post/${slug}`)
  const post = await res.json()

  return {
    props: {
       post
    },
  }
}
Copy the code

GetServerSideProps is handled on the Node side, on each request.

The content of the article is usually the same after it is written, so you can store the page statically on the server first, which greatly reduces the database stress.

GetStaticProps requests data at build time.

export async function getStaticProps(context) {
  // fetch data
  return {
    props: {
        //data}}},Copy the code

You need to get the full list of articles at build time, and the blog detail page is a dynamic route, so you need the getStaticPaths API

Get the data for dynamic routes when getStaticPaths is built

export async function async getStaticPaths() {
   const slugs= await getAllSlugs()
  return {
    paths: slugs.map(slug= >({
        params:slug
    })),
    fallback: true //or false
  };
}
Copy the code

When the site is built and new articles need to generate static pages, you can set fallback to true, and if false, all articles outside the build will return 404 pages.

Below is the body code for the article detail page

// pages/posts/[slug].js
import { useRouter } from 'next/router'

function Post({ post }) {
  const router = useRouter()

  // If the page is not statically generated, the following loading is displayed first
  // Until 'getStaticProps()' is run
  if (router.isFallback) {
    return <div>Loading...</div>
  }

  // Render post...
}

// Run at build time to get the entire article path
export async function getStaticPaths() {
  return {
    // Generate static pages for '/posts/1' and '/posts/2' at package time
    paths: [{ params: { id: '1'}}, {params: { id: '2'}}].// Enable static generation of other pages
    // For example: `/posts/3`
    fallback: true,}}// Run at build time to get article details based on the ID in Params
export async function getStaticProps({ params }) {
  // If the route to the page is /posts/1, the params.id value is 1
  const res = await fetch(`https://... /posts/${params.id}`)
  const post = await res.json()

  // Post the data to the props of the page
  return {
    props: { post },
    // The article details page is generated again when the request comes in, such as modifying the article to regenerate
    // Generates a maximum of one time within 1s
    revalidate: 1,}}export default Post

Copy the code

Prisma — the next generation ORM framework

Nodejs framework access database, often need an ORM framework to help us manage the data layer code, while in node.js community, sequelize, TypeORM and other frameworks are widely used, while Prisma is a newcomer.

Prisma supports Mysql, Postgresql, and Sqlite. Access to the Prisma website makes it easy to get started and quickly access from older projects

Prisma and TypeORM solve similar problems, but they work in very different ways.

Compared with TypeORM

TypeORM is a traditional ORM that maps tables to model classes. These model classes can be used to generate SQL migrations. An instance of the model class then provides an interface to the APPLICATION’s CRUD queries at run time.

Prisma is a new ORM that alleviates many of the problems of traditional ORM, such as ballooning model instances, mixing business and storage logic, lack of type safety, or unpredictable queries caused by lazy loading.

It uses Prisma Schema to define application models declaratively. Then using the Prisma Migrate command, Prisma Schema generates SQL migrations and executes them against the database. Prisma CRUD queries are provided by Prisma Client, a lightweight and fully type-safe database Client for Node.js and TypeScript.

Compare the two codes

  1. Prisma Schema
model User {
  id      Int      @id @default(autoincrement())
  name    String?
  email   String   @unique
  posts   Post[]
}

model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String?
  published Boolean @default(false)
  authorId  Int?
  author    User?   @relation(fields: [authorId], references: [id])
}
Copy the code

Schema is a description file that describes the direct relationships between data models. Prisma generate generates typescript declarations.

  1. TypeORM Entity
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, ManyToOne } from 'typeorm'

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number

  @Column({ nullable: true })
  name: string

  @Column({ unique: true })
  email: string

  @OneToMany(
    type= > Post,
    post= > post.author
  )
  posts: Post[]
}

@Entity()
export class Post {
  @PrimaryGeneratedColumn()
  id: number

  @Column()
  title: string

  @Column({ nullable: true })
  content: string

  @Column({ default: false })
  published: boolean

  @ManyToOne(
    type= > User,
    user= > user.posts
  )
  author: User
}
Copy the code

Entity is a JavaScript class that inherits at run time via @entity ().

filter

  1. Prisma
const posts = await postRepository.find({
  where: {
    title: { contains: 'Hello World'}},})Copy the code
  1. TypeORM
const posts = await postRepository.find({
  where: {
    title: ILike('%Hello World%'),}})Copy the code

Cascading many-to-many relationships

  1. Prisma
typeCategories={ id? :number
  name: stringcreatedAt? :Date | null} []type PostBody = Post & {
  categories: Categories;
};

const { title, summary, slug, content, published, categories } =
    req.body as PostBody;

const connectOrCreate = categories.map(({ name }) = > {
    return {
      create: {
        name,
      },
      where: {
        name,
      },
    };
  });
  const newPost = await prisma.post.create({
    data: {
      title,
      summary,
      slug,
      content,
      published,
      categories: {
        connectOrCreate,
      },
      user: {
        connect: {
          id: req.user.id,
        },
      },
    },
    include: {
      categories: true,}});Copy the code

There is a many-to-many relationship between articles and categories. An article can have multiple categories, and there can be multiple articles under one category.

Categories can select existing or newly added categories, and judge whether to add or cascade by name unique familiarity.

  1. TypeORM
@Entity(a)export class Post {
  @PrimaryGeneratedColumn(a)id: number

  @Column(a)@IsNotEmpty(a)title: string

  @Column({
    select: false.type: 'text',})content: string

  @ManyToMany((type) = > Category, {
    cascade: true./ / cascade insert modify Boolean | (" insert "|" update "|" remove "|" soft - remove "|" recover ") []
  })
  @JoinTable(a)categories: Category[]
}

constnewPost = postRepository.create({ ... ctx.request.body, })Copy the code

Typeorm uses the Cascade property to add, delete, modify, and delete files

Postgresql

This refactoring also moves the database to Postgresql.

MySQL has a pit where only UTF8MB4 can display emoji, Pg does not have this pit;

2. Pg can store array and JSON, and can build indexes on array and JSON.

Code editor

From the last version, codeMiror and Remark wrote their own components. This version found that The Markdown editing of Nuggets was easier to use, so it directly used Bytemd. The bottom layer was remark and Rehype, which supported any framework and had rich plug-ins. It is useful, but there is no separate TOC component in the article details page, so you have to wrap a separate TOC component.

summary

In this article, I mainly record the knowledge and records of the reconstruction of the blog, of course, there are still a lot of deficiencies, but also a lot of features to develop, such as: graph bed, comments, SEO optimization, statistics and monitoring.

Of course, content is the most important. I hope I can write an article every week or every two weeks to record and summarize knowledge.

Heroku supports a Postgresql database for free. You can also deploy your application to Vercel. app/ (which is faster in China and does not support databases). Remember to give a small star ✨!