Configurazione .htaccess corretta per SSG Next.js

Jul 09 2020

NextJS esporta un sito statico con la seguente struttura:


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

Sto usando un file .htaccess per nascondere le estensioni .html:

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

Tutto funziona perfettamente, TRANNE:

  • Se seguo un collegamento ad domain/articleesso viene visualizzata la pagina article.html, ma la mia barra degli indirizzi mostra domain/article<- Buono.
  • Se aggiorno, vengo inviato all'indirizzo: domain/article/(nota barra finale) che elenca il contenuto della directory dell'articolo <- Cattivo (stessa cosa con Tag)
  • Allo stesso modo, la digitazione manuale domain/articlemi porta a domain/article/invece di mostrare article.htmlsenza l' .htmlestensione.

Così...

  • Come lo risolvo?
  • È un problema di .htaccess?
  • Un problema di configurazione di Nextjs?
  • (Non sarebbe meglio per NextJS creare un article\index.htmlinvece di un file nella directory principale?)

exportTrailingSlash

Ho provato a giocare con ciò exportTrailingSlashche sembra correlato, ma questo ha creato altri problemi come avere sempre una barra finale alla fine di tutti i miei collegamenti:

Ad esempio: se vado su domain/article/somearticlee premo Aggiorna, qualcosa (.httaccess?) Aggiunge un /alla fine per domain/article/somearticle/non darmi orribile, solo non molto pulito e incoerente ...

Modifica: In realtà, è un po 'più orribile, perché a volte otteniamo una barra finale, a volte non lo facciamo sui link nextjs ... deve essere qualcosa su come sto usando<Link />ma non riesco a capirlo.

Indipendentemente da ciò, NESSUNA delle .htaccessregole che ho provato a rimuovere con successo la barra finale tutto il tempo ogni volta ...


Più dettagli:

Nella mia prossima app, ho una cartella:

/articles/
   [slug].js
   index.js

In varie pagine, utilizzo il componente NextJS Link:

import Link from 'next/link';

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

Risposte

6 MrWhite Jul 14 2020 at 05:30

Se richiedi /articleed /articleesiste come una directory fisica, mod_dir di Apache aggiungerà (per impostazione predefinita) la barra finale per "correggere" l'URL. Ciò si ottiene con un reindirizzamento permanente 301, quindi verrà memorizzato nella cache dal browser.

Anche se avere una directory fisica con lo stesso nome di base di un file e l'utilizzo di URL senza estensione crea un'ambiguità. per esempio. È /articlesupposto per accedere alla directory /article/o il file /article.html. In ogni caso, non sembra che tu voglia consentire l'accesso diretto alle directory, quindi questo sembrerebbe risolvere tale ambiguità.

Per impedire ad Apache mod_dir di aggiungere la barra finale alle directory, è necessario disabilitare il file DirectorySlash. Per esempio:

DirectorySlash Off

Ma come accennato, se hai visitato in precedenza, /articleil reindirizzamento a /article/sarà stato memorizzato nella cache dal browser, quindi dovrai svuotare la cache del browser prima che sia efficace.

Poiché stai rimuovendo l'estensione del file, devi anche assicurarti che MultiViews sia disabilitato, altrimenti mod_negotiation emetterà una sottorichiesta interna per il file sottostante e potrebbe entrare in conflitto con mod_rewrite. MultiViews è disabilitato per impostazione predefinita, sebbene alcuni host condivisi lo abilitino per qualche motivo. Dall'output che stai ottenendo non sembra che MultiViews sia abilitato, ma è meglio essere sicuri ...

# Ensure that MutliViews is disabled
Options -MultiViews

Tuttavia, se è necessario essere in grado di accedere alla directory stessa, sarà necessario aggiungere manualmente la barra finale con una riscrittura interna. Anche se questo non sembra essere un requisito qui. Tuttavia, dovresti assicurarti che gli elenchi di directory siano disabilitati:

# Disable directory listings
Options -Indexes

Il tentativo di accedere a qualsiasi directory (che alla fine non si associa a un file - vedi sotto) e non contiene un DirectoryIndexdocumento restituirà una risposta 403 Forbidden, invece di un elenco di directory.

