简简单单的 spa + vercel + serverless function 居然有这么多坑

1,670 阅读6分钟

手上没活不太行,所以我想整个活。

整个小网站吧,要有路由,要有域名,要有接口,要有数据库。还要 不花钱

我看中了 vercel

天下没有免费的午餐,vercel 有,不花钱的 Hobby Plan 足够整活用了,但是不要效仿“Java之父”把自己的站点交给网友压测。

FeatureHobbyPro
Projects200Unlimited
Bandwidth100 GB1 TB
Edge Functions execution units500,0001 million
Edge Middleware invocations1 million2 million
Serverless Function maximum duration10 seconds15s (default) - configurable up to 300s
Serverless Function execution100 GB-hours1,000 GB-hours
Build execution minutes6,00024,000
Image Optimization source images1,0005,000
Team collaboration features-Yes
Remote Cache artifact downloads10 GB20 GB
Domains per project50Unlimited
Deployments per day1006,000
Analytics-Limited
Email support-Yes

它提供了 templates

各种用途风格框架的项目模板都有

image.png

点进去可以预览 demo,也可以一键部署

image.png

支持多种 Git Provider

image.png

后面就是傻瓜式操作,选个 Provider,选个账号,选个项目名,Create 就完了,你会得到一个代码仓库和 vercel 白给的域名,点进去就可以预览你的网站了。

它支持了 serverless function

vercel 支持用 Node.js, Go, Python, Ruby 写 serverless function,具体请参考文档。

同时还提供了一些模板

image.png

它集成了 storage

在这里选个喜欢的,连接到项目就行了,文档在这里 👉🏻 vercel.com/docs/storag…

image.png

它连接了 GitHub

在 dashboard 里 create new project,会被引导创建项目,可以轻松地连接到 GitHub 项目并部署

image.png

它还整了 持续集成

当你 project settings 中监听的分支有变更时,vercel 会自动构建,这里可以查看构建历史

image.png

我遇到了 坑

看了文档和模板示例,我说快整吧,我感觉我现在强得不行。然而,事情并不顺利,最简单的起步,让我踩了仿佛两年半的坑。

线上非默认路由刷新 404 了

我用的 React 的 BrowserRouter,想必开发过 spa 的鸽鸽们都遇到过,本地没问题,到线上刷新非默认路由 404.

这种情况如果没有 nginx 反向代理之类的配置,通常访问默认路由没问题,跳转非默认路由也没问题,但是当在非默认路由页面刷新时就会 404,是因为请求不到资源,spa 中就一个 html,没其他页面,请求不到也正常。

我们要想办法把这个请求定向到 index.html,这就妥了。

Stack Overflow 和 vercel 文档上都有方法,不赘述了,直接贴解决办法

在项目根目录中创建 vercel.json,甚至可以在这个配置文件里设置反向代理。然后部署项目就好了

{
  "rewrites": [
    {
      "source": "/(.*)",
      "destination": "/index.html"
    }
  ]
}

本地调接口进到路由里了

到这里先说怎么加接口,整个最简单的 Hello World 接口,参考 👉🏻 Quickstartexample 原来加个接口这么简单。

加了接口之后,为什么线上能访问,本地接口一访问就进路由了呢?

原来不能用 pnpm dev 启动服务,需要用 vercel dev,它才会把 serverless function 跑起来,同时还要改造下 vercel.json,用负向前行断言或叫否定前瞻断言告诉 vercel 把别的路由定向到 /index.html 得了,api 开头的是接口,不要这么处理。

{
  "rewrites": [
    {
      "source": "/((?!api\/.*).*)",
      "destination": "/index.html"
    }
  ]
}

这么一来在本地也把 api 开头的请求当做接口处理了。

调接口 FUNCTION_INVOCATION_FAILED

但是,为啥一调用接口就报错了呢?

查了一大圈,Stack Overflow、GitHub issues、discussions、vercel 文档都看遍了,配置也是一顿改,没用。然后问了 GPT,它最没用,说你看日志去吧,问题是根本没日志。

然后我想,既然本地和线上环境是有差异的,保不齐线上能行呢?发了下线上,调接口,还不行。但是,不同的是线上有日志,在这个地方

image.png

没想到从最没用的回答中找到了最朴素的解决方法。按照日志提示,在 package.json 中设置 type: module 解决问题。

调接口返回内容异常了

此时应该没有什么幺蛾子了,继续调试,发现线上好的,本地访问接口也能访问,但是返回的响应有点不对劲。

仔细一看,没有返回 Hello World,而是把 api/hello.ts 中的 file content 返回出来了,这也没用啊,肯定是配置问题。

配置改来改去,用过 rewrites, redirects, functions 等等都没用。

又找来找去,找到个方法,说在 vercel.json 中用 routes 字段替换 rewrites 字段,虽说文档不建议使用这个字段,但是没办法,试试吧

image.png

{
  "routes": [
    {
      "src": "/((?!api\/.*).*)",
      "dest": "/index.html"
    }
  ]
}

结果改成这样还真行,欢欢喜喜部署到线上。

本地好了,线上不行了

又出问题了,本地行了,线上不行了,访问页面加载不出来了,一看是加载 js 资源文件出问题了,响应头 Content-Type 变成 text/html 了,怪不得不行了,记得文档上有在 vercel.json 中设置 headers 的选项。

针对 dist/assets/**/*.js 资源设置了正确的响应头,线上倒是能访问页面了,但是样式没了,项目中用了 unocss。这怎么行?

我们已经知道用 vercel 运行项目时需要 vercel.json 配置文件,能不能像加载 .env 文件似的在不同环境加载不同的配置?查了下没有这种写法,但是从 vercel help dev 命令中找到了希望。

➜ ~ vercel help dev
Vercel CLI 33.4.1

  ▲ vercel dev [dir] [options]

  Starts the `vercel dev` server.                                           

  Options:

   --listen <uri>  Specify a URI endpoint on which to listen [0.0.0.0:3000]  


  Global Options:

       --cwd <DIR>            Sets the current working directory for a      
                              single run of a command                       
  -d,  --debug                Debug mode (default off)                      
  -Q,  --global-config <DIR>  Path to the global `.vercel` directory        
  -h,  --help                 Output usage information                      
  -A,  --local-config <FILE>  Path to the local `vercel.json` file          
       --no-color             No color mode (default off)                   
  -S,  --scope                Set a custom scope                            
  -t,  --token <TOKEN>        Login token                                   
  -v,  --version              Output the version number                     


  Examples:

  - Start the `vercel dev` server on port 8080

    $ vercel dev --listen 8080

  - Make the `vercel dev` server bind to localhost on port 5000

    $ vercel dev --listen 127.0.0.1:5000 

可以看到我们能使用 -A 参数指定配置文件,默认是 vercel.json,那好,我在项目根目录准备了两个文件:

vercel.json

{
  "rewrites": [
    {
      "source": "/((?!api\/.*).*)",
      "destination": "/index.html"
    }
  ]
}

vercel.local.json

{
  "routes": [
    {
      "src": "/((?!api\/.*).*)",
      "dest": "/index.html"
    }
  ]
}

在本地启动时用命令 vercel -A vercel.local.json dev,本地没问题了。

部署到线上,测试也没问题。

问题解决了

到现在问题是解决了,我明白,虽然这么整有点丑,但是我还没找到更体面的方法,先这么地吧。

希望能帮大家在整活路上踩坑时间少一点,有经验一起分享。

最后祝各位鸽鸽回家路上平平安安

ikun_rd.jpeg