使用 NextJS 和 TailwindCSS 重构我的博客

本文涉及的产品
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
简介: 这是笔者第三次重构博客应用。本文主要是笔者记录重构博客所用的知识和记录,希望以后每周或者每两周能够有一篇文章,记录和总结知识。

image.png

这是笔者第三次重构博客,虽然博客应用是最简单的应用,但学习新技术何不从重构博客开始?

  • 第一版:使用 Hexo 和 Github pages

    • 优点:重新部署只要花5分钟,内容管理在本地 纯静态、免费;
    • 缺点:依赖Github,国内访问困难;
  • 第二版:React + Antd + Mysql 服务器是阿里云 ESC 最低配

    • 优点: 感觉没什么优点;
    • 缺点: 浏览器渲染,搜索引擎无法收录 ESO 优化难,Antd 组件使用方便,但前台页面定制需要覆盖样式;
  • 第三版:NextJS + TailwindCSS + Postgresql

    • 优点: 服务端渲染(SSR) + 静态生成, 访问速度极快,全新 UI 支持换肤;

TailwindCSS

在国外如火如荼,但是在国内却很少看到在生产上应用,对我来说, TailwindCSS 不仅仅是一个原子类的超级样式库;

1、我们在写样式的时候,经常会写类名,团队成员之间会存在样式冲突的可能,虽然我们可以使用 css modules 来避免,但却会存在取类名称的疲劳的问题,重复的类名称 -header,-body -container --wrapper等;

2、Utility-First: 默认采用 rem 单位, 变量也就是16 的倍数, px-1是 16 的 1/4 也就是 4 px,我们不会写出13px、17px 等不统一的单位变量,正所谓失之毫厘,差之千里。 配合 VScode 插件, 我们可以根据提示实时看到实际单位数值,写出高度还原高保真的样式;

image.png

3、jwt 模式: just-in-time 模式,可以写出在原子类之外的样式,比如: w-[762px]表示width:762px, grid-cols-[1fr,700px,2fr] 表示grid-template-columns: 1fr 700px 2fr; 当然还有h-[calc(1000px-4rem)]等等,这些都是运行时才生成的样式;配合在tailwind.config.js 中加入purge: ['./src/**/*.{js,ts,jsx,tsx}']打包时只会提取使用到的样式,让应用css最小化。

4、之前写了《使用 CSS variables 和Tailwind css实现主题换肤》也运用到了我的博客中。

Next.js

next.js 是一个 react 服务端渲染框架,相比react单页应用,网络爬虫可以识别 HTML 语义标签,更有利于 SEO。

接下来介绍下 NextJS 主要 API:

getServerSideProps 服务端渲染

下面是最简单的客户端渲染代码

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>  
        </>
    )
}

改成 NextJS 后的代码如下

// 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
    },
  }
}

getServerSideProps 是在node端处理,每个 request 请求时执行。

而文章内容写完之后是通常不变的,所以可以先将页面静态存储在服务器上,这样就可以大大减小数据库压力。

getStaticProps 在构建时请求数据。

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

这样就需要在构建时获取全部文章列表,而博客详情页是一个动态路由,就需要 getStaticPaths 这个API

getStaticPaths 构建时获取动态路由的数据

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

当网站构建后,新写的文章也需要生成静态页面,这时就可以将fallback 设置为true, 如果设为false,则在构建之外的文章都将返回404页面。

下面是文章详情页的主体代码

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

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

  // 如果页面还没静态生成,则先显示下面的loading
  // 直到 `getStaticProps()`运行完成
  if (router.isFallback) {
    return <div>Loading...</div>
  }

  // Render post...
}

// 在构建时运行,获取全部文章路径
export async function getStaticPaths() {
  return {
    // 在打包时值生成 `/posts/1` 和 `/posts/2` 的静态页面
    paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
    // 开启其他页面的静态生成
    // For example: `/posts/3`
    fallback: true,
  }
}

