使用 NextJS 和 TailwindCSS 重构我的博客

sxkk20081年前知识分享131

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

  • 第一版:使用 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。记得给一个小星 ✨ !

返回列表

没有更早的文章了...

下一篇:网易 有态度

相关文章

百度天工:人工智能赋能产业升级与创新发展

百度天工:人工智能赋能产业升级与创新发展

  随着人工智能技术的快速发展和应用,百度天工作为积极推动智能产业发展的先行者,致力于将人工智能赋能各行各业,推动产业升级与创新发展。百度天工的出现极大地丰富和拓展了人工智能...

人工智能对生活的改变 - 从智能家居到医疗健康

人工智能对生活的改变 - 从智能家居到医疗健康

  近年来,人工智能技术快速发展,已经深入到我们的生活中。从智能家居到医疗健康,AI的应用已经开始改变我们的生活方式,甚至可能重塑我们的社会结构。本文就从智能家居、智能医疗和...

AI技术培训:培养未来人工智能领域专业人才

AI技术培训:培养未来人工智能领域专业人才

  人工智能(AI)如今已经成为全球热门话题。从制造业、医疗保健到教育领域,AI技术已经开始对人们的生产和生活产生积极影响。然而,真正掌握AI技术的工程师人才并不多,这意味着...

2-3 分钟后会在屏幕上输出宝塔面板的登录地址,宝塔面板会随机生成用户名、密码和端口,我们需要把这些地址保存到本地,以免下次忘记。

接下来我们需要在云服务器上设置安全组或者防火墙,放行自动生成的端口。

腾讯云配置安全组

开通了端口,输入宝塔面板地址,输入用户名和密码就可以登录了 宝塔面板登录

域名解析

腾讯云域名解析

在你的域名服务商后台,将一个域名解析到 这台这台服务器 ip,解析完成后,你就可以使用域名访问了。

安装 PHP+mysql

登录后要先绑定一个宝塔账号,这个大家自行注册就可以了 选择系统推荐的 LNMP 环境 绑定成功后,我们来安装 PHP+mysql,系统会自动弹窗框让我们来选择环境,我这里选择 LNMP

  • 急速安装,安装时间极快(5-10 分钟),版本与稳定性略低于编译安装,适合快速部署测试
  • 编译安装,安装时间长(30 分钟到 2 小时),性能最大化,适合生产环境, 点击一键安装后,宝塔面板就会开始安装环境了

LNMP 环境安装进度 等待 10 分钟后,环境安装完成,当然有经验的同学可以自行安装 PHP+mysql 的环境,但是使用宝塔面板对新手比较友好。

访问IP显示会宝塔404页面 此时访问我们的 IP 就可以看到页面,说明我们的环境已经安装成功了。

安装微擎

微擎官网:https://www.we7.cc/

微擎官方文档:https://www.kancloud.cn/donknap/we7/136557

微擎源码地址:https://gitee.com/we7coreteam/pros

微擎是一款小程序和公众号管理系统,可以实现微信平台(mp.weixin.qq.com)不能实现的功能,例如商城,餐饮,酒店,汽车,门店,同城,各类行业解决方案,营销,推广,吸粉,游戏,物联网和人工智能等功能,这些应用大部分收费,也有免费的应用,大家可以在官网上搜索安装。

点击网站,添加网站,输入你自己的要解析的域名, 宝塔面板添加网站

选择创建数据库,和 FTP,点击提交,此时输入我们的域名可以看到如下页面

宝塔默认创建的页面

说明我们的网站创建成功了。

安装微擎框架

在网站 ftp 目录下上传微擎框架的源码 上传微擎框架源码

上传完成后点击 zip 文件解压

设置网站默认站点

点击默认站点,设置我们刚才创建的网站

输入IP,开始安装微擎 输入 IP 地址,就可以进入微擎的安装页面了。 微擎安装页面检查环境

安装过程中会检查 PHP 环境要求,若检查不成功,我们需要修改相应的 PHP info 文件。

微擎配置数据库和密码

点击继续输入刚才创建的数据库信息,并且设置微擎后台密码

微擎安装完成

点击继续安装完成

《成语小秀才》部署教程

前言相信很多朋友都玩过这款小游戏,叫成语秀才,没体验过的朋友可以点击这里,扫码体验,这款小游戏开发起来还是比较困难的,首先要有一份海量的题库,然后在每道题都有不同的布局,我是一个非常喜欢学习的人,于是...

AI合成人脸技术:现实还是幻觉?

AI合成人脸技术:现实还是幻觉?

  随着人工智能技术的迅猛发展,AI合成人脸成为一个备受关注的话题。这项技术利用机器学习和深度神经网络来生成逼真的虚拟人脸,引发了人们对技术进步的热议和对安全与伦理问题的担忧...

新的文档采用了全新的架构 next.js + Tailwind CSS ,改版后的文档界面有种焕然一新的感觉,支持暗黑模式,我们可以在线学习,并且写代码,采用了这种所见即所得的形式,大大降低了学习者的成本,我也被这种形式所深深吸引,那么这种所见即所得的形式是如何实现的呢?

基本介绍

新文档地址在 https://github.com/reactjs/reactjs.org/ 中的 beta 目录下,外层代码是目前的文档代码,那么我们可以直接 git clone 并且拷贝 beta 目录下的内容

这里面有 yarn.lock 文件,跟绝大多数 next 项目一样 yarn install 之后,运行 yarn dev 就可以运行开发环境

s16501401132022

启动速度非常快,仅仅 3.3s, 打开 http://localhost:3000,此时 next.js 会再次编译,大概 200ms,这种优势得益于 next.js 按需编译的优势,也就是是说当前启动的时候,并不会全站打包,而是当进入某个页面的时候编译当前页面,所以速度相当快。

约定式路由

s16384301132022 next 是约定式路由,在 pages 文件夹下的目录默认生成路由,即 '/src/pages/learn/add-react-to-a-website.md' 生成路由 /learn/add-react-to-a-website

此时发现里面的文档都是.md后缀的 Markdown 文件,那么 markdown 也可以写交互功能了吗? image.png

React 新的文档用到了哪些技术?

前言https://beta.reactjs.org React 的新的文档已经 完成了 70 % 并且呼吁社区进行翻译工作。 新的文档采用了全新的架构 next.js + Tailwind CSS...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。