Cấu hình .htaccess phù hợp cho Next.js SSG
NextJS xuất một trang web tĩnh với cấu trúc sau:
|-- index.html
|-- article.html
|-- tag.html
|-- article
| |-- somearticle.html
| \-- anotherarticle.html
\-- tag
|-- tag1.html
\-- tag2.html
Tôi đang sử dụng tệp .htaccess để ẩn các phần mở rộng .html:
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(.*)$ $1.html
Mọi thứ hoạt động hoàn hảo, NGOẠI TRỪ:
- Nếu tôi theo một liên kết đến
domain/articlenó sẽ hiển thị trang article.html, nhưng thanh địa chỉ của tôi hiển thịdomain/article<- Tốt. - Nếu tôi làm mới, tôi sẽ được gửi đến địa chỉ:
domain/article/(dấu gạch chéo ghi chú) liệt kê nội dung của thư mục bài viết <- Bad (điều tương tự với Thẻ) - Tương tự, việc nhập thủ công
domain/articlesẽ đưa tôi đếndomain/article/thay vì hiển thịarticle.htmlmà không có.htmlphần mở rộng.
Vì thế...
- Làm cách nào để sửa lỗi này?
- Đây có phải là vấn đề .htaccess không?
- Sự cố cấu hình nextjs?
- (Sẽ tốt hơn nếu NextJS tạo
article\index.htmlmột tệp thay vì một tệp trong thư mục gốc?)
exportTrailingSlash
Tôi đã thử chơi với exportTrailingSlashthứ có vẻ liên quan, nhưng điều này đã tạo ra các vấn đề khác như luôn có dấu gạch chéo ở cuối tất cả các liên kết của tôi:
Ví dụ: nếu tôi truy cập domain/article/somearticlevà nhấn làm mới, một cái gì đó (.httaccess?) Đang thêm một /vào cuối để cho tôi domain/article/somearticle/không quá kinh khủng, chỉ là không sạch sẽ và không nhất quán ...
Chỉnh sửa: Trên thực tế, nó kinh khủng hơn một chút, bởi vì đôi khi chúng tôi nhận được một dấu gạch chéo, đôi khi chúng tôi không có liên kết nextjs ... phải là một cái gì đó về cách tôi sử dụng<Link />nhưng tôi không thể hiểu được điều đó.
Bất kể, KHÔNG có .htaccessquy tắc nào mà tôi đã thử xóa thành công dấu gạch chéo mọi lúc ...
Thêm chi tiết:
Trong ứng dụng tiếp theo của tôi, tôi có thư mục:
/articles/
[slug].js
index.js
Trong các trang khác nhau, tôi sử dụng thành phần NextJS Link:
import Link from 'next/link';
<Link href="/articles" as="/articles">
<a>Articles</a>
</Link>
Trả lời
Nếu bạn yêu cầu /articlevà /articletồn tại dưới dạng thư mục vật lý thì mod_dir của Apache (theo mặc định) sẽ thêm dấu gạch chéo vào để "sửa" URL. Điều này đạt được với chuyển hướng vĩnh viễn 301 - vì vậy nó sẽ được trình duyệt lưu vào bộ nhớ cache.
Mặc dù có một thư mục vật lý có cùng tên cơ sở với một tệp và sử dụng URL không có phần mở rộng sẽ tạo ra sự không rõ ràng. ví dụ. Phải /articletruy cập vào thư mục /article/hoặc tệp /article.html. Bạn dường như không muốn cho phép truy cập trực tiếp vào các thư mục, vì vậy điều đó dường như sẽ giải quyết được sự mơ hồ đó.
Để ngăn Apache mod_dir thêm dấu gạch chéo vào các thư mục, chúng ta cần vô hiệu hóa DirectorySlash. Ví dụ:
DirectorySlash Off
Nhưng như đã đề cập, nếu bạn đã truy cập trước /articleđó thì chuyển hướng tới /article/sẽ được trình duyệt lưu vào bộ nhớ cache - vì vậy bạn cần phải xóa bộ nhớ cache của trình duyệt trước khi điều này có hiệu lực.
Vì bạn đang xóa phần mở rộng tệp, nên bạn cũng cần đảm bảo rằng MultiViews bị tắt, nếu không, mod_negotiation sẽ đưa ra yêu cầu con nội bộ cho tệp cơ bản và có khả năng xung đột với mod_rewrite. MultiViews bị tắt theo mặc định, mặc dù một số máy chủ được chia sẻ có bật tính năng này vì một số lý do. Từ đầu ra mà bạn nhận được, có vẻ như MultiViews không được bật, nhưng tốt hơn là hãy chắc chắn ...
# Ensure that MutliViews is disabled
Options -MultiViews
Tuy nhiên, nếu bạn cần có thể truy cập vào chính thư mục đó thì bạn sẽ cần phải thêm dấu gạch chéo theo cách thủ công với một bản viết lại nội bộ. Mặc dù điều này dường như không phải là một yêu cầu ở đây. Tuy nhiên, bạn nên đảm bảo rằng danh sách thư mục bị vô hiệu hóa:
# Disable directory listings
Options -Indexes
Cố gắng truy cập bất kỳ thư mục nào (cuối cùng không ánh xạ tới tệp - xem bên dưới) và không chứa DirectoryIndextài liệu sẽ trả về phản hồi 403 Forbidden, thay vì danh sách thư mục.
Lưu ý rằng sự khác biệt duy nhất có thể xảy ra giữa việc nhấp vào liên kết tới domain/article, làm mới trang và nhập thủ công domain/articlelà vào bộ nhớ đệm ... bởi trình duyệt hoặc bất kỳ bộ đệm proxy trung gian nào. (Trừ khi bạn có JavaScript chặn sự kiện nhấp chuột trên neo ?!)
Bạn vẫn cần phải viết lại yêu cầu từ /foođể /foo.htmlHOẶC /footới /foo/index.html(xem dưới đây), tùy thuộc vào cách bạn đã cấu hình trang web của bạn. Mặc dù bạn nên chọn cái này hay cái kia, hơn là cả hai (như bạn ngụ ý có thể là như vậy).
RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME}\.html -f RewriteRule ^(.*)$ $1.html
Không rõ điều này dường như đang "hoạt động" đối với bạn hiện tại như thế nào - trừ khi bạn thấy phản hồi được lưu trong bộ nhớ cache? Khi bạn yêu cầu /article, điều kiện đầu tiên không thành công vì điều kiện này tồn tại dưới dạng thư mục vật lý và quy tắc không được xử lý. Ngay cả khi bật MultiView, mod_dir sẽ được ưu tiên và thêm dấu gạch chéo vào sau.
Điều kiện thứ hai để kiểm tra sự tồn tại của .htmltệp không nhất thiết phải kiểm tra cùng một tệp đang được ghi lại. ví dụ. Nếu bạn yêu cầu /foo/bar, nơi /foo.htmltồn tại, nhưng không có thư mục vật lý /foothì lệnh RewriteCondkiểm tra sự tồn tại của /foo.html- thành công, nhưng yêu cầu được ghi lại nội bộ thành /foo/bar.html(từ RewriteRule mẫu đã chụp ) - điều này dẫn đến một vòng lặp ghi lại nội bộ và 500 phản hồi lỗi được trả lại cho máy khách. Xem câu trả lời của tôi cho câu hỏi ServerFault sau đây để đi sâu hơn vào những gì đang thực sự xảy ra ở đây.
Chúng tôi cũng có thể làm cho một tối ưu hóa hơn nữa nếu chúng ta giả định rằng bất kỳ URL nào chứa những gì trông giống như một phần mở rộng tập tin (ví dụ. Các tài nguyên tĩnh của bạn .css, .jsvà các tập tin hình ảnh) nên bỏ qua, nếu không chúng tôi đang thực hiện kiểm tra hệ thống tập tin trên tất cả các yêu cầu, đó là tương đối đắt tiền .
Vì vậy, để ánh xạ (trong nội bộ viết lại) yêu cầu của các hình thức /articleđể /article.htmlvà /article/somearticleđể /article/somearticle.htmlbạn sẽ cần phải sửa đổi các quy tắc trên để đọc một cái gì đó như:
# Rewrite /foo to /foo.html if it exists
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.html -f
RewriteRule !\.\w{2,4}$ %{REQUEST_URI}.html [L]
Không cần phải gạch chéo ngược để thoát khỏi dấu chấm theo nghĩa đen trong RewriteCond TestString - dấu chấm không mang ý nghĩa đặc biệt ở đây; nó không phải là một regex.
Sau đó, để xử lý các yêu cầu của biểu mẫu /foomà /foo/index.htmlbạn có thể làm như sau:
# 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]
Thông thường, bạn sẽ cho phép mod_dir phục vụ DirectoryIndex(ví dụ. index.html), Nhưng đã bỏ qua dấu gạch chéo trong thư mục, điều này có thể có vấn đề.
Tóm lược
Kết hợp các điểm trên lại với nhau, chúng ta có:
# 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]
Điều này có thể được tối ưu hóa hơn nữa, tùy thuộc vào cấu trúc trang web của bạn và liệu bạn có thêm bất kỳ chỉ thị nào vào .htaccesstệp hay không. Ví dụ:
- bạn có thể kiểm tra phần mở rộng tệp trên URL được yêu cầu ở đầu tệp để ngăn chặn bất kỳ quá trình xử lý nào. Sau đó,
RewriteRuleregex trên mỗi quy tắc tiếp theo có thể được "đơn giản hóa". - Các yêu cầu bao gồm một dấu gạch chéo có thể bị chặn hoặc chuyển hướng (để xóa dấu gạch chéo).
- Nếu yêu cầu dành cho một
.htmltệp thì hãy chuyển hướng đến URL không có phần mở rộng. Điều này sẽ phức tạp hơn một chút nếu bạn đang xử lý cả hai/foo.htmlvà/foo/index.html. Nhưng điều này chỉ thực sự cần thiết nếu bạn đang thay đổi cấu trúc URL hiện có.
Ví dụ: việc triển khai # 1 và # 2 ở trên, sẽ cho phép các chỉ thị được viết như vậy:
# 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]
Luôn kiểm tra với chuyển hướng 302 (tạm thời) trước khi chuyển sang chuyển hướng 301 (vĩnh viễn) để tránh các vấn đề về bộ nhớ đệm.
- (Sẽ tốt hơn nếu NextJS tạo
article\index.htmlmột tệp thay vì một tệp trong thư mục gốc?)
Đúng! Và Next có thể làm điều đó cho bạn:
It is possible to configure Next.js to export pages as index.html files and require trailing slashes,
/aboutbecomes/about/index.htmland is routable via/about/. This was the default behavior prior to Next.js 9.To switch back and add a trailing slash, open
next.config.jsand enable theexportTrailingSlashconfig:
module.exports = { exportTrailingSlash: true, }