// 在构建时运行,根据params中的id 获取文章详情
export async function getStaticProps({ params }) {
  // 如果页面的路由是 /posts/1, 这 params.id 的值就是1
  const res = await fetch(`https://.../posts/${params.id}`)
  const post = await res.json()

  // 把数据专递给页面的props
  return {
    props: { post },
    //当请求进入的时候再次生成文章详情页,比如修改文章重新生成
    // 1s 内最多生成1次
    revalidate: 1,
  }
}

export default Post

prisma —— 下一代 ORM 框架

Nodejs 框架访问数据库,往往会需要一个ORM 框架来帮我们管理数据层代码,而在 Node.js 社区中,sequelize、TypeORM 等框架都被广泛应用,而 prisma 却是一个新秀。

Prisma 支持 Mysql、Postgresql 和 Sqlite, 访问官网我们可以很容易的上手,也可以快速的从老项目接入

虽然 Prisma 和 TypeORM 解决了类似的问题,但它们的工作方式却大相径庭。

与 TypeORM 对比

TypeORM 是一种传统的 ORM,它将表映射到模型类。这些模型类可用于生成 SQL 迁移。然后,模型类的实例在运行时为应用程序的 CRUD 查询提供一个接口。

Prisma 是一种新的 ORM,它缓解了传统 ORM 的许多问题,例如: 模型实例的膨胀、业务与存储逻辑的混合、缺乏类型安全性或由延迟加载引起的不可预测查询。

它使用 Prisma Schema,以声明的方式定义应用程序模型。然后使用 Prisma Migrate 命令, Prisma Schema 会生成 SQL 迁移并根据数据库执行它们。Prisma CRUD 查询由 Prisma Client 提供,这是一个针对 Node.js 和 TypeScript 的轻量级且完全类型安全的数据库客户端。

对比下二者代码

  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])
}

Schema 是一个描述文件,描述了数据模型直接的关系,再通过prisma generate 生成 typescript 声明文件。

  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
}

Entity 是在运行时,代码通过@Entity()来实现 JavaScript 类的继承。

过滤

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

多对多关系级联操作

  1. Prisma
type Categories={
  id?: number
  name: string
  createdAt?: 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,
    },
  });

文章和分类是多对多的关系,一篇文章可以有多个分类,一个分类下可以有多篇文章,

categories 可以选择已经存在的分类,也可以是新加的分类,通过name唯一熟悉来判断是否要新增还是级联。

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

  @Column()
  @IsNotEmpty()
  title: string

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

  @ManyToMany((type) => Category, {
    cascade: true,//级联插入修改  boolean | ("insert" | "update" | "remove" | "soft-remove" | "recover")[]
  })
  @JoinTable()
  categories: Category[]
}

const newPost = postRepository.create({
      ...ctx.request.body,
    })

typeorm 通过cascade 属性 就可以级联增、删、改 软删除 等

Postgresql

本次重构还讲数据库迁移到了 Postgresql。

1、MySQL 里有只有 utf8mb4 才能显示 emoji 的坑, Pg 就没这个坑;

2、Pg可以存储 array 和 json, 可以在 array 和 json 上建索引;

代码编辑器

从上一版是 codemiror 和 remark 自己写的组件 ,这一版发现掘金的 Markdown 编辑比较好用,就直接使用了bytemd, 底层都是使用了 remark 和 rehype,支持任何框架,并且拥有丰富的插件,还是比较好用的,但是在文章详情页却没有单独的 TOC(目录)组件,得单独封装一个TOC组件了。

小结

本文主要是笔者记录重构博客所用的知识和记录,当然还有很多不足,也还有很多功能得开发,
比如:图床、评论、SEO优化、 统计和监控等。

当然内容是最重要的,希望以后每周或者每两周能够有一篇文章,记录和总结知识。