Si noti che l'unica differenza che potrebbe verificarsi tra seguire un collegamento a domain/article, aggiornare la pagina e digitare manualmente domain/articleè la memorizzazione nella cache ... tramite il browser o qualsiasi cache proxy intermedia. (A meno che tu non abbia JavaScript che intercetta l'evento click sull'ancoraggio ?!)

Hai ancora bisogno di riscrivere le richieste da /fooa /foo.htmlOR /fooa /foo/index.html(vedi sotto), a seconda di come hai configurato il tuo sito. Anche se sarebbe preferibile che tu scelga l'uno o l'altro, piuttosto che entrambi (come sembra implicare potrebbe essere il caso).

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

Non è chiaro come questo stia apparentemente "funzionando" per te attualmente - a meno che tu non stia vedendo una risposta memorizzata nella cache? Quando richiedi /article, la prima condizione ha esito negativo perché esiste come directory fisica e la regola non viene elaborata. Anche con MultiViews abilitato, mod_dir avrà la priorità e aggiungerà la barra finale.

La seconda condizione che verifica l'esistenza del .htmlfile non è necessariamente il controllo dello stesso file in cui viene riscritto. per esempio. Se richiedi /foo/bar, dove /foo.htmlesiste, ma non esiste una directory fisica, /foola RewriteConddirettiva verifica l'esistenza di /foo.html- che ha esito positivo, ma la richiesta viene riscritta internamente /foo/bar.html(dal RewriteRule modello catturato ) - questo si traduce in un ciclo di riscrittura interno e un 500 risposta di errore restituita al client. Vedi la mia risposta alla seguente domanda ServerFault che va più in dettaglio dietro ciò che sta effettivamente accadendo qui.

Possiamo anche fare un ulteriore ottimizzazione se si assume che qualsiasi URL che contiene quello che sembra un estensione del file (ad es. Le risorse statiche .css, .jse file di immagine) dovrebbe essere ignorata, altrimenti stiamo eseguendo controlli del file system su ogni richiesta, che è relativamente costoso .

Quindi, per mappare (riscrivere internamente) le richieste del modulo /articlesu /article.htmle /article/somearticleverso, /article/somearticle.htmldovresti modificare la regola sopra per leggere qualcosa come:

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

Non è necessario che la barra rovesciata sfugga a un punto letterale in RewriteCond TestString : il punto non ha alcun significato speciale qui; non è una regex.

Quindi, per gestire le richieste del modulo /fooche dovrebbe mappare a /foo/index.htmlte puoi fare qualcosa di simile a quanto segue:

# 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, permetteresti a mod_dir di servire il DirectoryIndex(es. index.html), Ma avendo omesso la barra finale dalla directory, questo può essere problematico.

Sommario

Riunendo i punti precedenti, abbiamo:

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

Questo potrebbe essere ulteriormente ottimizzato, a seconda della struttura del tuo sito e se stai aggiungendo altre direttive al .htaccessfile. Per esempio:

  1. è possibile verificare la presenza di estensioni di file sull'URL richiesto all'inizio del file per impedire ulteriori elaborazioni. La RewriteRuleregex su ogni regola successiva potrebbe quindi essere "semplificata".
  2. Le richieste che includono una barra finale potrebbero essere bloccate o reindirizzate (per rimuovere la barra finale).
  3. Se la richiesta è per un .htmlfile, reindirizza all'URL senza estensione. Questo è leggermente più complicato se hai a che fare con entrambi /foo.htmle /foo/index.html. Ma questo è veramente necessario solo se stai modificando una struttura URL esistente.

Ad esempio, l'implementazione di # 1 e # 2 sopra, consentirebbe di scrivere le direttive in questo modo:

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

Prova sempre con un reindirizzamento 302 (temporaneo) prima di passare a un reindirizzamento 301 (permanente) per evitare problemi di memorizzazione nella cache.

jdaz Jul 11 2020 at 04:00
  • (Non sarebbe meglio per NextJS creare un article\index.htmlinvece di un file nella directory principale?)

Sì! E Next può farlo per te:

È possibile configurare Next.js per esportare pagine come file index.html e richiedere barre finali, /aboutdiventa /about/index.htmled è instradabile tramite /about/. Questo era il comportamento predefinito prima di Next.js 9.

Per tornare indietro e aggiungere una barra finale, apri next.config.jse abilita la exportTrailingSlashconfigurazione:

module.exports = { exportTrailingSlash: true, }