Retour au cours

infra / nginx

Fichiers statiques et cache

Leçon 61 exercice

Explication

Ce que vous allez apprendre

  • Utiliser try_files pour tester plusieurs chemins dans l'ordre avant de renvoyer une erreur
  • Configurer un cache long et sûr pour des assets versionnés (avec un hash dans le nom)
  • Comprendre pourquoi le HTML ne doit jamais être mis en cache longtemps
  • Servir correctement une SPA (Single Page Application) avec un fallback vers index.html
  • Réduire la charge CPU/syscalls avec sendfile, tcp_nopush et open_file_cache

Dans quel contexte ?

Une équipe frontend déploie une application React buildée en fichiers statiques (app.a1b2c3.js, index.html) sur un serveur Nginx. Elle veut que les assets versionnés (avec un hash unique dans leur nom) soient mis en cache très longtemps par le navigateur, mais que chaque nouveau déploiement soit immédiatement visible pour tous les utilisateurs, sans qu'ils aient besoin de vider leur cache manuellement.

D'abord, comprendre try_files, la commande centrale de cette leçon

try_files $uri $uri/ =404; teste chaque chemin dans l'ordre donné : d'abord le fichier exact demandé, puis ce même chemin comme dossier (utile pour un index.html implicite), et enfin retourne une erreur 404 si rien n'a été trouvé. C'est un mécanisme de fallback en cascade, pas une simple vérification d'existence.

Une fois ce mécanisme compris, un cas particulier mérite un fallback différent

Une application React ou Vue gère son propre routage côté client (/produits/42 n'existe pas comme fichier réel sur le disque). try_files $uri $uri/ /index.html; renvoie systématiquement index.html pour toute route inconnue du système de fichiers, laissant le JavaScript de l'application décider quoi afficher — c'est la configuration standard pour toute SPA.

Ensuite, la question du cache se pose différemment selon le type de fichier

Un fichier avec un hash dans son nom (app.a1b2c3.js) ne changera jamais de contenu sous ce nom précis : un nouveau déploiement génère un nouveau hash, donc un nouveau nom de fichier. On peut donc lui appliquer un cache extrêmement long et agressif (Cache-Control: public, immutable), sans aucun risque.

Type de contenuStratégie de cacheRaison
Assets versionnés (app.a1b2c3.js)expires 1y + immutableLe nom change à chaque nouveau contenu
Images sans hashexpires 30d, sans immutablePeuvent changer sans que le nom change
Fichiers HTMLexpires -1 + no-cache, must-revalidateDoit toujours refléter le dernier déploiement

Il reste un piège classique à éviter absolument

Mettre en cache le HTML avec la même agressivité que les assets versionnés casse totalement le mécanisme de déploiement : les utilisateurs continueraient à charger une vieille page HTML référençant d'anciens fichiers JS supprimés, bien après un nouveau déploiement. Le HTML doit toujours être revalidé, jamais mis en cache longtemps.

Piège fréquent

Appliquer Cache-Control: public, immutable sur un fichier image dont le nom ne change jamais (comme logo.png) empêche toute mise à jour de cette image côté navigateur, même après un nouveau déploiement qui la remplace : les visiteurs verront l'ancienne image pendant toute la durée du cache. Réserve immutable aux fichiers réellement versionnés par leur nom.

Prérequis

Cette leçon suppose que tu es à l'aise avec les location et leurs priorités (leçon 3) : les règles de cache s'appliquent typiquement via des location basées sur l'extension du fichier.

Enfin, un dernier levier de performance, plus bas niveau

sendfile on; permet au noyau Linux de copier un fichier directement vers la socket réseau sans repasser par l'espace utilisateur, tcp_nopush regroupe les paquets avant envoi, et open_file_cache évite de rouvrir les mêmes descripteurs de fichiers à chaque requête. Ces réglages réduisent la charge CPU pour du contenu statique à fort volume.

Maintenant que le service de fichiers statiques est optimisé, la prochaine leçon aborde un sujet indissociable de toute mise en production sérieuse : sécuriser le trafic avec HTTPS et TLS.

Commandes & code

Fichiers statiques et cache

nginx
server {
    listen 80;
    server_name static.example.com;
    root /var/www/static;

    # try_files : essaie chaque chemin dans l'ordre, sert le premier qui existe
    location / {
        try_files $uri $uri/ =404;
    }

    # Cache long pour les assets versionnés (hash dans le nom : app.a1b2c3.js)
    location ~* \.(js|css|woff2?|ttf|svg)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Images : cache modéré, pas "immutable" (peuvent changer sans hash dans le nom)
    location ~* \.(jpg|jpeg|png|gif|webp|ico)$ {
        expires 30d;
        add_header Cache-Control "public";
    }

    # HTML : jamais de cache long (sinon les déploiements ne se propagent pas)
    location ~* \.html$ {
        expires -1;
        add_header Cache-Control "no-cache, must-revalidate";
    }
}
nginx
# Servir un SPA (React/Vue/Next export statique) : fallback vers index.html pour le routing côté client
server {
    listen 80;
    server_name app.example.com;
    root /var/www/app/dist;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;   # toute route inconnue retombe sur index.html
    }

    location /assets/ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;                      # évite de polluer les logs pour chaque asset
    }
}
nginx
# sendfile / tcp_nopush / tcp_nodelay : optimisent l'envoi de fichiers statiques au niveau kernel
http {
    sendfile on;          # copie fichier -> socket directement dans le kernel (évite un aller-retour userspace)
    tcp_nopush on;         # regroupe les paquets avant envoi (efficace avec sendfile)
    tcp_nodelay on;          # désactive l'algorithme de Nagle pour les petites réponses (latence)

    open_file_cache max=2000 inactive=20s;         # cache les descripteurs de fichiers ouverts
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;                       # cache aussi les "fichier non trouvé"
}
nginx
# ETag et Last-Modified : validation de cache conditionnelle (304 Not Modified)
location /downloads/ {
    etag on;                 # activé par défaut, génère un ETag basé sur mtime+taille
    if_modified_since exact; # comparaison stricte de la date (défaut : "before", plus permissif)
}

Résumé

  • try_files teste des chemins dans l'ordre : indispensable pour les SPA (try_files $uri $uri/ /index.html;).
  • Assets versionnés (hash dans le nom) : Cache-Control: public, immutable + expires 1y sans risque.
  • HTML jamais mis en cache longtemps : sinon les nouveaux déploiements restent invisibles pour les clients.
  • sendfile, tcp_nopush, open_file_cache réduisent la charge CPU/syscalls pour le service de fichiers statiques.

Exercices pratiques

1 disponible
1

Mission : réparer une SPA qui affiche du contenu périmé après déploiement

Objectif : Configurer try_files pour le routage côté client d'une SPA et appliquer la bonne stratégie de cache par type de fichier.

Contexte

Une SPA React est déployée sur /var/www/app/dist. Actuellement, location / { try_files $uri $uri/ =404; } fait que toute route interne comme /produits/42 renvoie une 404 (aucun fichier réel n'existe à ce chemin). Par ailleurs, un développeur a appliqué Cache-Control: public, immutable avec expires 1y sur TOUT le dossier, y compris index.html, si bien que les visiteurs voient une version de l'application vieille de plusieurs déploiements.

Résoudre l’exercice →