Configuración de .htaccess adecuada para Next.js SSG
NextJS exporta un sitio estático con la siguiente estructura:
|-- index.html
|-- article.html
|-- tag.html
|-- article
| |-- somearticle.html
| \-- anotherarticle.html
\-- tag
|-- tag1.html
\-- tag2.html
Estoy usando un archivo .htaccess para ocultar las extensiones .html:
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(.*)$ $1.html
Todo funciona perfectamente, EXCEPTO:
- Si sigo un enlace
domain/article
, muestra la página article.html, pero mi barra de direcciones muestradomain/article
<- Bueno. - Si actualizo, me envían a la dirección:
domain/article/
(nota barra diagonal) que enumera el contenido del directorio del artículo <- Malo (lo mismo con la etiqueta) - De manera similar, escribir manualmente
domain/article
me lleva a endomain/article/
lugar de mostrararticle.html
sin la.html
extensión.
Entonces...
- ¿Cómo puedo solucionar esto?
- ¿Es este un problema de .htaccess?
- ¿Un problema de configuración de nextjs?
- (¿No sería mejor para NextJS crear
article\index.html
un archivo en lugar de un archivo en el directorio raíz?)
exportTrailingSlash
Intenté jugar con lo exportTrailingSlash
que parece relacionado, pero esto creó otros problemas como tener siempre una barra al final de todos mis enlaces:
Por ejemplo: si voy a domain/article/somearticle
y presiono actualizar, algo (.httaccess?) Es agregar un /
al final para que domain/article/somearticle/
no sea horrible, solo que no sea muy limpio e inconsistente ...
Editar: En realidad, es un poco más horrible, porque a veces obtenemos una barra inclinada, a veces no en los enlaces nextjs ... debe haber algo sobre cómo estoy usando,<Link />
pero no puedo entender eso.
Independientemente, NINGUNA de las .htaccess
reglas que he intentado eliminar con éxito la barra inclinada final todo el tiempo cada vez ...
Más detalles:
En mi próxima aplicación, tengo la carpeta:
/articles/
[slug].js
index.js
En varias páginas, uso el componente nextJS Link:
import Link from 'next/link';
<Link href="/articles" as="/articles">
<a>Articles</a>
</Link>
Respuestas
Si lo solicita /article
y /article
existe como un directorio físico, entonces el mod_dir de Apache agregará (de forma predeterminada) la barra al final para "arreglar" la URL. Esto se logra con un redireccionamiento permanente 301, por lo que el navegador lo almacenará en caché.
Aunque tener un directorio físico con el mismo nombre de base que un archivo y usar URL sin extensión crea una ambigüedad. p.ej. Se /article
supone que accede al directorio /article/
o al archivo /article.html
. No parece que desee permitir el acceso directo a los directorios de todos modos, por lo que parecería resolver esa ambigüedad.
Para evitar que Apache mod_dir agregue la barra diagonal al final de los directorios, debemos deshabilitar DirectorySlash
. Por ejemplo:
DirectorySlash Off
Pero como se mencionó, si ya lo ha visitado /article
, el /article/
navegador habrá almacenado en caché la redirección a , por lo que deberá borrar la caché del navegador antes de que esto sea efectivo.
Dado que está eliminando la extensión del archivo, también debe asegurarse de que MultiViews esté deshabilitado; de lo contrario, mod_negotiation emitirá una solicitud secundaria interna para el archivo subyacente y posiblemente entrará en conflicto con mod_rewrite. MultiViews está deshabilitado de forma predeterminada, aunque algunos hosts compartidos lo habilitan por alguna razón. Por la salida que está obteniendo, no parece que MultiViews esté habilitado, pero es mejor estar seguro ...
# Ensure that MutliViews is disabled
Options -MultiViews
Sin embargo, si necesita poder acceder al directorio en sí, deberá agregar manualmente la barra inclinada con una reescritura interna. Aunque esto no parece ser un requisito aquí. Sin embargo, debe asegurarse de que las listas de directorios estén deshabilitadas:
# Disable directory listings
Options -Indexes
Si intenta acceder a cualquier directorio (que en última instancia no se asigne a un archivo, consulte a continuación) y no contiene un DirectoryIndex
documento, se devolverá una respuesta 403 Forbidden, en lugar de una lista de directorios.
Tenga en cuenta que la única diferencia que podría ocurrir entre seguir un enlace a domain/article
, actualizar la página y escribir manualmente domain/article
es el almacenamiento en caché ... ya sea por el navegador o cualquier caché de proxy intermediario. (¡¿A menos que tenga JavaScript que intercepte el evento de clic en el ancla ?!)
Aún necesita reescribir las solicitudes de /foo
a /foo.html
OR /foo
a /foo/index.html
(ver más abajo), dependiendo de cómo haya configurado su sitio. Aunque sería preferible que elijas uno u otro, en lugar de ambos (como parece que podría ser el caso).
RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME}\.html -f RewriteRule ^(.*)$ $1.html
No está claro cómo esto aparentemente "funciona" para usted actualmente, a menos que vea una respuesta en caché. Cuando lo solicita /article
, la primera condición falla porque existe como un directorio físico y la regla no se procesa. Incluso con MultiViews habilitado, mod_dir tendrá prioridad y agregará la barra al final.
La segunda condición que verifica la existencia del .html
archivo no es necesariamente verificar el mismo archivo en el que se está reescribiendo. p.ej. Si solicita /foo/bar
, donde /foo.html
existe, pero no hay un directorio físico, /foo
entonces la RewriteCond
directiva verifica la existencia de /foo.html
, lo cual es exitoso, pero la solicitud se reescribe internamente /foo/bar.html
(desde el RewriteRule
patrón capturado ), esto da como resultado un bucle de reescritura interno y un 500 respuesta de error devuelta al cliente. Vea mi respuesta a la siguiente pregunta de ServerFault que explica con más detalle lo que realmente está sucediendo aquí.
También podemos hacer una optimización adicional si asumimos que cualquier URL que contiene lo que parece ser una extensión de archivo (por ejemplo. Los recursos estáticos .css
, .js
y archivos de imagen) debe ser ignorada, de lo contrario estamos realizando comprobaciones del sistema de archivos en cada petición, que es relativamente caro .
Por lo tanto, para asignar (reescribir internamente) las solicitudes del formulario /article
hacia /article.html
y /article/somearticle
hacia /article/somearticle.html
, deberá modificar la regla anterior para que lea algo como:
# Rewrite /foo to /foo.html if it exists
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.html -f
RewriteRule !\.\w{2,4}$ %{REQUEST_URI}.html [L]
No es necesario RewriteCond
utilizar la barra invertida para escapar de un punto literal en TestString ; el punto no tiene ningún significado especial aquí; no es una expresión regular.
Luego, para manejar las solicitudes del formulario /foo
que deberían asignarse a /foo/index.html
usted, puede hacer algo como lo siguiente:
# 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]
Normalmente, permitiría que mod_dir sirva el DirectoryIndex
(por ejemplo index.html
), pero habiendo omitido la barra al final del directorio, esto puede ser problemático.
Resumen
Juntando los puntos anteriores, tenemos:
# 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]
Esto podría optimizarse aún más, dependiendo de la estructura de su sitio y si está agregando más directivas al .htaccess
archivo. Por ejemplo:
- puede verificar las extensiones de archivo en la URL solicitada en la parte superior del archivo para evitar cualquier procesamiento posterior. La
RewriteRule
expresión regular de cada regla subsiguiente podría entonces "simplificarse". - Las solicitudes que incluyen una barra al final se pueden bloquear o redireccionar (para eliminar la barra al final).
- Si la solicitud es para un
.html
archivo, redirija a la URL sin extensión. Esto se vuelve un poco más complicado si se trata de ambos/foo.html
y/foo/index.html
. Pero esto solo es realmente necesario si está cambiando una estructura de URL existente.
Por ejemplo, implementar los números 1 y 2 anteriores permitiría que las directivas se escribieran así:
# 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]
Pruebe siempre con una redirección 302 (temporal) antes de cambiar a una redirección 301 (permanente) para evitar problemas de almacenamiento en caché.
- (¿No sería mejor para NextJS crear
article\index.html
un archivo en lugar de un archivo en el directorio raíz?)
¡Si! Y Next puede hacer eso por ti:
Es posible configurar Next.js para exportar páginas como archivos index.html y requerir barras finales, se
/about
convierte/about/index.html
y es enrutable a través de/about/
. Este era el comportamiento predeterminado antes de Next.js 9.Para volver atrás y agregar una barra al final, abra
next.config.js
y habilite laexportTrailingSlash
configuración:
module.exports = { exportTrailingSlash: true, }