Configuración de .htaccess adecuada para Next.js SSG

Jul 09 2020

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 muestra domain/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/articleme lleva a en domain/article/lugar de mostrar article.htmlsin la .htmlextensió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.htmlun archivo en lugar de un archivo en el directorio raíz?)

exportTrailingSlash

Intenté jugar con lo exportTrailingSlashque 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/somearticley 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 .htaccessreglas 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

6 MrWhite Jul 14 2020 at 05:30

Si lo solicita /articley /articleexiste 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 /articlesupone 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 DirectoryIndexdocumento, 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/articlees 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 /fooa /foo.htmlOR /fooa /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 .htmlarchivo no es necesariamente verificar el mismo archivo en el que se está reescribiendo. p.ej. Si solicita /foo/bar, donde /foo.htmlexiste, pero no hay un directorio físico, /fooentonces la RewriteConddirectiva 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, .jsy 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 /articlehacia /article.htmly /article/somearticlehacia /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 /fooque deberían asignarse a /foo/index.htmlusted, 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 .htaccessarchivo. Por ejemplo:

  1. puede verificar las extensiones de archivo en la URL solicitada en la parte superior del archivo para evitar cualquier procesamiento posterior. La RewriteRuleexpresión regular de cada regla subsiguiente podría entonces "simplificarse".
  2. Las solicitudes que incluyen una barra al final se pueden bloquear o redireccionar (para eliminar la barra al final).
  3. Si la solicitud es para un .htmlarchivo, redirija a la URL sin extensión. Esto se vuelve un poco más complicado si se trata de ambos /foo.htmly /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é.

jdaz Jul 11 2020 at 04:00
  • (¿No sería mejor para NextJS crear article\index.htmlun 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 /aboutconvierte /about/index.htmly 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.jsy habilite la exportTrailingSlashconfiguración:

module.exports = { exportTrailingSlash: true, }