为什么 nextjs 项目, 访问不到 public 目录下的 html 文件?
发现问题
打算搞一个百度收录, 百度需要我在根目录放一个 baidu_verify_xxx.html
的文件, 用于验证。
我一想, 这不太简单了, 直接把 .html
文件扔到 public
目录下不就好了?
扔进去, 测试一下, 404... 当时就猜, 应该被 nextjs
拦截了, 一看源码, 果然...
瞄瞄源码
以下仅截取关键代码
class NextNodeServer extends BaseServer {
/**
* https://github.com/vercel/next.js/blob/canary/packages/next/src/server/next-server.ts#L642
*/
protected async findPageComponents() {
/**
* https://github.com/vercel/next.js/blob/canary/packages/next/src/server/next-server.ts#L710
*/
const components = await loadComponents({
distDir: this.distDir,
page: pagePath,
isAppPath,
})
}
}
而 loadComponents
中:
function loadComponents() {
/**
* https://github.com/vercel/next.js/blob/canary/packages/next/src/server/load-components.ts#L135
*/
const ComponentMod = await Promise.resolve().then(() =>
requirePage(page, distDir, isAppPath)
)
}
requirePage
:
/**
* https://github.com/vercel/next.js/blob/canary/packages/next/src/server/require.ts#L108
*/
function requirePage() {
const pagePath = getPagePath(page, distDir, undefined, isAppPath)
if (pagePath.endsWith('.html')) {
return promises.readFile(pagePath, 'utf8').catch((err) => {
throw new MissingStaticPage(page, err.message)
})
}
}
getPagePath
:
/**
* https://github.com/vercel/next.js/blob/canary/packages/next/src/server/require.ts#L93
*/
export function getPagePath(
page: string,
distDir: string,
locales: string[] | undefined,
isAppPath: boolean
): string {
const pagePath = getMaybePagePath(page, distDir, locales, isAppPath)
if (!pagePath) {
throw new PageNotFoundError(page)
}
return pagePath
}
getMaybePagePath
(此处完整截取了该函数代码):
/**
* https://github.com/vercel/next.js/blob/canary/packages/next/src/server/require.ts#L25
*
* 太直白了, 没什么好注释解释的了, 就是:
* 1. 从缓存里取, 如果没取到:
* 2. 构建的时候会生成 .next/server/app-paths-manifest.json 和 .next/server/pages-manifest.json, 看看这两个文件里面有没有这个路径, 并拼上 .next/server
*/
export function getMaybePagePath(
page: string,
distDir: string,
locales: string[] | undefined,
isAppPath: boolean
): string | null {
const cacheKey = `${page}:${distDir}:${locales}:${isAppPath}`
let pagePath = pagePathCache?.get(cacheKey)
// If we have a cached path, we can return it directly.
if (pagePath) return pagePath
const serverBuildPath = path.join(distDir, SERVER_DIRECTORY)
let appPathsManifest: undefined | PagesManifest
if (isAppPath) {
appPathsManifest = loadManifest(
path.join(serverBuildPath, APP_PATHS_MANIFEST),
!isDev
) as PagesManifest
}
const pagesManifest = loadManifest(
path.join(serverBuildPath, PAGES_MANIFEST),
!isDev
) as PagesManifest
try {
page = denormalizePagePath(normalizePagePath(page))
} catch (err) {
console.error(err)
throw new PageNotFoundError(page)
}
const checkManifest = (manifest: PagesManifest) => {
let curPath = manifest[page]
if (!manifest[curPath] && locales) {
const manifestNoLocales: typeof pagesManifest = {}
for (const key of Object.keys(manifest)) {
manifestNoLocales[normalizeLocalePath(key, locales).pathname] =
pagesManifest[key]
}
curPath = manifestNoLocales[page]
}
return curPath
}
if (appPathsManifest) {
pagePath = checkManifest(appPathsManifest)
}
if (!pagePath) {
pagePath = checkManifest(pagesManifest)
}
if (!pagePath) {
pagePathCache?.set(cacheKey, null)
return null
}
pagePath = path.join(serverBuildPath, pagePath)
pagePathCache?.set(cacheKey, pagePath)
return pagePath
}
稍加分析
nextjs
对于 .html
后缀的访问, 直接去生成的 app
和 pages
目录里面找, 找不到就直接返回 404 了, 根本不管你 public
目录里面有没有
解决方案
虽然可以从源码中添加一个 baidu_verify_xxx
文件, 但我感觉这样挺烦的, 干脆, 我们原来的在 nginx
中的做法不是把所有的东西都扔给 nextjs
处理嘛, 现在不了, nginx
先去 public
目录里面找一找, 有就直接返回, 没有再交给 nextjs
。
server {
+ try_files /public$uri @nextjs;
- location / {
+ location @nextjs {
proxy_pass http://nextjs_upstream;
}
# ...
}
p.s. 其实, 像这种情况,
nginx
直接返回文本省事多了🙃 但我要是不用try_files
, 前面的源码不是白分析了吗 [狗头] 这样一搞, 什么 html 静态文件都能用了 [正色]
再 p.s. 其实, 不适合一股脑try_files
, 因为如果是图片, 交给nextjs
处理,next/image
会做优化, 直接由nginx
丢出去, 优化就无效了
题外话
配置好后, 页面仍然报 404
, 哪里出了问题呢?
-
看看
nginx
是谁执行的- 执行
ps aux | grep '[n]ginx'
- 哦, 原来是
www-data
用户
- 执行
-
看看
www-data
用户能不能访问到相应的文件- 执行
sudo -u www-data stat /path-to-target-file
- 也可以执行
sudo -u www-data namei /path-to-target-file
, 这个命令能更清楚地看到是在哪一目录才denied
的 - md
permission denied
- 执行
哦哦, 查一下, 发现 nginx
需要 x
权限, 而我的项目放在 /home/xxx
目录下, 权限是 750
...
教训就是, 项目别放到 /home
目录下!
warning
chatgpt 说:
对于普通的Web服务器,try_files 的影响通常是可以忽略不计的。但是,在高性能要求的环境中,需要谨慎使用,并进行性能测试以确保不会成为性能瓶颈。如果性能成为问题,可能需要考虑其他优化策略,如缓存、负载均衡等。