Ce que la refonte de mon portfolio m'a appris sur les dépendances

4 min de lecture

  • Nuxt
  • Performance
  • Sécurité

Mon portfolio a tenu cinq ans sur une base artisanale : cinq pages HTML écrites à la main, du Sass, la grille de Bootstrap et une configuration Webpack montée pièce par pièce. Il fonctionnait. Deux points ont fini par le rendre coûteux à maintenir : le contenu vivait dans le balisage, et le nombre de domaines extérieurs contactés au chargement avait augmenté sans que je m'en aperçoive.

Cinq domaines pour un site statique

En reprenant l'ancien code, j'ai compté les domaines contactés au chargement d'une page :

  • fonts.googleapis.com et fonts.gstatic.com pour la police,
  • cdn.jsdelivr.net pour une feuille de reset — importée depuis le Sass, donc bloquante au rendu,
  • media.giphy.com pour une image décorative,
  • www.google.com pour un reCAPTCHA rattaché à un formulaire qui, lui, ne fonctionnait plus,
  • instant.page pour du préchargement au survol.

Cinq domaines, aucun sous mon contrôle. En reprenant chaque cas, je n'ai trouvé aucune raison de les garder. Le reCAPTCHA protégeait un formulaire hors service depuis un moment.

L'effet de bord qui valait le détour

J'ai fait le ménage pour le temps de chargement. Le gain que je n'attendais pas est ailleurs : sans ressource externe, la politique de sécurité du contenu peut démarrer à default-src 'none'.

default-src 'none'; script-src 'self' 'sha256-…'; style-src 'self';
img-src 'self' data:; font-src 'self'; connect-src 'self';
base-uri 'none'; form-action 'none'; frame-ancestors 'none'

Tout est interdit par défaut, et seul ce qui vient de mon domaine passe. Cette règle devient intenable dès qu'une seule police est chargée depuis un CDN.

Trois pièges rencontrés en chemin

Une police récupérée sans CDN

Auto-héberger une police suppose d'obtenir les fichiers. Le réflexe est de les télécharger depuis Google Fonts. Sauf que ça recrée une dépendance réseau au moment du build : sans accès à internet, la génération échoue.

Fontsource publie les mêmes polices sur npm. Le fichier arrive par le gestionnaire de paquets et il est versionné dans le dépôt. Rubik en variable pèse 35 Ko et couvre toutes les graisses en une requête.

Des icônes qui appelaient une API distante

Iconify résout les icônes via son API quand la collection n'est pas installée localement. Le paquet était bien présent, mais un repli restait actif et le build sortait vers api.iconify.design sans rien signaler.

La configuration explicite règle le problème :

icon: {
  mode: 'svg',           // SVG inline plutôt que masques CSS
  fallbackToApi: false,  // une icône manquante casse le build
}

fallbackToApi: false est la ligne qui compte. Sans elle, une icône mal orthographiée passe inaperçue en développement et déclenche une requête externe en production.

Un thème sombre sans fond

Le plus difficile à diagnostiquer. Tailwind 4 élague les variables déclarées dans @theme qu'aucune classe n'utilise littéralement. Ma palette de neutres était bien définie, mais comme aucune classe ne s'appelait bg-ink-900, les variables disparaissaient du CSS final, et avec elles les tokens que Nuxt UI en dérive.

Le thème sombre passait donc le texte en blanc et laissait le fond blanc. Du texte invisible, uniquement en mode sombre, uniquement en production.

Le correctif tient en un mot-clé : @theme static conserve toutes les variables, utilisées ou non.

Ce que j'en ai fait

Aucun des trois ne serait apparu dans un test fonctionnel. Le site s'affichait correctement dans les trois cas, ce qui est précisément le problème.

J'ai donc écrit des scripts de vérification. L'un parcourt le HTML généré et échoue si une ressource pointe hors du domaine. Un autre calcule les ratios de contraste de la palette : c'est ainsi que j'ai découvert qu'une de mes teintes plafonnait à 4,49:1, pour un seuil fixé à 4,5.

Ils s'exécutent à chaque modification. Aucune de ces trois erreurs n'était détectable à l'œil, et rien ne garantit qu'elles le deviendraient dans six mois.