基于 NextJS + Vercel + Github Issues 的博客开发(二)

发布于 2020/10/24, 编辑于 2022/10/1

这里主要记录开发本博客过程中的一些坑

相关链接

  1. 基于 NextJS + Vercel + Github Issues 的博客开发(一)
  2. 基于 NextJS + Vercel + Github Issues 的博客开发(三)

源码和博客地址

  1. 源码
  2. 线上地址

环境搭建时的坑

build 报错

根据 luffyZh 的文章,对项目进行了简单的部署后,到 Vercel 上 build 的时候却出现了报错,经过测试发现在本地的时候 build 也会出现问题,如下图所示出现了报错:

image

可见是 terser plugin 出现了问题,GitHub 寻找 issue 发现了有人提出来:https://github.com/luffyZh/next-antd-scaffold/issues/12 ,根据 solution,作出以下修改即可:

image

build 完成后无法进入首页和列表页,却能进入关于页面

根据 Vecel 的控制台信息的提示,说是找不到 document 对象,而总所周知 document 对象是浏览器的一个对象不可能不存在。说明这里是服务端渲染时报的错,如下图:

image

可以看到引用了 document 的地方是 /node_modules/rc-notification/lib/Notification.js:216:13,但是我根本没有用 rc-notification 啊,很可能是被 antd 的组件封装了起来,最大的嫌疑是 antd/message 组件。然后就把项目中引用了 antd/message 的地方全部注释掉了,果然,这时候报错没了,也能成功进入主页了

但是这个组件被我用在了 redux-sagas 中间件中,用来提示报错的,还是很有必要保留下来的。于是加了如下的判断,当处在服务端的时候就用 console.log() 打印错误信息,处在客户端也就是浏览器时就使用 message.error()

export default () => next => action => {
  const ret = next(action)
  switch (action.type) {
    case REQUEST_FAIL: {
      if (!!process.browser) {  // 判断当前环境
        message.error(action.payload || ERROR_TEXT)
      } else {
        console.log(action.payload || ERROR_TEXT)
      }
      break
    }
    default:
  }
  return ret
}

GitHub 的接口请求次数限制

在开发的过程中,发现接口动不动就报错,返回的信息提示 github api rate limit,说明请求接口次数有限制,Google 了一下发现了:

For unauthenticated requests, the rate limit allows for up to 60 requests per hour. Unauthenticated requests are associated with the originating IP address, and not the user making requests.
对于没有认证的请求,GitHub Api 的每小时次数限制是60次,并且非认证请求根据的是当前请求的 IP 地址进行限制,而不是根据用户账号来限制

也就是说肯定是存在认证请求:

For API requests using Basic Authentication or OAuth, you can make up to 5000 requests per hour. Authenticated requests are associated with the authenticated user, regardless of whether Basic Authentication or an OAuth token was used.
你可以通过 Basic Authentication 或者 OAuth 让请求次数提升到每小时5000次,不管是 Basic Authentication 还是 OAuth 的请求认证都是根据认证用户进行次数限制

另外我还查到了搜索接口是每分钟15次,认证请求后会提升到每分钟30次

2021.02.14 更新:上面的描述 github 已经从文档中删除,认证方式也从 Basic Auth 和 OAuth 两种变成了 github apps、oauth apps 和 personal access tokens 三种,上面提到的 oauth 实际上变成了 person access tokens,下面提到的 token 方式其实指的也是 personal access tokens 的认证方式(今天发现博客上的接口全挂了才发现 gh 更新了这一部分,不过幸好没什么特别大的变动,重新进行下 personal access tokens 认证即可)

所以事不宜迟我马上用自己的账号进行了认证,这里采用的是 ,参考此处,权限范围一个都不用选,默认是只读权限

于是就获得了一个 token,只要把 token 放到请求头中就能发起认证请求,提升请求次数:

const opts = {
  method,
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    Accept: 'application/json',
    Authorization: `token ${githubToken}` // 把 token 放到这里
  },
  // credentials: 'include',
  timeout,
  mode: 'cors',
  cache: 'no-cache',
}

分别调用列表接口和搜索接口测试一下:

[列表接口的 Response Header 的一部分]
X-Accepted-OAuth-Scopes: repo
X-Content-Type-Options: nosniff
X-Frame-Options: deny
X-GitHub-Media-Type: github.v3
X-GitHub-Request-Id: EDA3:179E:6D4C9F:84AEDC:5F940AEC
X-OAuth-Scopes
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4997
X-RateLimit-Reset: 1603541229
X-RateLimit-Used: 3
X-XSS-Protection: 1; mode=block

[搜索接口的 Response Header 的一部分]
X-Accepted-OAuth-Scopes
X-Content-Type-Options: nosniff
X-Frame-Options: deny
X-GitHub-Media-Type: github.v3
X-GitHub-Request-Id: D763:5ED6:1B24C5F:2398D78:5F940C18
X-OAuth-Scopes
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 29
X-RateLimit-Reset: 1603538004
X-RateLimit-Used: 1
X-XSS-Protection: 1; mode=block

上面的 X-RateLimit-Limit 表示当前最高请求次数,X-RateLimit-Remaining 为剩余次数,可以看到两个接口的次数均已经提升到 GitHub App 的最高次数(注:OAuth 认证是给 GitHub App 开发用的一种认证方式)