喜欢的同学可以fork一下,免费部署到 Heroku 中,Heroku 支持免费的 Postgresql 数据库,也可以将程序部署到 https://vercel.app/ (国内比较快,不支持数据库),数据库还是选择 Heroku。记得给一个小星✨ !

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
7月前
|
数据采集 前端开发 JavaScript
我是如何使用 Next.js14 + Tailwindcss 重构个人项目的
这篇文章介绍了作者在学习React和Nest时受到大佬imsyy项目DailyHot的启发,基于React开发了一个项目,并因为这个项目获得了少量流量而进行了优化。作者随后因为想要优化SEO和深入学习Next.js14,决定重构这个项目。文章详细介绍了项目的信息、特性、演示图、运行环境、Vercel本地部署步骤以及责任声明。作者还感谢了为本项目提供支持与灵感的项目,并承诺会记录下开发过程中遇到的问题及解决方法以帮助他人。
我是如何使用 Next.js14 + Tailwindcss 重构个人项目的
|
1月前
|
前端开发 JavaScript
手敲Webpack 5:React + TypeScript项目脚手架搭建实践
手敲Webpack 5:React + TypeScript项目脚手架搭建实践
|
7月前
|
JavaScript 前端开发 Go
8 大博客引擎 jekyll/hugo/Hexo/Pelican/Gatsby/VuePress/Nuxt.js/Middleman 对比
探索各类博客引擎:Jekyll、Hugo、Hexo、Pelican、Gatsby、VuePress、Nuxt.js和Middleman的对比,包括语言、模板引擎、速度、社区活跃度等。了解每种引擎的优缺点,助你选择合适的博客构建工具。查看详细文章以获取更多实战和安装指南。
|
7月前
|
前端开发 JavaScript SEO
【NextJs】NextJs是什么
什么是NextJs 首先查看NextJs官网给出了如下的解释(官网地址: nextjs.org): The React Framework for the Web. Used by some of the world's largest companies, Next.js enables you to create high-quality web applications with the power of React components 总结就是用的公司多,框架水平NB,经住了很多项目的磨练。然后官网就给出了NextJs学习教程,做一个Dashboard网站。 如果要看这个教程的话:可
110 1
|
JavaScript 前端开发
从0搭建Vue3组件库(十一): 集成项目的编程规范工具链(ESlint+Prettier+Stylelint)
从0搭建Vue3组件库(十一): 集成项目的编程规范工具链(ESlint+Prettier+Stylelint)
262 0
|
前端开发
前端知识学习案例9-tailWind Css+vite2.0-项目优化
前端知识学习案例9-tailWind Css+vite2.0-项目优化
78 0
前端知识学习案例9-tailWind Css+vite2.0-项目优化
|
前端开发
前端知识学习案例1-tailWind Css+vite2.0-创建项目
前端知识学习案例1-tailWind Css+vite2.0-创建项目
55 0
前端知识学习案例1-tailWind Css+vite2.0-创建项目
|
前端开发
前端知识学习案例2-tailWind Css+vite2.0-创建项目2
前端知识学习案例2-tailWind Css+vite2.0-创建项目2
58 0
前端知识学习案例2-tailWind Css+vite2.0-创建项目2
|
JavaScript 前端开发 API
VuePress 手摸手教你搭建Vue风格的技术文档/博客
通过阿里云云开发平台 VuePress项目手摸手教你快速搭建Vue风格的技术文档/博客。
764 1
VuePress 手摸手教你搭建Vue风格的技术文档/博客
|
JavaScript 前端开发 开发工具
VuePress 手摸手教你搭建一个类Vue文档风格的技术文档/博客
VuePress是尤大为了支持 Vue 及其子项目的文档需求而写的一个项目,VuePress界面十分简洁,并且非常容易上手,一个小时就可以将项目架构搭好。现在已经有很多这种类型的文档,如果你有写技术文档的项目的需求,VuePress绝对可以成为你的备选项之一。 游泳、健身了解一下:博客、前端积累文档、公众号、GitHub VuePress特性: 为技术文档而优化的 内置 Markdown 拓展 在 Markdown 文件中使用 Vue 组件的能力 Vue 驱动的自定义主题系统 自动生成 Service Worker Google Analytics 集成 基于 Git 的 “最后
656 0
VuePress 手摸手教你搭建一个类Vue文档风格的技术文档/博客

相关实验场景

更多