avatar
CRUMBLEDWALL
Keep Curious
用 Next.js 重构博客
May 26,2022

从复试结束以来一直在忙东忙西,要准备毕设、写之前答应朋友的外包项目,还要帮未来导师搬砖。终于最近闲了下来,可以试着把之前看到的一个很有趣的博客项目来实现一下

关于语雀和 Next.js

Sukka 说过:“哪个男孩不想拥有一个速度非常快的博客”,我一直深以为然。所以第一次看到那些基于 Next.js 的博客的优秀性能,我就很想把博客迁移到 Next.js 了,考研期间没那么多时间,只是用 Next.js 写了 mini 版本的日记项目,来记录每天刷了什么试卷背了什么单词之类的,算是第一次接触和使用 Next.js。

然后有天看到助手社团的公开 blog,是基于 Notion api 和 Next.js 实现的,然后去了解了一下,发现语雀也有公开的 api。而且还有个很有趣的功能,就是每次发布文章可以发送推送,也就是支持向某个 api 发送一条请求。这个特性再搭配上 Vercel 的 Deploy Hook api,可以实现语雀这边每发布一篇博客,Vercel 那边就自动构建一版静态博客并部署,整个流程非常舒服。

另外提到 Next.js 就不得不提一下它的 SSG(Static Site Generation) 功能,顾名思义,使用 SSG 的页面,其数据在构建时就被获取,渲染成静态页面后,再进行部署。我们只要在需要静态渲染的页面添加一个如下的方法,这个方法会在 build 阶段被执行,将返回内容作为静态的数据传入组件中。

export const getStaticProps = async ({ params }) => {
  const postData = await getPost(params.id as string)
  return {
    props: {
      postData
    }
  }
}

export default function Post({ postData }) {
  return (
    // ...
    <div>{postData?.title}</div>
    // ...
  )
}

Windi CSS 的 typography 插件

我们从语雀获取 markdown 格式的文章内容后,将其解析为 html 格式的文本。对于这些无法直接编辑其 class 值的 html,就没有办法方便的用 Windi CSS 来修改其样式了。这时候就可以考虑使用 typography 插件。

我们只要在所需的地方添加一个 prose 类,Windi CSS 就会为其自动添加一些样式。同时再到 windi.config.ts 文件里,覆盖一些 typography 的默认配置,基本就可以满足所需。

关于暗色模式

为了优化体验,给博客添加了暗色模式。Windi CSS 对暗色模式的支持很好,对于暗色模式和亮色模式两种样式,只要像这样写:bg-light-100 dark:bg-dark-800,就可指定两种模式下当前元素各自的背景颜色。

Windi CSS 默认支持两种暗色模式的切换选项,class 模式和 media 模式。前者有开发者自己维护,只需要给父元素加一个 dark 类,它的所以子元素都表现为暗色模式;后者则是跟随用户系统的亮暗模式变化。为了添加一个切换亮暗模式的按钮,我们需要把 Windi CSS 改成 class 模式,但是这时,如果我们想要用户第一次访问网站时,能根据用户系统的亮暗模式来显示,需要再进行一次判断。

一开始想自己在页面加载阶段通过媒介查询判断一下当前系统亮暗模式,然后给 body 赋一个 class,来给出第一次的显示模式,但是后来发现如果是夜间模式,总是要闪屏一下,很影响观感。

后来选用了一个 next-themes 的依赖,它可以非常方便的管理亮暗模式,支持维护一个跨页面的 theme 状态,方便获取和修改当前模式,也不会有闪屏的问题了。

只需要在 _app.tsx 把入口修改为:

import { ThemeProvider } from 'next-themes'

function App({ Component, pageProps }) {
  return (
    <ThemeProvider attribute="class">
      <Component {...pageProps} />
    </ThemeProvider>
  )
}

export default App

然后第一次访问它会根据用户系统的亮暗模式选择一个颜色模式,如果用户通过模式修改了亮暗,则存到 localstorage,之后访问根据 localstorage 中存的模式显示。

