[{"data":1,"prerenderedAt":481},["ShallowReactive",2],{"i-lucide:sun":3,"i-lucide:moon":8,"i-lucide:menu":10,"i-lucide:x":12,"i-social:linkedin":14,"i-social:github":17,"i-social:dailydev":21,"accueil-projets":23,"accueil-projets-repli":151,"accueil-parcours":200,"accueil-article":237,"i-lucide:folder-open":475,"i-lucide:arrow-right":477,"i-lucide:mail":479},{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":7},0,24,false,"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"4\"\u002F>\u003Cpath d=\"M12 2v2m0 16v2M4.93 4.93l1.41 1.41m11.32 11.32l1.41 1.41M2 12h2m16 0h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":9},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":11},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 5h16M4 12h16M4 19h16\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":13},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M18 6L6 18M6 6l12 12\"\u002F>",{"left":4,"top":4,"width":15,"height":15,"rotate":4,"vFlip":6,"hFlip":6,"body":16},44.856,"\u003Cpath fill=\"currentColor\" d=\"M39.376,0a5.483,5.483,0,0,1,5.48,5.48v33.9a5.483,5.483,0,0,1-5.48,5.48H5.48A5.483,5.483,0,0,1,0,39.376V5.48A5.483,5.483,0,0,1,5.48,0h33.9ZM14.058,37.084V17.308H7.484V37.084Zm23.7,0V25.743c0-6.074-3.243-8.9-7.568-8.9a6.529,6.529,0,0,0-5.924,3.265v-2.8H17.7c.087,1.856,0,19.775,0,19.775h6.573V26.04a4.494,4.494,0,0,1,.217-1.6,3.6,3.6,0,0,1,3.372-2.4c2.377,0,3.33,1.814,3.33,4.471V37.084H37.76ZM10.815,7.773a3.426,3.426,0,1,0-.087,6.834h.042a3.428,3.428,0,1,0,.045-6.834Z\"\u002F>",{"left":4,"top":4,"width":18,"height":19,"rotate":4,"vFlip":6,"hFlip":6,"body":20},46.142,44.459,"\u003Cpath fill=\"currentColor\" d=\"M55.071,32a22.76,22.76,0,0,0-7.292,44.422c1.154.208,1.576-.5,1.576-1.1,0-.546-.021-1.975-.031-3.88-6.417,1.379-7.776-3.057-7.776-3.057a6.079,6.079,0,0,0-2.565-3.335c-2.091-1.419.154-1.389.154-1.389a4.825,4.825,0,0,1,3.533,2.352c2.06,3.483,5.4,2.481,6.715,1.9a4.829,4.829,0,0,1,1.463-3.047c-5.119-.576-10.506-2.531-10.506-11.264a8.739,8.739,0,0,1,2.369-6.113,8.107,8.107,0,0,1,.227-6.034s1.936-.615,6.345,2.332a22.227,22.227,0,0,1,11.556,0c4.408-2.948,6.334-2.332,6.334-2.332a8.107,8.107,0,0,1,.227,6.034A8.775,8.775,0,0,1,69.769,53.6c0,8.753-5.4,10.679-10.536,11.244A5.418,5.418,0,0,1,60.8,69.058c0,3.047-.031,5.508-.031,6.252,0,.605.412,1.32,1.586,1.092A22.747,22.747,0,0,0,55.071,32Z\" transform=\"translate(-32 -32)\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":22},"\u003Cpath fill=\"currentColor\" d=\"M18.29 5.706a1.405 1.405 0 0 0-1.987 0L4.716 17.296l1.324-2.65-2.65-2.649 3.312-3.311 2.65 2.65 1.986-1.988-3.642-3.642a1.405 1.405 0 0 0-1.987 0L.411 11.004a1.404 1.404 0 0 0 0 1.987l4.305 4.304.993.993a1.405 1.405 0 0 0 1.987 0L19.285 6.7l-.993-.994Zm-.332 3.647 2.65 2.65-4.306 4.305a1.404 1.404 0 1 0 1.986 1.986l5.299-5.298a1.404 1.404 0 0 0 0-1.987l-4.305-4.304-1.324 2.648Z\"\u002F>",[24,76,117],{"id":25,"title":26,"body":27,"context":50,"contributions":51,"cover":54,"coverAlt":55,"description":33,"extension":56,"featured":57,"links":58,"meta":60,"navigation":57,"order":61,"outcome":62,"path":63,"period":64,"role":65,"seo":66,"stack":67,"status":72,"stem":73,"summary":74,"__hash__":75},"projets\u002Fprojets\u002Fkosmood.md","Kosmood",{"type":28,"value":29,"toc":46},"minimark",[30,34,37],[31,32,33],"p",{},"Kosmood est allé plus loin que les autres projets de cette période : il a été\nmis en production, avec ce que cela suppose de déploiements, de montées de\nversion et de correctifs sur du code déjà utilisé.",[31,35,36],{},"Le back-end Symfony expose une API, le front-end Vue.js la consomme. Le moteur\nde templates de Symfony aurait suffi pour une première version et représentait\nmoins de travail. Nous avons préféré séparer les deux, ce qui a permis de les\nfaire évoluer indépendamment par la suite.",[31,38,39,40,45],{},"L'infrastructure Docker du projet fait l'objet d'",[41,42,44],"a",{"href":43},"\u002Fprojets\u002Fdocker-kosmood","une fiche à\npart",".",{"title":47,"searchDepth":48,"depth":48,"links":49},"",3,[],"Projet d'équipe mené dans un cadre scolaire, puis mis en production. L'équipe a choisi sa stack et son organisation, sans consigne imposée.",[52,53],"TODO: ce que tu as développé précisément (modules, API, écrans…)","TODO: ta part dans les choix techniques et l’organisation","\u002Fimages\u002Fprojets\u002Fkosmood.jpg","Capture d'écran de l'interface de Kosmood","md",true,{"demo":59},"https:\u002F\u002Fwww.kosmood.com",{},1,"TODO: le résultat concret — mise en production, nombre d'utilisateurs, ce que le projet a changé dans ta pratique.","\u002Fprojets\u002Fkosmood","TODO: préciser la période, par exemple 2020 — 2021","Développeur full-stack, en équipe",{"title":26,"description":33},[68,69,70,71],"Symfony 5","PHP 8","Vue.js","Vuetify","live","projets\u002Fkosmood","Application web de gestion mise en production, développée en équipe avec Symfony et Vue.js.","sogPeD6E5U0Hnr8vTTxYjBNu4aioDnx-7oqQZ4BOHCc",{"id":77,"title":78,"body":79,"context":94,"contributions":95,"cover":99,"coverAlt":100,"description":83,"extension":56,"featured":57,"links":101,"meta":103,"navigation":57,"order":104,"outcome":105,"path":43,"period":106,"role":107,"seo":108,"stack":109,"status":72,"stem":114,"summary":115,"__hash__":116},"projets\u002Fprojets\u002Fdocker-kosmood.md","Infrastructure Docker pour Kosmood",{"type":28,"value":80,"toc":92},[81,84],[31,82,83],{},"La configuration passe par des variables d'environnement, et non par un fichier\ndupliqué puis modifié à la main sur chaque machine. Même image, mêmes services,\nseuls les paramètres changent.",[31,85,86,87,91],{},"Le ",[88,89,90],"code",{},"docker-compose.yml"," n'a rien de remarquable en soi. Son intérêt est d'avoir\nrapproché la configuration du serveur de celle des postes de travail.",{"title":47,"searchDepth":48,"depth":48,"links":93},[],"À plusieurs sur une application Symfony et Vue.js, sans environnement commun, on finit par passer plus de temps à comprendre pourquoi le code tourne chez l'un et pas chez l'autre qu'à écrire des fonctionnalités.",[96,97,98],"Découpage en services et rédaction des Dockerfile.","Orchestration via Docker Compose, avec configuration par variables d'environnement.","Documentation de la procédure de démarrage pour l'équipe.","\u002Fimages\u002Fprojets\u002Fdocker-kosmood.jpg","Logo de Docker",{"repo":102},"https:\u002F\u002Fgithub.com\u002FBaptisteCrouzet\u002FDocker-for-Kosmood",{},4,"Le même environnement pour tout le monde, montable en une commande. Le déploiement reprend la même configuration.","TODO: année","Conception et réalisation, seul",{"title":78,"description":83},[110,111,112,113],"Docker","Docker Compose","NGINX","PHP-FPM","projets\u002Fdocker-kosmood","Environnement conteneurisé reproductible pour le développement et le déploiement de Kosmood.","E2H18dLneMrrj3P8iYbdG_X-vnrf5iX11J_yu4pqU3o",{"id":118,"title":119,"body":120,"context":130,"contributions":131,"cover":134,"coverAlt":135,"description":124,"extension":56,"featured":57,"links":136,"meta":138,"navigation":57,"order":139,"outcome":140,"path":141,"period":142,"role":143,"seo":144,"stack":145,"status":72,"stem":148,"summary":149,"__hash__":150},"projets\u002Fprojets\u002Ftheme-vscode.md","Dark Theme The New",{"type":28,"value":121,"toc":128},[122,125],[31,123,124],{},"Le thème tient dans un fichier JSON de quelques centaines de lignes.\nL'essentiel du travail a consisté à décider quoi colorer : un mot-clé, un nom\nde variable et un commentaire ne se lisent pas avec la même attention, et la\ncouleur est le principal levier pour le signaler.",[31,126,127],{},"Le parti pris est donc restrictif : peu de teintes, réservées à ce qui porte du\nsens, et un contraste suffisant pour une lecture prolongée.",{"title":47,"searchDepth":48,"depth":48,"links":129},[],"Aucun thème existant ne me convenait vraiment : trop de couleurs en concurrence, ou pas assez de contraste sur les commentaires et les chaînes de caractères.",[132,133],"Définition de la palette et des règles de coloration syntaxique.","Publication et maintenance de l'extension sur le Marketplace.","\u002Fimages\u002Fprojets\u002Ftheme-vscode.jpg","Éditeur VS Code affichant du code avec le thème sombre",{"demo":137},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=BaptisteCrouzet.dark-theme-the-new",{},5,"TODO: nombre d'installations, si le chiffre est intéressant.","\u002Fprojets\u002Ftheme-vscode","TODO: année de publication","Conception et développement, seul",{"title":119,"description":124},[146,147],"JSON","VS Code Extension API","projets\u002Ftheme-vscode","Thème sombre pour Visual Studio Code, publié sur le Marketplace officiel.","0a_cDx77j12HlcaLQz0aaTxLCaCcMO_ZgS8PLaiaxX0",[152,170,186],{"id":25,"title":26,"body":153,"context":50,"contributions":165,"cover":54,"coverAlt":55,"description":33,"extension":56,"featured":57,"links":166,"meta":167,"navigation":57,"order":61,"outcome":62,"path":63,"period":64,"role":65,"seo":168,"stack":169,"status":72,"stem":73,"summary":74,"__hash__":75},{"type":28,"value":154,"toc":163},[155,157,159],[31,156,33],{},[31,158,36],{},[31,160,39,161,45],{},[41,162,44],{"href":43},{"title":47,"searchDepth":48,"depth":48,"links":164},[],[52,53],{"demo":59},{},{"title":26,"description":33},[68,69,70,71],{"id":77,"title":78,"body":171,"context":94,"contributions":181,"cover":99,"coverAlt":100,"description":83,"extension":56,"featured":57,"links":182,"meta":183,"navigation":57,"order":104,"outcome":105,"path":43,"period":106,"role":107,"seo":184,"stack":185,"status":72,"stem":114,"summary":115,"__hash__":116},{"type":28,"value":172,"toc":179},[173,175],[31,174,83],{},[31,176,86,177,91],{},[88,178,90],{},{"title":47,"searchDepth":48,"depth":48,"links":180},[],[96,97,98],{"repo":102},{},{"title":78,"description":83},[110,111,112,113],{"id":118,"title":119,"body":187,"context":130,"contributions":195,"cover":134,"coverAlt":135,"description":124,"extension":56,"featured":57,"links":196,"meta":197,"navigation":57,"order":139,"outcome":140,"path":141,"period":142,"role":143,"seo":198,"stack":199,"status":72,"stem":148,"summary":149,"__hash__":150},{"type":28,"value":188,"toc":193},[189,191],[31,190,124],{},[31,192,127],{},{"title":47,"searchDepth":48,"depth":48,"links":194},[],[132,133],{"demo":137},{},{"title":119,"description":124},[146,147],{"id":201,"title":202,"achievements":203,"awards":206,"company":207,"companyUrl":208,"contract":209,"end":210,"extension":56,"kind":211,"location":212,"meta":213,"missions":218,"order":61,"role":224,"stack":225,"start":233,"stem":234,"summary":235,"__hash__":236},"parcours\u002Fparcours\u002Fperfmanager.md","Lead Développeur — Perfmanager",[204,205],"Rédaction du plan de reprise et du plan de continuité d'activité (PRA\u002FPCA), et des procédures qui les accompagnent.","Arbitrage des choix technologiques, en tenant ensemble le gain immédiat et la maintenabilité à long terme.",[],"Perfmanager","https:\u002F\u002Fwww.perfmanager.com","CDI",null,"poste","Firminy, Loire",{"body":214},{"type":28,"value":215,"toc":216},[],{"title":47,"searchDepth":48,"depth":48,"links":217},[],[219,220,221,222,223],"Développement en JavaScript et en PHP, sur une API Symfony 6.4.","Gestion de projet : rédaction des spécifications techniques et fonctionnelles des user stories.","Administration du serveur de production et sécurisation du système d'information.","Conduite des entretiens de recrutement, pour l'équipe comme pour les stages et l'alternance.","Participation aux événements de Digital League, le cluster numérique d'Auvergne-Rhône-Alpes dont Perfmanager est adhérente.","Lead Développeur",[226,227,228,229,230,231,232,110,112],"JavaScript","TypeScript","Nuxt","Node.js","PHP","Symfony 6.4","PostgreSQL","2022-12","parcours\u002Fperfmanager","Perfmanager édite un logiciel QSE en SaaS : pilotage des systèmes de management de la qualité, de la sécurité et de l'environnement, en conformité avec les normes ISO 9001, ISO 14001 et ISO 45001. J'y porte les choix techniques, la production et la sécurisation du système d'information.","ejfqhAIqjcHJQ25xlLKGYwKkz53l0tMx5-vPEYqlpqI",{"id":238,"title":239,"body":240,"cover":210,"coverAlt":47,"date":464,"description":465,"draft":6,"extension":56,"meta":466,"navigation":57,"path":467,"seo":468,"seoTitle":469,"stem":470,"tags":471,"updated":210,"__hash__":474},"blog\u002Fblog\u002Fmigrer-webpack-vers-nuxt.md","Ce que la refonte de mon portfolio m'a appris sur les dépendances",{"type":28,"value":241,"toc":454},[242,245,250,253,291,294,298,304,313,316,320,325,328,331,335,342,345,406,412,416,427,430,437,441,444,447,450],[31,243,244],{},"Mon portfolio a tenu cinq ans sur une base artisanale : cinq pages HTML écrites\nà la main, du Sass, la grille de Bootstrap et une configuration Webpack montée\npièce par pièce. Il fonctionnait. Deux points ont fini par le rendre coûteux à\nmaintenir : le contenu vivait dans le balisage, et le nombre de domaines\nextérieurs contactés au chargement avait augmenté sans que je m'en aperçoive.",[246,247,249],"h2",{"id":248},"cinq-domaines-pour-un-site-statique","Cinq domaines pour un site statique",[31,251,252],{},"En reprenant l'ancien code, j'ai compté les domaines contactés au chargement\nd'une page :",[254,255,256,267,273,279,285],"ul",{},[257,258,259,262,263,266],"li",{},[88,260,261],{},"fonts.googleapis.com"," et ",[88,264,265],{},"fonts.gstatic.com"," pour la police,",[257,268,269,272],{},[88,270,271],{},"cdn.jsdelivr.net"," pour une feuille de reset — importée depuis le Sass,\ndonc bloquante au rendu,",[257,274,275,278],{},[88,276,277],{},"media.giphy.com"," pour une image décorative,",[257,280,281,284],{},[88,282,283],{},"www.google.com"," pour un reCAPTCHA rattaché à un formulaire qui, lui, ne\nfonctionnait plus,",[257,286,287,290],{},[88,288,289],{},"instant.page"," pour du préchargement au survol.",[31,292,293],{},"Cinq domaines, aucun sous mon contrôle. En reprenant chaque cas, je n'ai trouvé\naucune raison de les garder. Le reCAPTCHA protégeait un formulaire hors service\ndepuis un moment.",[246,295,297],{"id":296},"leffet-de-bord-qui-valait-le-détour","L'effet de bord qui valait le détour",[31,299,300,301,45],{},"J'ai fait le ménage pour le temps de chargement. Le gain que je n'attendais pas\nest ailleurs : sans ressource externe, la politique de sécurité du contenu peut\ndémarrer à ",[88,302,303],{},"default-src 'none'",[305,306,311],"pre",{"className":307,"code":309,"language":310},[308],"language-text","default-src 'none'; script-src 'self' 'sha256-…'; style-src 'self';\nimg-src 'self' data:; font-src 'self'; connect-src 'self';\nbase-uri 'none'; form-action 'none'; frame-ancestors 'none'\n","text",[88,312,309],{"__ignoreMap":47},[31,314,315],{},"Tout est interdit par défaut, et seul ce qui vient de mon domaine passe. Cette\nrègle devient intenable dès qu'une seule police est chargée depuis un CDN.",[246,317,319],{"id":318},"trois-pièges-rencontrés-en-chemin","Trois pièges rencontrés en chemin",[321,322,324],"h3",{"id":323},"une-police-récupérée-sans-cdn","Une police récupérée sans CDN",[31,326,327],{},"Auto-héberger une police suppose d'obtenir les fichiers. Le réflexe est de les\ntélécharger depuis Google Fonts. Sauf que ça recrée une dépendance réseau au\nmoment du build : sans accès à internet, la génération échoue.",[31,329,330],{},"Fontsource publie les mêmes polices sur npm. Le fichier arrive par le\ngestionnaire de paquets et il est versionné dans le dépôt. Rubik en variable\npèse 35 Ko et couvre toutes les graisses en une requête.",[321,332,334],{"id":333},"des-icônes-qui-appelaient-une-api-distante","Des icônes qui appelaient une API distante",[31,336,337,338,341],{},"Iconify résout les icônes via son API quand la collection n'est pas installée\nlocalement. Le paquet était bien présent, mais un repli restait actif et le\nbuild sortait vers ",[88,339,340],{},"api.iconify.design"," sans rien signaler.",[31,343,344],{},"La configuration explicite règle le problème :",[305,346,350],{"className":347,"code":348,"language":349,"meta":47,"style":47},"language-ts shiki shiki-themes github-light-high-contrast github-light-high-contrast github-dark-high-contrast","icon: {\n  mode: 'svg',           \u002F\u002F SVG inline plutôt que masques CSS\n  fallbackToApi: false,  \u002F\u002F une icône manquante casse le build\n}\n","ts",[88,351,352,364,384,401],{"__ignoreMap":47},[353,354,356,360],"span",{"class":355,"line":61},"line",[353,357,359],{"class":358},"szw1H","icon",[353,361,363],{"class":362},"s7iLA",": {\n",[353,365,367,370,373,377,380],{"class":355,"line":366},2,[353,368,369],{"class":358},"  mode",[353,371,372],{"class":362},": ",[353,374,376],{"class":375},"semJd","'svg'",[353,378,379],{"class":362},",           ",[353,381,383],{"class":382},"sq92B","\u002F\u002F SVG inline plutôt que masques CSS\n",[353,385,386,389,391,395,398],{"class":355,"line":48},[353,387,388],{"class":358},"  fallbackToApi",[353,390,372],{"class":362},[353,392,394],{"class":393},"ssYy9","false",[353,396,397],{"class":362},",  ",[353,399,400],{"class":382},"\u002F\u002F une icône manquante casse le build\n",[353,402,403],{"class":355,"line":104},[353,404,405],{"class":362},"}\n",[31,407,408,411],{},[88,409,410],{},"fallbackToApi: false"," est la ligne qui compte. Sans elle, une icône mal\northographiée passe inaperçue en développement et déclenche une requête externe\nen production.",[321,413,415],{"id":414},"un-thème-sombre-sans-fond","Un thème sombre sans fond",[31,417,418,419,422,423,426],{},"Le plus difficile à diagnostiquer. Tailwind 4 élague les variables déclarées dans\n",[88,420,421],{},"@theme"," qu'aucune classe n'utilise littéralement. Ma palette de neutres était\nbien définie, mais comme aucune classe ne s'appelait ",[88,424,425],{},"bg-ink-900",", les\nvariables disparaissaient du CSS final, et avec elles les tokens que Nuxt UI en\ndérive.",[31,428,429],{},"Le thème sombre passait donc le texte en blanc et laissait le fond blanc. Du\ntexte invisible, uniquement en mode sombre, uniquement en production.",[31,431,432,433,436],{},"Le correctif tient en un mot-clé : ",[88,434,435],{},"@theme static"," conserve toutes les\nvariables, utilisées ou non.",[246,438,440],{"id":439},"ce-que-jen-ai-fait","Ce que j'en ai fait",[31,442,443],{},"Aucun des trois ne serait apparu dans un test fonctionnel. Le site s'affichait\ncorrectement dans les trois cas, ce qui est précisément le problème.",[31,445,446],{},"J'ai donc écrit des scripts de vérification. L'un parcourt le HTML généré et\néchoue si une ressource pointe hors du domaine. Un autre calcule les ratios de\ncontraste de la palette : c'est ainsi que j'ai découvert qu'une de mes teintes\nplafonnait à 4,49:1, pour un seuil fixé à 4,5.",[31,448,449],{},"Ils s'exécutent à chaque modification. Aucune de ces trois erreurs n'était\ndétectable à l'œil, et rien ne garantit qu'elles le deviendraient dans six\nmois.",[451,452,453],"style",{},"html pre.shiki code .szw1H, html code.shiki .szw1H{--shiki-light:#702C00;--shiki-default:#702C00;--shiki-dark:#FFB757}html pre.shiki code .s7iLA, html code.shiki .s7iLA{--shiki-light:#0E1116;--shiki-default:#0E1116;--shiki-dark:#F0F3F6}html pre.shiki code .semJd, html code.shiki .semJd{--shiki-light:#032563;--shiki-default:#032563;--shiki-dark:#ADDCFF}html pre.shiki code .sq92B, html code.shiki .sq92B{--shiki-light:#66707B;--shiki-default:#66707B;--shiki-dark:#BDC4CC}html pre.shiki code .ssYy9, html code.shiki .ssYy9{--shiki-light:#023B95;--shiki-default:#023B95;--shiki-dark:#91CBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":47,"searchDepth":48,"depth":48,"links":455},[456,457,458,463],{"id":248,"depth":366,"text":249},{"id":296,"depth":366,"text":297},{"id":318,"depth":366,"text":319,"children":459},[460,461,462],{"id":323,"depth":48,"text":324},{"id":333,"depth":48,"text":334},{"id":414,"depth":48,"text":415},{"id":439,"depth":366,"text":440},"2026-09-07","Migration d'un portfolio Webpack vers Nuxt avec zéro ressource tierce : police auto-hébergée, icônes locales et politique de sécurité stricte.",{},"\u002Fblog\u002Fmigrer-webpack-vers-nuxt",{"title":239,"description":465},"Migrer un portfolio Webpack vers Nuxt","blog\u002Fmigrer-webpack-vers-nuxt",[228,472,473],"Performance","Sécurité","hLoOHhNwx-gcMutwKSfapHzeVoskzPqJCQvN5-bfvYM",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":476},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m6 14l1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":478},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 12h14m-7-7l7 7l-7 7\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":480},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"m22 7l-8.991 5.727a2 2 0 0 1-2.009 0L2 7\"\u002F>\u003Crect width=\"20\" height=\"16\" x=\"2\" y=\"4\" rx=\"2\"\u002F>\u003C\u002Fg>",1791663007436]