Next.js SSG 的正确 .htaccess 配置
Posted
技术标签:
【中文标题】Next.js SSG 的正确 .htaccess 配置【英文标题】:Proper .htaccess config for Next.js SSG 【发布时间】:2020-10-29 04:06:43 【问题描述】:NextJS 导出具有以下结构的静态站点:
|-- index.html
|-- article.html
|-- tag.html
|-- article
| |-- somearticle.html
| \-- anotherarticle.html
\-- tag
|-- tag1.html
\-- tag2.html
我正在使用 .htaccess 文件来隐藏 .html 扩展名:
RewriteEngine on
RewriteCond %REQUEST_FILENAME !-d
RewriteCond %REQUEST_FILENAME\.html -f
RewriteRule ^(.*)$ $1.html
一切都完美无缺,除了:
如果我点击指向domain/article
的链接,它会显示article.html 页面,但我的地址栏显示domain/article
如果我刷新,我会被发送到地址:domain/article/
(注意尾部斜杠),其中列出了文章目录的内容
同样,手动输入 domain/article
会将我带到 domain/article/
,而不是在没有 .html
扩展名的情况下显示 article.html
。
所以...
我该如何解决这个问题? 这是 .htaccess 问题吗? nextjs 配置问题? (NextJS创建article\index.html
而不是根目录下的文件不是更好吗?)
exportTrailingSlash
我尝试使用似乎相关的exportTrailingSlash
,但这会产生其他问题,例如我的所有链接末尾总是有一个斜杠:
例如:如果我去domain/article/somearticle
并点击刷新,某些东西(.httaccess?)在末尾添加/
给我domain/article/somearticle/
并不可怕,只是不是很干净和不一致......
编辑:实际上,这有点可怕,因为有时我们会得到一个尾部斜杠,有时我们不会在 nextjs 链接上......一定是什么关于我如何使用<Link />
,但我无法弄清楚。
无论如何,我尝试过的所有.htaccess
规则都没有成功地每次都删除尾部斜杠...
更多细节:
在我的下一个应用程序中,我有文件夹:
/articles/
[slug].js
index.js
在各个页面中,我使用nextJS Link组件:
import Link from 'next/link';
<Link href="/articles" as="/articles">
<a>Articles</a>
</Link>
【问题讨论】:
【参考方案1】:如果您请求 /article
和 /article
作为物理目录存在,则 Apache 的 mod_dir 将(默认情况下)附加尾部斜杠以“修复”URL。这是通过 301 永久重定向实现的 - 因此它将被浏览器缓存。
虽然物理目录与文件具有相同的基本名称并使用无扩展名 URL 会产生歧义。例如。 /article
应该访问目录/article/
还是文件/article.html
。无论如何,您似乎不想允许直接访问目录,所以这似乎可以解决这种歧义。
为了防止 Apache mod_dir 将尾部斜杠附加到目录,我们需要禁用 DirectorySlash
。例如:
DirectorySlash Off
但如前所述,如果您之前访问过 /article
,则重定向到 /article/
将被浏览器缓存 - 因此您需要清除浏览器缓存才能生效。
由于您要删除文件扩展名,因此您还需要确保禁用 MultiViews,否则,mod_negotiation 将为基础文件发出内部子请求,并可能与 mod_rewrite 冲突。 MultiViews 默认情况下是禁用的,尽管某些共享主机出于某种原因确实启用了它。从您得到的输出来看,MultiViews 似乎没有启用,但最好确定...
# Ensure that MutliViews is disabled
Options -MultiViews
但是,如果您需要能够访问目录本身,则需要手动附加尾部斜杠并进行内部重写。虽然这似乎不是这里的要求。但是,您应该确保禁用目录列表:
# Disable directory listings
Options -Indexes
尝试访问任何目录(最终不会映射到文件 - 见下文)并且不包含 DirectoryIndex
文档将返回 403 Forbidden 响应,而不是目录列表。
请注意,跟随domain/article
的链接、刷新页面和手动输入domain/article
之间可能发生的唯一区别是缓存... 浏览器或任何中间代理缓存. (除非你有拦截锚点点击事件的 javascript?!)
您仍然需要将请求从/foo
重写为/foo.html
或/foo
到/foo/index.html
(见下文),具体取决于您如何配置您的站点。尽管您最好选择其中一个,而不是两者都选择(您似乎暗示可能是这种情况)。
RewriteCond %REQUEST_FILENAME !-d RewriteCond %REQUEST_FILENAME\.html -f RewriteRule ^(.*)$ $1.html
目前尚不清楚这对您来说是如何“工作”的 - 除非您看到缓存的响应?当您请求/article
时,第一个条件失败,因为它作为物理目录存在并且不处理规则。即使启用了 MultiViews,mod_dir 也会优先考虑并附加尾部斜杠。
检查.html
文件是否存在的第二个条件不一定是检查要重写的同一个文件。例如。如果您请求/foo/bar
,其中/foo.html
存在,但没有物理目录/foo
,那么RewriteCond
指令会检查/foo.html
的存在——这是成功的,但请求在内部被重写为@987654349 @(来自捕获的RewriteRule
pattern) - 这会导致内部重写循环和 500 错误响应返回给客户端。请参阅 my answer 到 following ServerFault question,其中详细介绍了此处实际发生的情况。
如果我们假设任何包含看起来像文件扩展名的 URL(例如,您的静态资源 .css
、.js
和图像文件)应该被忽略,我们还可以进行进一步优化,否则我们正在执行文件系统检查每个请求,这相对昂贵。
因此,为了将/article
形式的请求映射(内部重写)到/article.html
和/article/somearticle
到/article/somearticle.html
,您需要修改上述规则以读取如下内容:
# Rewrite /foo to /foo.html if it exists
RewriteCond %DOCUMENT_ROOT%REQUEST_URI.html -f
RewriteRule !\.\w2,4$ %REQUEST_URI.html [L]
RewriteCond
TestString 中的文字点不需要用反斜杠转义 - 点在这里没有特殊含义;这不是正则表达式。
然后,要处理应映射到 /foo/index.html
的 /foo
形式的请求,您可以执行以下操作:
# Rewrite /foo to /foo/index.html if it exists
RewriteCond %DOCUMENT_ROOT%REQUEST_URI/index.html -f
RewriteRule !\.\w2,4$ %REQUEST_URI/index.html [L]
通常,您会允许 mod_dir 提供 DirectoryIndex
(例如 index.html
),但在目录中省略了尾部斜杠,这可能会出现问题。
总结
综合以上几点,我们有:
# Disable directory indexes and MultiViews
Options -Indexes -MultiViews
# Prevent mod_dir appending a slash to directory requests
DirectorySlash Off
RewriteEngine On
# Rewrite /foo to /foo.html if it exists
RewriteCond %DOCUMENT_ROOT%REQUEST_URI.html -f
RewriteRule !\.\w2,4$ %REQUEST_URI.html [L]
# Otherwise, rewrite /foo to /foo/index.html if it exists
RewriteCond %DOCUMENT_ROOT%REQUEST_URI/index.html -f
RewriteRule !\.\w2,4$ %REQUEST_URI/index.html [L]
这可以进一步优化,具体取决于您的站点结构以及您是否向.htaccess
文件添加更多指令。例如:
-
您可以在文件顶部检查请求的 URL 上的文件扩展名,以防止任何进一步的处理。然后可以“简化”每个后续规则中的
RewriteRule
正则表达式。
可能会阻止或重定向包含尾部斜杠的请求(以删除尾部斜杠)。
如果请求是针对 .html
文件,则重定向到无扩展名 URL。如果您同时处理/foo.html
和/foo/index.html
,这会稍微复杂一些。但这只有在您更改现有的 URL 结构时才真正需要。
例如,实现上面的#1 和#2,可以将指令写成这样:
# Disable directory indexes and MultiViews
Options -Indexes -MultiViews
# Prevent mod_dir appending a slash to directory requests
DirectorySlash Off
RewriteEngine On
# Prevent any further processing if the URL already ends with a file extension
RewriteRule \.\w2.4$ - [L]
# Redirect any requests to remove a trailing slash
RewriteRule (.*)/$ /$1 [R=301,L]
# Rewrite /foo to /foo.html if it exists
RewriteCond %DOCUMENT_ROOT/$1.html -f
RewriteRule (.*) $1.html [L]
# Otherwise, rewrite /foo to /foo/index.html if it exists
RewriteCond %DOCUMENT_ROOT/$1/index.html -f
RewriteRule (.*) $1/index.html [L]
在更改为 301(永久)重定向之前始终使用 302(临时)重定向进行测试,以避免缓存问题。
【讨论】:
太棒了!我在这里学到了一些东西。特别是DirectorySlash
对我来说是新的。我没有时间测试这一切,直到明天,但感谢彻底的回应
只是添加...您还需要确保禁用 MultiViews 以使这些指令按预期工作。 (从你的输出来看,我认为它可能已经是了。)我已经更新了我的答案。
超级彻底。虽然我还没有真正解决我的问题,但详细的解释让我有很多事情要做……所以我很乐观。
Ack 对不起 - 如果生命来了,赏金会在 1/2 自动奖励,然后我才能回到这里。感谢您的帮助。
在我将 RewriteEngine On
添加到我的 .htaccess 文件之前,我发现摘要答案仍然对我不起作用。【参考方案2】:
(NextJS创建article\index.html
而不是根目录下的文件不是更好吗?)
是的!接下来can do that 为您服务:
可以配置 Next.js 将页面导出为 index.html 文件并需要尾部斜杠,
/about
变为/about/index.html
并且可以通过/about/
进行路由。这是之前的默认行为 Next.js 9.要切换回并添加尾部斜杠,请打开
next.config.js
并 启用exportTrailingSlash
配置:
module.exports = exportTrailingSlash: true,
【讨论】:
现在不知道我是怎么错过exportPathMap
的。谢谢!
太棒了!如果它最终对你有用,请接受我的回答。
所以我重新访问了exportTrailingSlash
,这看起来可行,除了当我到达domain/article/somearticle
并点击刷新时,某些东西(.httaccess?)是在末尾添加一个/
给我domain/article/somearticle/
不可怕,只是不太干净......
您可能需要另一个 htaccess 规则来删除目录的尾部斜杠:***.com/a/27264788/1174966
@Trees4theForest /article/somearticle
是否也映射到物理目录? (这在您发布的文件结构中没有说明。)以上是关于Next.js SSG 的正确 .htaccess 配置的主要内容,如果未能解决你的问题,请参考以下文章