Configuration .htaccess correcte pour Next.js SSG

Jul 09 2020

NextJS exporte un site statique avec la structure suivante:


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

J'utilise un fichier .htaccess pour masquer les extensions .html:

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

Tout fonctionne parfaitement, SAUF:

  • Si je suis un lien vers domain/articlecelui-ci, la page article.html s'affiche, mais ma barre d'adresse affiche domain/article<- Bien.
  • Si je rafraîchis, je suis envoyé à l'adresse: domain/article/(notez la barre oblique de fin) qui répertorie le contenu du répertoire de l'article <- Mauvais (même chose avec Tag)
  • De même, la saisie manuelle domain/articlem'amène à domain/article/au lieu de s'afficher article.htmlsans l' .htmlextension.

Alors...

  • Comment puis-je réparer ça?
  • Est-ce un problème .htaccess?
  • Un problème de configuration nextjs?
  • (Ne serait-il pas préférable que NextJS crée article\index.htmlun fichier au lieu d'un fichier dans le répertoire racine?)

exportTrailingSlash

J'ai essayé de jouer avec exportTrailingSlashce qui semble lié, mais cela a créé d'autres problèmes comme toujours avoir une barre oblique à la fin de tous mes liens:

Par exemple: si je vais sur domain/article/somearticleet appuie sur rafraîchir, quelque chose (.httaccess?) Ajoute un /à la fin pour me donner domain/article/somearticle/pas horrible, juste pas très propre et incohérent ...

Edit: En fait, c'est un peu plus horrible, parce que parfois nous obtenons une barre oblique à la fin, parfois nous ne le faisons pas sur les liens nextjs ... ça doit être quelque chose sur la façon dont j'utilise<Link />mais je ne peux pas le comprendre.

Quoi qu'il en soit, AUCUNE des .htaccessrègles que j'ai essayées avec succès ne supprime la barre oblique de fin à chaque fois ...


Plus de détails:

Dans ma prochaine application, j'ai un dossier:

/articles/
   [slug].js
   index.js

Dans diverses pages, j'utilise le composant nextJS Link:

import Link from 'next/link';

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

Réponses

6 MrWhite Jul 14 2020 at 05:30

Si vous demandez /articleet /articleexiste en tant que répertoire physique, le mod_dir d'Apache ajoutera (par défaut) la barre oblique de fin afin de "corriger" l'URL. Ceci est réalisé avec une redirection permanente 301 - il sera donc mis en cache par le navigateur.

Bien que le fait d'avoir un répertoire physique avec le même nom de base qu'un fichier et d'utiliser des URL sans extension crée une ambiguïté. par exemple. Est /articlecensé accéder au répertoire /article/ou au fichier /article.html. Vous ne semblez pas vouloir autoriser l'accès direct aux répertoires de toute façon, ce qui semble résoudre cette ambiguïté.

Pour empêcher Apache mod_dir d'ajouter la barre oblique de fin aux répertoires, nous devons désactiver le DirectorySlash. Par exemple:

DirectorySlash Off

Mais comme mentionné, si vous avez déjà visité, /articlela redirection vers /article/aura été mise en cache par le navigateur - vous devrez donc vider le cache du navigateur avant que cela ne soit effectif.

Puisque vous supprimez l'extension de fichier, vous devez également vous assurer que MultiViews est désactivé, sinon mod_negotiation émettra une sous-requête interne pour le fichier sous-jacent, et potentiellement en conflit avec mod_rewrite. MultiViews est désactivé par défaut, bien que certains hôtes partagés l'activent pour une raison quelconque. D'après la sortie que vous obtenez, il ne semble pas que MultiViews soit activé, mais mieux vaut être sûr ...

# Ensure that MutliViews is disabled
Options -MultiViews

Cependant, si vous devez pouvoir accéder au répertoire lui-même, vous devrez ajouter manuellement la barre oblique de fin avec une réécriture interne. Bien que cela ne semble pas être une exigence ici. Vous devez cependant vous assurer que les listes de répertoires sont désactivées:

# Disable directory listings
Options -Indexes

Tenter d'accéder à un répertoire (qui ne correspond finalement pas à un fichier - voir ci-dessous) et ne contient pas de DirectoryIndexdocument renverra une réponse 403 Forbidden, au lieu d'une liste de répertoires.

Notez que la seule différence qui pourrait se produire entre le suivi d'un lien vers domain/article, l'actualisation de la page et la saisie manuelle domain/articleest la mise en cache ... soit par le navigateur, soit par des caches proxy intermédiaires. (Sauf si vous avez JavaScript qui intercepte l'événement de clic sur l'ancre?!)

Vous devez toujours réécrire les demandes de /fooà /foo.htmlOU /fooà /foo/index.html(voir ci-dessous), selon la façon dont vous avez configuré votre site. Bien qu'il soit préférable que vous choisissiez l'un ou l'autre, plutôt que les deux (comme vous semblez le laisser entendre, cela pourrait être le cas).

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

On ne sait pas comment cela "fonctionne" pour vous actuellement - à moins que vous ne voyiez une réponse mise en cache? Lorsque vous demandez /article, la première condition échoue car il existe en tant que répertoire physique et la règle n'est pas traitée. Même avec MultiViews activé, mod_dir aura la priorité et ajoutera la barre oblique de fin.

La deuxième condition qui vérifie l'existence du .htmlfichier ne vérifie pas nécessairement le même fichier qui est réécrit. par exemple. Si vous demandez /foo/bar, où /foo.htmlexiste, mais qu'il n'y a pas de répertoire physique, /foola RewriteConddirective vérifie l'existence de /foo.html- ce qui réussit, mais la demande est réécrite en interne /foo/bar.html(à partir du RewriteRule modèle capturé ) - cela se traduit par une boucle de réécriture interne et un 500 réponse d'erreur renvoyée au client. Voir ma réponse à la question ServerFault suivante qui entre plus en détail sur ce qui se passe réellement ici.

Nous pouvons également faire une nouvelle optimisation si nous supposons que toute URL contenant ce qui ressemble à une extension de fichier (par exemple. Vos ressources statiques .css, .jset les fichiers d'image) doit être ignorée, sinon nous effectuons des vérifications du système de fichiers sur chaque demande, ce qui est relativement cher .

Ainsi, afin de mapper (réécrire en interne) les demandes du formulaire /articlevers /article.htmlet /article/somearticlevers, /article/somearticle.htmlvous devez modifier la règle ci-dessus pour lire quelque chose comme:

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

Il n'est pas nécessaire de faire échapper une barre oblique inverse à un point littéral dans la RewriteCond TestString - le point n'a pas de signification particulière ici; ce n'est pas une regex.

Ensuite, pour gérer les demandes du formulaire /fooqui doivent correspondre, /foo/index.htmlvous pouvez faire quelque chose comme ce qui suit:

# 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]

Normalement, vous autorisez mod_dir à servir le DirectoryIndex(par exemple index.html), mais ayant omis la barre oblique de fin du répertoire, cela peut être problématique.

Sommaire

En réunissant les points ci-dessus, nous avons:

# 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]

Cela pourrait être optimisé davantage, selon la structure de votre site et si vous ajoutez d'autres directives au .htaccessfichier. Par exemple:

  1. vous pouvez vérifier les extensions de fichier sur l'URL demandée en haut du fichier pour empêcher tout traitement ultérieur. L' RewriteRuleexpression régulière sur chaque règle suivante pourrait alors être "simplifiée".
  2. Les demandes qui incluent une barre oblique de fin peuvent être bloquées ou redirigées (pour supprimer la barre oblique de fin).
  3. Si la demande concerne un .htmlfichier, redirigez-vous vers l'URL sans extension. Cela est un peu plus compliqué si vous traitez à la fois avec /foo.htmlet /foo/index.html. Mais cela n'est vraiment nécessaire que si vous modifiez une structure d'URL existante.

Par exemple, implémenter les numéros 1 et 2 ci-dessus permettrait d'écrire les directives comme suit:

# 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]

Testez toujours avec une redirection 302 (temporaire) avant de passer à une redirection 301 (permanente) afin d'éviter les problèmes de mise en cache.

jdaz Jul 11 2020 at 04:00
  • (Ne serait-il pas préférable que NextJS crée article\index.htmlun fichier au lieu d'un fichier dans le répertoire racine?)

Oui! Et Next peut le faire pour vous:

Il est possible de configurer Next.js pour exporter des pages en tant que fichiers index.html et exiger des barres obliques de fin, /aboutdevient /about/index.htmlet est routable via /about/. C'était le comportement par défaut avant Next.js 9.

Pour revenir en arrière et ajouter une barre oblique à la fin, ouvrez next.config.jset activez la exportTrailingSlashconfiguration:

module.exports = { exportTrailingSlash: true, }