对于模式切换,它提供了一个 React Hook 一样的接口:

const { resolvedTheme, setTheme } = useTheme()

可以根据 resolvedTheme 切换当前模式按钮的样式,然后通过 setTheme 切换当前亮暗模式,非常舒服。

除此之外其实还有一个细节,关于页面滚动条的亮暗,这点其实 Chrome 和 Firefox 的表现是不同的,Chrome 想要页面滚动条显示为暗色,需要设置一个 <meta content="dark" name="color-scheme" /> 标签,或者给 :root 的 css 里加上这条,而 Firefox 的滚动条其实是半透明的,只要确保 body 的 background-color 是暗色,滚动条就是暗色。next-themes 会帮助添加和切换 color-scheme 的值,所以 Chrome 端不需要我们来考虑,对于 Firefox,我们需要给 body 添加背景色。对 Next.js 来说,想要修改 body 的 tag,需要新建一个 _document.tsx,并在其中修改:

import { Head, Html, Main, NextScript } from 'next/document'

export default function Document() {
  return (
    <Html>
      <Head />
      <body className="dark:bg-dark-800 bg-light-100">
        <Main />
        <NextScript />
      </body>
    </Html>
  )
}

这样就解决了滚动条亮暗的问题。

友链和 Grid 布局

由很多卡片组成的友链页,其实是个蛮有意思的地方。如果单纯采用我平时用的比较多的 Flex 布局,然后用 Flex-Wrap 来布局的话,其实是比较难受的。

比如简单模拟一个类似的场景:

<div class="flex w-96 flex-wrap gap-4 p-8 bg-gray-200">
<!--div class="flex w-72 flex-wrap gap-4 p-8 bg-gray-200"-->
    <div class="w-20 h-16 bg-gray-400"></div>
    <div class="w-20 h-16 bg-gray-400"></div>
    <div class="w-20 h-16 bg-gray-400"></div>
    <div class="w-20 h-16 bg-gray-400"></div>
    <div class="w-20 h-16 bg-gray-400"></div>
    <div class="w-20 h-16 bg-gray-400"></div>
    <div class="w-20 h-16 bg-gray-400"></div>
  </div>

这里假设 w-96 和 w-72 模拟的是页面的宽度,当这个页面宽度变化时,我们的内容会像这样,偏向一侧,比较难看。

并且,如果我们想添加 justify-content: center 或者 justify-content: space-between 的属性来解决的话,得到的布局也是奇奇怪怪,完全不符合预期:

这时候就有必要考虑换成 Grid 布局了,搭配 Windi CSS 一起用,也是非常方便,只要:

<div class="grid grid-cols-3 w-96 gap-4 p-8 bg-gray-200 justify-items-center">
    <div class="w-20 h-16 bg-gray-400"></div>
    <div class="w-20 h-16 bg-gray-400"></div>
    <div class="w-20 h-16 bg-gray-400"></div>
    <div class="w-20 h-16 bg-gray-400"></div>
    <div class="w-20 h-16 bg-gray-400"></div>
    <div class="w-20 h-16 bg-gray-400"></div>
    <div class="w-20 h-16 bg-gray-400"></div>
    <div class="w-20 h-16 bg-gray-400"></div>
  </div>

grid-cols-3 可以设置当前分为3列,我们可以用 Windi CSS 的媒介查询来根据不同的页面宽度来更改,比如设置成 grid-cols-1 md:grid-cols-2 lg:grid-cols-3,然后对于之前 Flex 很难受的偏向一边的问题,只要设置一个 justify-items: center 就可以解决了,显示效果符合预期:

所以友链页面最终也是采用了 Grid 布局,方便地解决了多种宽度下的布局。

2022.11 更新

后来感觉语雀的 api 还是有一定局限,最后转为使用 Notion 提供的开放 API,对博客进行了一波重构。

Copyright @ 2018-2024
Crumbledwall