😶🌫️ Next.js 踩坑记(1)——路径体操
date
May 29, 2024
slug
path-middle
status
Published
tags
React
Next.js
Website
summary
如何愉快的将你的 Nextjs 项目部署上 Github Page?
type
Post
引言
前段时间入坑了 React 开发,学习了基本语法后就决定找个项目跟着写写,由于b站上相关的资源重复率实在是太高了,我实在不想再写一个苍穹外卖之类的东西,于是在 YouTube 上找到了该视频,该项目是一个作品集项目,我觉得作为我 React 的 startup 非常不错,而且这个网页的审美也很合我的品味。
本着 up 敲一行我就敲一行的学习精神,两个半小时的项目也不难敲,最后项目上线的时候该博主使用 Vercel 进行部署,本着能省一点是一点的原则,我决定使用 Github Page 功能进行部署,主要原因如下
- 免费,真的良心
- 本来这东西就是静态的,没什么后端服务,我想着给他渲染成纯静态页面丢上去不就完了呗
于是,开搞!
困境
首先,我遇到了第一个问题,部署页面有两种方式,我到底该使用 Github Action 方式,还是 Deploy from a branch 方式?网上介绍的大多是第二种方式,这里笔者推荐使用第一种方式,我们可以看到这两种方式配置好之后的工作流是什么样的,孰优孰劣大家自有评判
Github Action 方式
以 main 分支为当前分支为例,只需要将当前 commit push 上去→触发 Github Action,直接完成 page 更新。
Deploy from a branch 方式
本地修改后,在本地进行编译→使用 gh-pages 将编译结果推送到 gh-pages 分支,完成更新,相比于前一种方式,除了修改代码,还需要手动编译,再推送,非常不优雅。
关于如何使用 Github Action 方式进行部署,选中该方式之后,github 会弹一个工作流文件出来,检测到当前项目为 Nextjs 项目后会自动推荐 Nextjs 的工作流,我们可以暂时先用这个工作流,后面再进行修改。
此时按理来说,只要你没有使用什么在服务端不可用的生命周期钩子,你的代码会顺利在 Github 的服务器上完成编译,并成为该项目的 Page,但事情并没有这么简单。
静态资源路径问题
当我第一次看到 Github Action 中的工作流中,编译成功的绿标出现后,我非常兴奋的打开了我的网页,它长这样:
坑爹呢这是 😵💫,此时打开开发者工具发现,除了当前页面的 html 外,不论是处于
_next/static
路径下的静态资源(主要是js和css),还是处于public
路径下的静态资源(主要是图片),资源路径通通是错的,这个项目的路径应该是musherm.github.io/Portofolio/
,但所有的资源都指向musherm.github.io/
,换言之,我需要给所有的静态资源路径前面加上一个’Portofolio/’
,查阅官方文档,我们可以发现,nextjs.config.mjs这一文件中,存在两个选项与路径有关,他们分别是:basePath
该选项用于在一个子路径下部署项目,他主要在两种场景下生效:
- Links:当使用
next/link
和next/router
时,会自动添加basePath,也就是说<Link href="/about">About Page</Link>
会输出<a href="/docs/about">About Page</a>
(basePath=’/docs’
)
- Image:当使用
next/image
时,需要手动添加basePath,说白了就是不管你引用图片用的是<img>
还是<Image>
,你通通需要手动把这个路径写完整
官方既然考虑到了图片资源会出现这个问题,为啥不能通过 basePath 自动给他加上呢,无语了。
assetPrefix
该选项仅针对
_next/static
下的静态js css资源,不会影响public下的资源,看来这一选项可以帮助我们解决js和css找不到的问题!修改我们的
nextconfig
为这样:/** @type {import('next').NextConfig} */
const nextConfig = {
output: "export",
basePath: "/Portofolio",
assetPrefix: "/Portofolio/",
images: {
unoptimized: true,
},
};
export default nextConfig;
好,至少我们的js和css确实找到了,但是事情还没有解决,我们的img标签下的所有图片,无一例外,还是指向了根目录,根据官方文档,你必须手动给他加上。
比如一个
img
标签,你修改之前它长这样:<img
src={icon}
alt={icon}
className="p-2"
/>
修改之后呢,它应该长这样:
<img
src={
process.env.NEXT_PUBLIC_ASSET_PREFIX + icon
}
alt={icon}
className="p-2"
/>
同时修改你的
nextconfig
,提供一下NEXT_PUBLIC_ASSET_PREFIX
这个环境变量,当然,你也可以通过.env .env.local 来提供,但我不喜欢把配置文件分散在好几个文件上,尽量集中管理比较清晰:/** @type {import('next').NextConfig} */
const nextConfig = {
output: "export",
basePath: "/Portofolio",
assetPrefix: "/Portofolio/",
images: {
unoptimized: true,
},
env: {
NEXT_PUBLIC_ASSET_PREFIX: "/Portofolio",
},
};
export default nextConfig;
到这里,你在编译的代码如果直接打开,资源路径应该已经加上了你的相对路径名,如果直接 push 上去,Github Action 部署还是会不成功,因为服务器端并不会读取你在 nextconfig 里定义的环境变量,你还需要在工作流中提供这个环境变量,所幸这个操作其实也很简单:打开用于定义工作流的文件,在我的环境下是.github/workflows/nextjs.yml,并在编译的时候提供环境变量,比如这样
- name: Build with Next.js
run: ${{ steps.detect-package-manager.outputs.runner }} next build
# Add this
env:
NEXT_PUBLIC_ASSET_PREFIX: /Portofolio
添加的这一行,会在服务器端编译的过程中提供这个环境变量,从而保证编译后对于public文件夹下的静态资源访问正确,到这里,我们就已经完成了对于 Nextjs 项目的 Github Page 部署。
后谈
细心的小伙伴可能发现,写死
basePath
assetPrefix
NEXT_PUBLIC_ASSET_PREFIX
这些变量,当你在本地dev环境开发时,你的首页会从localhost:3000
变成localhost:3000/Portofolio
,如果你能接受,那其实也还行,但如果想“不行,我在本地开发的时候,我就要在localhost:3000
直接访问
!”那你可以继续看下去(没错,说的就是我自己,我真的忍受不了每次从终端点开这个链接,还需要手动补全后面的路径)。答案是,你可以像NEXT_PUBLIC_ASSET_PREFIX
一样,再引入一个NODE_ENV
来判断当前的开发环境,从而在生产环境使用子路径,但在开发环境不实用,此时nextconfig
修改如下:/** @type {import('next').NextConfig} */
const isProd = process.env.NODE_ENV === "production";
const nextConfig = {
output: "export",
basePath: isProd ? "/Portofolio" : "",
assetPrefix: isProd ? "/Portofolio/" : "",
images: {
unoptimized: true,
},
env: {
NEXT_PUBLIC_ASSET_PREFIX: isProd ? "/Portofolio" : "",
},
};
export default nextConfig;
同时修改
img
标签的src
如下<img
src={
(process.env.NEXT_PUBLIC_ASSET_PREFIX || "") + "/profile.svg"
}
alt="profile"
/>
你可能想问为什么?按理来说我在dev环境已经指定了
NEXT_PUBLIC_ASSET_PREFIX
为空啊?这就是Nextjs
的又一大坑,Nextjs
的环境变量无法指定为空!(至少据我所知)如果你像这样试图指定为空,它会变成undefined
,因而你需要通过(process.env.NEXT_PUBLIC_ASSET_PREFIX || "")
这样的表达式体操来让undefined
变成真正的空。此外,你还需要修改 Github Action 的流程定义文件,从而指定编译环境为
production
。- name: Build with Next.js
run: ${{ steps.detect-package-manager.outputs.runner }} next build
env:
NODE_ENV: production
NEXT_PUBLIC_ASSET_PREFIX: /Portofolio
于是,经过这么一番折腾,我们终于调好了环境!
- 在 Github Action 侧,我们通过
basePath
assetPrefix
NEXT_PUBLIC_ASSET_PREFIX
三兄弟,完成了对css
js
img
这些静态资源的路径补全;
- 在本地 dev 侧,我们将这三兄弟全部设为空,从而保持原有的根路径访问
后后谈
总结这篇博客查阅资料时,我查到了这个东西
由于你可以像这样定义
image loader
:export default function myImageLoader({ src, width, quality }) {
return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}
所以理论上,你也可以通过在imageLoader内部拼接src,从而补全路径,但笔者懒得写了,就先放到 todo List 吧。
成果展示: