Next.js SSG 的正确 .htaccess 配置 [英] Proper .htaccess config for Next.js SSG

查看:68
本文介绍了Next.js SSG 的正确 .htaccess 配置的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

NextJS 导出一个静态站点,结构如下:

<预><代码>|-- index.html|-- 文章.html|-- 标签.html|-- 文章||-- somearticle.html|\-- anotherarticle.html\ - 标签|-- tag1.html\-- tag2.html

我正在使用 .htaccess 文件来隐藏 .html 扩展名:

RewriteEngine onRewriteCond %{REQUEST_FILENAME} !-dRewriteCond %{REQUEST_FILENAME}\.html -f重写规则 ^(.*)$ $1.html

一切都完美无缺,除了:

  • 如果我点击 domain/article 的链接,它会显示 article.html 页面,但我的地址栏会显示 domain/article <--Good.
  • 如果我刷新,我将被发送到地址:domain/article/(注意尾部斜杠),其中列出了文章目录的内容 <--Bad(与 Tag 相同)
  • 同样,手动输入 domain/article 会将我带到 domain/article/ 而不是显示没有 article.html.html 扩展名.

所以...

  • 我该如何解决这个问题?
  • 这是 .htaccess 问题吗?
  • nextjs 配置问题?
  • (NextJS 创建一个 article\index.html 而不是根目录中的文件不是更好吗?)

exportTrailingSlash

我尝试使用看起来相关的 exportTrailingSlash,但这产生了其他问题,例如在所有链接的末尾总是有一个斜杠:

例如:如果我转到 domain/article/somearticle 并点击刷新,则某些内容(.httaccess?)会在末尾添加 / 以给我 domain/article/somearticle/ 并不可怕,只是不是很干净和不一致......

实际上,它有点可怕,因为有时我们会得到一个尾部斜杠,有时我们不会在 nextjs 链接上......一定是什么关于我如何使用 <Link/> 但我无法弄清楚.

无论如何,我尝试过的 .htaccess 规则中没有任何一个每次都成功删除尾部斜杠...


更多详情:

在我的下一个应用程序中,我有文件夹:

/articles/[slug].js索引.js

在各种页面中,我使用nextJS Link组件:

从'next/link'导入链接;<Link href="/articles";as=/articles"><a>文章</a></链接>

解决方案

如果您请求 /article/article 作为物理目录存在,那么 Apache 的 mod_dir,将 (默认情况下)附加尾部斜杠以修复"网址.这是通过 301 永久重定向实现的 - 因此它将被浏览器缓存.

尽管物理目录的基本名称与文件相同,并且使用无扩展名的 URL 会产生歧义.例如./article 是否应该访问目录 /article/ 或文件 /article.html.无论如何,您似乎不想允许直接访问目录,因此这似乎可以解决这种歧义.

为了防止 Apache mod_dir 将尾部斜杠附加到目录,我们需要禁用 DirectorySlash.例如:

DirectorySlash Off

但如前所述,如果您之前访问过 /article,那么对 /article/ 的重定向将被浏览器缓存 - 因此您需要清除在此之前的浏览器缓存将生效.

由于您要删除文件扩展名,因此还需要确保禁用 MultiViews,否则,mod_negotiation 将发出对底层文件的内部子请求,并可能与 mod_rewrite 冲突.默认情况下禁用 MultiViews,尽管某些共享主机出于某种原因启用了它.从您得到的输出来看,似乎没有启用 MultiViews,但最好确定...

# 确保 MutliViews 被禁用选项 - 多视图

但是,如果您需要能够访问目录本身,那么您需要使用内部重写手动附加尾部斜杠.虽然这似乎不是这里的要求.但是,您应该确保禁用目录列表:

# 禁用目录列表选项 - 索引

尝试访问任何目录(最终不会映射到文件 - 见下文)并且不包含 DirectoryIndex 文档将返回 403 Forbidden 响应,而不是目录列表.

请注意,跟踪到 domain/article 的链接、刷新页面和手动键入 domain/article 之间可能发生的唯一区别是 缓存... 通过浏览器或任何中间代理缓存.(除非你有拦截锚点点击事件的 JavaScript?!)

您仍然需要将请求从 /foo 重写为 /foo.html 或将 /foo 重写为 /foo/index.html(见下文),具体取决于您配置站点的方式.尽管您最好选择其中之一,而不是两者兼而有之(正如您所暗示的那样).

<块引用>

RewriteCond %{REQUEST_FILENAME} !-dRewriteCond %{REQUEST_FILENAME}\.html -f重写规则 ^(.*)$ $1.html

目前还不清楚这似乎是如何工作"的.为您目前 - 除非您看到缓存的响应?当您请求 /article 时,第一个条件失败,因为它作为物理目录存在,并且没有处理规则.即使启用了 MultiViews,mod_dir 也会优先并附加尾部斜杠.

检查 .html 文件是否存在的第二个条件不一定检查正在被重写的同一个文件.例如.如果你请求/foo/bar,其中/foo.html存在,但没有物理目录/foo,那么RewriteCond 指令检查是否存在 /foo.html - 这是成功的,但请求在内部被重写为 /foo/bar.html(从捕获的RewriteRule pattern) - 这会导致内部重写循环,并向客户端返回 500 错误响应.请参阅我的回答以下 ServerFault 问题详细介绍这里实际发生的事情.

如果我们假设任何包含文件扩展名的 URL(例如,您的静态资源 .css.js 和图像文件)应该被忽略,否则我们对每个请求都执行文件系统检查,这是相对昂贵的.

因此,为了将 /article 表单的请求(内部重写)映射到 /article.html/article/somearticle/article/somearticle.html 你需要修改上面的规则来读取类似的内容:

#/foo 重写为/foo.html(如果存在)RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.html -f重写规则 !\.\w{2,4}$ %{REQUEST_URI}.html [L]

RewriteCond TestString 中不需要反斜杠转义文字点 - 这里点没有特殊含义;它不是正则表达式.

然后,要处理应该映射到 /foo/index.html/foo 形式的请求,您可以执行以下操作:

# 将/foo 重写为/foo/index.html(如果存在)RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}/index.html -f重写规则 !\.\w{2,4}$ %{REQUEST_URI}/index.html [L]

通常,您会允许 mod_dir 为 DirectoryIndex(例如 index.html)提供服务,但是如果从目录中省略尾部斜杠,这可能会出现问题.

总结

综合以上几点,我们有:

# 禁用目录索引和多视图选项 -Indexes -MultiViews# 防止 mod_dir 在目录请求中附加斜杠目录斜线# 将/foo 重写为/foo.html(如果存在)RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.html -f重写规则 !\.\w{2,4}$ %{REQUEST_URI}.html [L]# 否则,将/foo 重写为/foo/index.html(如果存在)RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}/index.html -f重写规则 !\.\w{2,4}$ %{REQUEST_URI}/index.html [L]

这可以进一步优化,具体取决于您的站点结构以及您是否要向 .htaccess 文件添加更多指令.例如:

  1. 您可以在文件顶部检查请求的 URL 上的文件扩展名,以防止任何进一步处理.然后可以简化"每个后续规则上的 RewriteRule 正则表达式.
  2. 包含尾部斜杠的请求可能会被阻止或重定向(以删除尾部斜杠).
  3. 如果请求是针对 .html 文件,则重定向到无扩展名的 URL.如果您同时处理 /foo.html/foo/index.html,这会稍微复杂一些.但这只有在您更改现有 URL 结构时才真正有必要.

例如,实现上面的 #1 和 #2,将使指令能够像这样编写:

# 禁用目录索引和多视图选项 -Indexes -MultiViews# 防止 mod_dir 在目录请求中附加斜杠目录斜线# 如果 URL 已经以文件扩展名结尾,则阻止任何进一步处理重写规则 \.\w{2.4}$ - [L]# 重定向任何请求以删除尾部斜杠重写规则 (.*)/$/$1 [R=301,L]# 将/foo 重写为/foo.html(如果存在)RewriteCond %{DOCUMENT_ROOT}/$1.html -f重写规则 (.*) $1.html [L]# 否则,将/foo 重写为/foo/index.html(如果存在)RewriteCond %{DOCUMENT_ROOT}/$1/index.html -f重写规则 (.*) $1/index.html [L]

在更改为 301(永久)重定向之前,始终使用 302(临时)重定向进行测试,以避免出现缓存问题.

NextJS exports a static site with the following structure:


|-- index.html
|-- article.html
|-- tag.html
|-- article
|   |-- somearticle.html
|   \-- anotherarticle.html
\-- tag
    |-- tag1.html
    \-- tag2.html

I'm using an .htaccess file to hide the .html extensions:

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(.*)$ $1.html

Everything works flawlessly, EXCEPT:

  • If I follow a link to domain/article it displays the article.html page, but my address bar shows domain/article <--Good.
  • If I refresh, I get sent to address: domain/article/ (note trailing slash) which lists the contents of the article directory <--Bad (same thing with Tag)
  • Similarly, manually typing in domain/article takes me to domain/article/ instead of showing article.html without the .html extension.

So...

  • How do I fix this?
  • Is this an .htaccess issue?
  • A nextjs config issue?
  • (Wouldn't it be better for NextJS to create a article\index.html instead of a file in the root directory?)

exportTrailingSlash

I tried playing around with exportTrailingSlash which seems related, but this created other problems like always having a trailing slash at the end of all my links:

Eg: if I go to domain/article/somearticle and hit refresh, something (.httaccess?) is adding a / to the end to give me domain/article/somearticle/ not horrible, just not very clean and inconsistent...

Edit: Actually, it's a little more horrible, because sometimes we get a trailing slash, sometimes we don't on the nextjs links... must be something about how I'm using <Link /> but I can't figure that out.

Regardless, NONE of the .htaccess rules I've tried successfully remove the trailing slash all the time every time...


More details:

In my next app, I have folder:

/articles/
   [slug].js
   index.js

In various pages, I use nextJS Link component:

import Link from 'next/link';

<Link href="/articles" as="/articles">
            <a>Articles</a>
</Link>

解决方案

If you request /article and /article exists as a physical directory then Apache's mod_dir, will (by default) append the trailing slash in order to "fix" the URL. This is achieved with a 301 permanent redirect - so it will be cached by the browser.

Although having a physical directory with the same basename as a file and using extensionless URLs creates an ambiguity. eg. Is /article supposed to access the directory /article/ or the file /article.html. You don't seem to want to allow direct access to directories anyway, so that would seem to resolve that ambiguity.

To prevent Apache mod_dir appending the trailing slash to directories we need to disable the DirectorySlash. For example:

DirectorySlash Off

But as mentioned, if you have previously visited /article then the redirect to /article/ will have been cached by the browser - so you'll need to clear the browser cache before this will be effective.

Since you are removing the file extension you also need to ensure that MultiViews is disabled, otherwise, mod_negotiation will issue an internal subrequest for the underlying file, and potentially conflict with mod_rewrite. MultiViews is disabled by default, although some shared hosts do enable it for some reason. From the output you are getting it doesn't look like MultiViews is enabled, but better to be sure...

# Ensure that MutliViews is disabled
Options -MultiViews

However, if you need to be able to access the directory itself then you will need to manually append the trailing slash with an internal rewrite. Although this does not seem to be a requirement here. You should, however, ensure that directory listings are disabled:

# Disable directory listings
Options -Indexes

Attempting to access any directory (that does not ultimately map to a file - see below) and does not contain a DirectoryIndex document will return a 403 Forbidden response, instead of a directory listing.

Note that the only difference that could occur between following a link to domain/article, refreshing the page and manually typing domain/article is caching... either by the browser or any intermediary proxy caches. (Unless you have JavaScript that intercepts the click event on the anchor?!)

You do still need to rewrite requests from /foo to /foo.html OR /foo to /foo/index.html (see below), depending on how you have configured your site. Although it would be preferable that you choose one or the other, rather than both (as you seem to imply could be the case).

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(.*)$ $1.html

It is unclear how this is seemingly "working" for you currently - unless you are seeing a cached response? When you request /article, the first condition fails because this exists as a physical directory and the rule is not processed. Even with MultiViews enabled, mod_dir will take priority and append the trailing slash.

The second condition that checks the existence of the .html file isn't necessarily checking the same file that is being rewritten to. eg. If you request /foo/bar, where /foo.html exists, but there is no physical directory /foo then the RewriteCond directive checks for the existence of /foo.html - which is successful, but the request is internally rewritten to /foo/bar.html (from the captured RewriteRule pattern) - this results in an internal rewrite loop and a 500 error response being returned to the client. See my answer to the following ServerFault question that goes into more detail behind what is actually happening here.

We can also make a further optimisation if we assume that any URL that contains what looks like a file extension (eg. your static resources .css, .js and image files) should be ignored, otherwise we are performing filesystem checks on every request, which is relatively expensive.

So, in order to map (internally rewrite) requests of the form /article to /article.html and /article/somearticle to /article/somearticle.html you would need to modify the above rule to read something like:

# Rewrite /foo to /foo.html if it exists
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.html -f
RewriteRule !\.\w{2,4}$ %{REQUEST_URI}.html [L]

There is no need to backslash escape a literal dot in the RewriteCond TestString - the dot carries no special meaning here; it's not a regex.

Then, to handle requests of the form /foo that should map to /foo/index.html you can do something like the following:

# Rewrite /foo to /foo/index.html if it exists
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}/index.html -f
RewriteRule !\.\w{2,4}$ %{REQUEST_URI}/index.html [L]

Ordinarily, you would allow mod_dir to serve the DirectoryIndex (eg. index.html), but having omitted the trailing slash from the directory, this can be problematic.

Summary

Bringing the above points together, we have:

# Disable directory indexes and MultiViews
Options -Indexes -MultiViews

# Prevent mod_dir appending a slash to directory requests
DirectorySlash Off

# Rewrite /foo to /foo.html if it exists
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.html -f
RewriteRule !\.\w{2,4}$ %{REQUEST_URI}.html [L]

# Otherwise, rewrite /foo to /foo/index.html if it exists
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}/index.html -f
RewriteRule !\.\w{2,4}$ %{REQUEST_URI}/index.html [L]

This could be further optimised, depending on your site structure and whether you are adding any more directives to the .htaccess file. For example:

  1. you could check for file extensions on the requested URL at the top of the file to prevent any further processing. The RewriteRule regex on each subsequent rule could then be "simplified".
  2. Requests that include a trailing slash could be blocked or redirected (to remove the trailing slash).
  3. If the request is for a .html file then redirect to the extensionless URL. This is made slightly more complicated if you are dealing with both /foo.html and /foo/index.html. But this is only really necessary if you are changing an existing URL structure.

For example, implementing #1 and #2 above, would enable the directives to be written like so:

# Disable directory indexes and MultiViews
Options -Indexes -MultiViews

# Prevent mod_dir appending a slash to directory requests
DirectorySlash Off

# Prevent any further processing if the URL already ends with a file extension
RewriteRule \.\w{2.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]

Always test with a 302 (temporary) redirect before changing to a 301 (permanent) redirect in order to avoid caching issues.

这篇关于Next.js SSG 的正确 .htaccess 配置的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