Servire un'applicazione Vue.js con nginx
Vue.js è un framework JavaScript progressivo pensato per costruire interfacce utente moderne. Una volta sviluppata e testata localmente, l'applicazione deve essere distribuita su un server web in grado di gestire le richieste HTTP in modo efficiente. nginx è oggi uno dei server web e reverse proxy più diffusi al mondo, apprezzato per le sue prestazioni elevate e la flessibilità di configurazione. In questo articolo vedremo come compilare un'applicazione Vue.js per la produzione e come configurare nginx per servirla correttamente, affrontando anche i problemi tipici legati al routing lato client.
Prerequisiti
Prima di procedere, assicurarsi di avere installati i seguenti strumenti:
- Node.js (versione 18 o superiore) e npm
- Vue CLI oppure un progetto creato con Vite
- Un server Linux con nginx installato (ad esempio Ubuntu 22.04)
- Accesso root o sudo al server
Creare e compilare l'applicazione Vue.js
Se non si dispone ancora di un progetto Vue.js, è possibile crearne uno rapidamente con Vite, il build tool consigliato per i nuovi progetti Vue 3:
# Crea un nuovo progetto con Vite
npm create vite@latest my-app -- --template vue
cd my-app
npm install
Una volta sviluppata l'applicazione, il passo fondamentale è la compilazione per la produzione. Questo processo genera file ottimizzati (HTML, CSS e JavaScript minificati) che possono essere serviti direttamente da nginx.
# Compila l'applicazione per la produzione
npm run build
Al termine del processo, nella radice del progetto comparirà una cartella dist/ contenente tutti i file statici dell'applicazione. La struttura tipica è la seguente:
dist/
├── index.html
├── assets/
│ ├── index-Bx1234ab.js
│ ├── index-Cx9876ef.css
│ └── logo-Dk4567gh.png
└── favicon.ico
I nomi dei file contengono un hash calcolato dal contenuto, il che garantisce che il browser non utilizzi versioni precedenti della cache quando il codice viene aggiornato (cache busting automatico).
Installare e verificare nginx
Su sistemi basati su Debian/Ubuntu, nginx si installa con il gestore di pacchetti di sistema:
# Aggiorna i repository e installa nginx
sudo apt update
sudo apt install nginx -y
# Verifica che il servizio sia attivo
sudo systemctl status nginx
Per verificare che nginx risponda correttamente, aprire un browser e navigare verso l'indirizzo IP del server. Comparirà la pagina di benvenuto predefinita di nginx.
Caricare i file sul server
I file presenti nella cartella dist/ devono essere copiati in una directory accessibile da nginx. La convenzione su sistemi Linux è di usare una sottocartella di /var/www/:
# Crea la directory di destinazione
sudo mkdir -p /var/www/my-app
# Copia i file compilati nella directory del server
sudo cp -r dist/* /var/www/my-app/
# Assegna i permessi corretti all'utente www-data (usato da nginx)
sudo chown -R www-data:www-data /var/www/my-app
sudo chmod -R 755 /var/www/my-app
In alternativa, utilizzando rsync da un ambiente di sviluppo remoto si può automatizzare il trasferimento:
# Trasferisce i file via rsync su un server remoto
rsync -avz --delete dist/ user@your-server-ip:/var/www/my-app/
Configurare nginx per servire l'applicazione
La configurazione di nginx avviene tramite file detti virtual host (o server block), situati in /etc/nginx/sites-available/. Ogni file descrive come nginx deve rispondere alle richieste per un determinato dominio o indirizzo IP.
Creiamo un nuovo file di configurazione per la nostra applicazione:
# Crea il file di configurazione del virtual host
sudo nano /etc/nginx/sites-available/my-app
Inserire la seguente configurazione di base:
server {
# Ascolta sulla porta 80 (HTTP)
listen 80;
# Sostituire con il proprio dominio o indirizzo IP
server_name example.com www.example.com;
# Percorso radice dove risiedono i file statici
root /var/www/my-app;
# File da servire per impostazione predefinita
index index.html;
location / {
# Prova prima il file richiesto, poi la directory,
# infine ricade sempre su index.html (necessario per il routing SPA)
try_files $uri $uri/ /index.html;
}
}
La direttiva try_files $uri $uri/ /index.html è la chiave per il corretto funzionamento delle Single Page Application. Poiché Vue Router gestisce le URL lato client, rotte come /about o /products/42 non corrispondono a file fisici sul server. Senza questa direttiva, nginx restituirebbe un errore 404 per qualsiasi URL diverso dalla radice.
Abilitare il virtual host
In nginx, un virtual host viene abilitato creando un collegamento simbolico dalla cartella sites-available alla cartella sites-enabled:
# Crea il collegamento simbolico per abilitare il virtual host
sudo ln -s /etc/nginx/sites-available/my-app /etc/nginx/sites-enabled/
# Verifica la correttezza della sintassi della configurazione
sudo nginx -t
# Ricarica nginx per applicare le modifiche
sudo systemctl reload nginx
Il comando nginx -t è un controllo fondamentale da eseguire sempre prima di ricaricare il servizio, in quanto individua eventuali errori di sintassi che potrebbero impedire l'avvio di nginx.
Configurazione avanzata: compressione e cache
Per migliorare le prestazioni dell'applicazione in produzione, è opportuno abilitare la compressione gzip e impostare correttamente le intestazioni di cache HTTP. Questo riduce la quantità di dati trasferiti e migliora i tempi di caricamento per gli utenti.
server {
listen 80;
server_name example.com www.example.com;
root /var/www/my-app;
index index.html;
# Abilita la compressione gzip per ridurre la dimensione delle risposte
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
image/svg+xml;
location / {
try_files $uri $uri/ /index.html;
}
# Configurazione cache per gli asset con hash nel nome file
location /assets/ {
# I file con hash possono essere memorizzati in cache per un anno
expires 1y;
add_header Cache-Control "public, immutable";
}
# index.html non deve mai essere messo in cache
location = /index.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
expires 0;
}
}
La strategia di cache differenziata è importante: i file con hash (generati da Vite) possono avere una cache molto lunga perché cambiano nome a ogni aggiornamento del codice, mentre index.html deve essere sempre aggiornato affinché il browser scarichi le versioni più recenti degli asset.
Configurare HTTPS con Certbot e Let's Encrypt
Oggi un sito web in produzione deve essere servito tramite HTTPS. Let's Encrypt offre certificati SSL gratuiti e automatizzati, gestibili tramite lo strumento Certbot.
# Installa Certbot e il plugin per nginx
sudo apt install certbot python3-certbot-nginx -y
# Richiede e installa automaticamente il certificato SSL
sudo certbot --nginx -d example.com -d www.example.com
Certbot modificherà automaticamente il file di configurazione nginx aggiungendo le direttive necessarie per HTTPS e impostando un redirect dal traffico HTTP a HTTPS. Il rinnovo automatico del certificato viene gestito tramite un timer systemd o un'attività cron installata da Certbot stesso.
Per verificare che il rinnovo automatico funzioni correttamente, è possibile simularlo con:
# Simula il rinnovo del certificato senza modifiche effettive
sudo certbot renew --dry-run
Configurare nginx come reverse proxy (opzionale)
Spesso un'applicazione Vue.js comunica con un backend API. Se il backend gira sullo stesso server (ad esempio un'applicazione Node.js sulla porta 3000), è comodo configurare nginx come reverse proxy per indirizzare le richieste alle API evitando problemi di CORS.
server {
listen 80;
server_name example.com;
root /var/www/my-app;
index index.html;
# Serve i file statici dell'applicazione Vue.js
location / {
try_files $uri $uri/ /index.html;
}
# Inoltra le richieste alle API al backend locale
location /api/ {
# Passa le richieste al server backend in ascolto sulla porta 3000
proxy_pass http://127.0.0.1:3000/;
# Intestazioni necessarie per il corretto funzionamento del proxy
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
Con questa configurazione, le chiamate a /api/users dall'applicazione Vue.js verranno inoltrate a http://127.0.0.1:3000/users, mentre tutte le altre richieste verranno gestite come file statici o indirizzate a index.html per il routing SPA.
Intestazioni di sicurezza
Una buona configurazione di produzione include anche intestazioni HTTP che migliorano la sicurezza dell'applicazione, proteggendola da attacchi comuni come il clickjacking, il cross-site scripting e il MIME sniffing:
server {
listen 443 ssl;
server_name example.com;
root /var/www/my-app;
index index.html;
# Impedisce l'inclusione della pagina in iframe su altri domini
add_header X-Frame-Options "SAMEORIGIN" always;
# Attiva il filtro XSS nei browser meno recenti
add_header X-XSS-Protection "1; mode=block" always;
# Impedisce al browser di cambiare il Content-Type dichiarato
add_header X-Content-Type-Options "nosniff" always;
# Forza HTTPS per un anno, inclusi i sottodomini
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Controlla le informazioni inviate nell'intestazione Referer
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location / {
try_files $uri $uri/ /index.html;
}
}
Gestione dei log
nginx genera due tipi di log: il log degli accessi (access.log) e il log degli errori (error.log). Questi file sono essenziali per monitorare il traffico e diagnosticare problemi.
server {
listen 80;
server_name example.com;
root /var/www/my-app;
# Percorso personalizzato per i log di questa applicazione
access_log /var/log/nginx/my-app.access.log;
error_log /var/log/nginx/my-app.error.log warn;
location / {
try_files $uri $uri/ /index.html;
}
}
Per consultare i log in tempo reale durante il debug:
# Visualizza gli ultimi accessi in tempo reale
sudo tail -f /var/log/nginx/my-app.access.log
# Visualizza gli errori più recenti
sudo tail -f /var/log/nginx/my-app.error.log
Automatizzare il deployment
In un flusso di lavoro professionale, il deployment viene automatizzato tramite script o pipeline CI/CD. Di seguito un semplice script Bash che compila l'applicazione e aggiorna i file sul server:
#!/bin/bash
# Variabili di configurazione
REMOTE_USER="deploy"
REMOTE_HOST="your-server-ip"
REMOTE_DIR="/var/www/my-app"
# Installa le dipendenze e compila l'applicazione
echo "Compilazione in corso..."
npm ci
npm run build
# Trasferisce i file sul server e ricarica nginx
echo "Deployment in corso..."
rsync -avz --delete dist/ "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/"
ssh "$REMOTE_USER@$REMOTE_HOST" "sudo systemctl reload nginx"
echo "Deployment completato."
Per eseguire lo script è sufficiente renderlo eseguibile e avviarlo dalla radice del progetto:
# Rende il file eseguibile
chmod +x deploy.sh
# Esegue il deployment
./deploy.sh
Risoluzione dei problemi comuni
Errore 404 navigando direttamente a una rotta: il problema più frequente con le SPA. La causa è l'assenza della direttiva try_files $uri $uri/ /index.html nella configurazione. Verificare che sia presente nel blocco location /.
Pagina bianca dopo il deployment: spesso causato da percorsi degli asset errati. Verificare che la variabile base nel file vite.config.js corrisponda al percorso in cui l'applicazione è servita. Se l'app è sulla radice del dominio, il valore predefinito / è corretto.
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
// Modificare se l'app è servita in una sottodirectory, es. '/my-app/'
base: '/',
})
nginx restituisce 403 Forbidden: il processo nginx non ha i permessi per leggere i file. Rieseguire il comando chown e verificare che i permessi siano impostati correttamente (755 per le directory, 644 per i file).
# Corregge i permessi in modo ricorsivo
sudo chown -R www-data:www-data /var/www/my-app
sudo find /var/www/my-app -type d -exec chmod 755 {} \;
sudo find /var/www/my-app -type f -exec chmod 644 {} \;
Modifiche non visibili dopo il deployment: il browser potrebbe stare utilizzando la cache. Verificare che le intestazioni Cache-Control siano configurate correttamente per index.html e forzare un hard refresh nel browser con Ctrl+Shift+R.
Conclusioni
Configurare nginx per servire un'applicazione Vue.js richiede pochi passaggi ma è fondamentale comprenderli nel dettaglio, soprattutto per quanto riguarda il routing SPA e la gestione della cache. Una configurazione ben strutturata garantisce prestazioni elevate, sicurezza adeguata e un flusso di aggiornamento affidabile. Con l'aggiunta di HTTPS tramite Let's Encrypt, della compressione gzip e delle intestazioni di sicurezza, si ottiene una configurazione robusta e pronta per un ambiente di produzione reale.