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 配置的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Next.js SSG 或 s-s-r 中使用 Redux 工具包调度?

字节跳动青训营--前端day8

Next.js + 云开发Webify 打造绝佳网站

鱼和熊掌兼得:Next.js 混合渲染

redux 如何与 next.js 一起工作?

如何在 Next.js 中正确使用 Google Tag 脚本?