但是,这也出现了另一个问题,就是这样会使我的 token 暴露到接口中,我不确定这样会不会有问题,于是我仔细看了下文档

Note: If you're building a GitHub App, you don’t need to provide scopes in your authorization request.
注意:如果你正在开发一个 GitHub App,你并不需要选择任何权限范围

事实上我也并没有选择权限范围,也就是使用默认的只读权限,但是我也并不确认只读范围有多大,万一会把我的 private 仓库暴露了就凉了,于是我就用认证请求请求了我的 private 仓库测试一下。最后发现其实并不会暴露 private 仓库,接口会直接说找不到:

image

所以,就可以放心使用该 token,这部分的问题也解决了

而这里事实上也有另一个坑,那就是如果你把 token 直接写死在你的代码中,那么 gh 会自动把你账户下的这个 token 给删掉,所以务必不要直接在你的代码中出现 token,有部署服务器的朋友可以通过写一个接口获取的方式来做,这里不提

markdown-navbar 插件相关的坑

core-js

会提示 core-js 版本不够,直接加到依赖中即可 "core-js": "^2.6.5"

markdown-navbar 自动改变 hash 导致的报错

markdown-nav 这个插件可以改变当前的页面的 hash 来锚定当前所处的 markdown 的位置,但是这引发了 NextJS 的一个问题:如果 hash 不是通过 Next 的 router 或者 <Link /> 来改变的话,那么这个 hash 就不会记录到 Next 的服务中,这时候如果使用了浏览器的前进或者后退按钮返回这个带有 hash 的页面的话就会报错:Cannot read property 'indexOf' of undefined in NextJS server

而且很遗憾的是,markdown-navbar 插件无法自定义 hash 更改行为,只能调用 hash 改变时的回调,要解决这个问题只能从两个地方去改:

  1. 首先是 markdown-navbar 的自动改变 hash 的行为,也就是滚动到页面对应位置时会自动变动 hash,这个可以通过 markdown-navbar 插件的 updateHashAuto 参数设置为 false 关闭
  2. 然后就是点击导航栏的 title 会跳到 markdown 文档在页面中对应的地方时,竟然也会自动添加 hash,还无法关闭,这点确实比较麻烦。经过一番 Google,只能采用一个退而求其次的方法——把浏览器实际的 hash 历史删除,具体代码如下:
// 在 markdown-navbar 的 onHashChange 中调用这个方法即可
const removeHash = e => {
  setTimeout(() => {
    window.location.replace(
      window.location.href.toString().replace(window.location.hash, '') +
        '#' +
        e,
    )
  }, 100)
}

列表页分页器的翻页优化

一般而言在 SPA 程序中实现分页,最主要的目的有两个:一是实时改变 url,让不同的 url 对应相应的分页,程序可以直接通过 url 进来列表后根据 url 上的参数跳到对应的页面而不用用户一页一页地去翻;二是可以监听浏览器的前进后退按键来恢复页面数据。

这两点在传统的静态页面中根本不是问题,但是在单页面程序中需要一些手段去实现,最主流的做法自然是 history.pushState 无刷新更改 url + 异步请求数据 + window.onpopstate 监听浏览器前进后退,具体做法这里不多说,因为在 NextJS 有另外的实现

首先先看下 NextJS 的与之相关的 api:

接口作用
hashChangeStart(url) hashChangeComplete(url)这两个接口监听 hash 的变化
routeChangeStart(url) routeChangeComplete(url)这两个接口监听 url 的变化(包括 search 的变化)

首先先说明下在使用中这两者有什么具体差异,就像上面所说的当 hash 参数(#后的参数)改变的时候会触发 hashChangeStart hashChangeComplete 而不会触发 routeChangeStart(url) routeChangeComplete(url),而只有当 url 本身或者 search 参数(? 后的参数)改变时才会触发 routeChangeStart(url) routeChangeComplete(url)

而根据我的需求,我们只需要一种改变 url 参数和监听的手段就行,而在 NextJS 中改变 url 参数的方法只能通过路由来跳而不能用 window 的方法来改变,否则会出现上面 markdown-navbar 中的坑。也就是说 url 和 hash 都是通过路由来改变的,例如:

// 改变 url
Router.push(`/post/list?page=1`)

// 改变 hash
Router.push(`/post/list#page=1`)

根据上面说的,其实无论是监听 router change 还是监听 hash change 都是可以实现分页优化的。于是我就直接采用了 hashChangeComplete 来做:

[文章列表页 src/containers/post/list.js]
import Router from 'next/router'

Router.events.on('routeChangeComplete', _handleRouteChange)

然后就是分页器点击事件,调用 router 改变 hash 即可:

import Router from 'next/router'

const handleClick = (page, keyword) => {
  Router.push(`/search#q=${keyword}&page=${page}`)
}

于是就能实现 NextJS 中的分页监听了,下一篇会讲讲怎样对程序做 SEO 优化

点击这里前往 Github 查看原文,交流意见~

文档信息

版权声明:自由转载 - 非商用 - 非衍生 - 保持署名(创意共享3.0许可证