[{"data":1,"prerenderedAt":2665},["ShallowReactive",2],{"home-projets-fr":3,"home-articles":596},[4,74,108,141,196,230,281,322,354,388,422,455,492,527,564],{"id":5,"title":6,"annee":7,"body":8,"client":57,"description":51,"extension":58,"image":59,"imageAlt":59,"lien":51,"meta":60,"navigation":61,"ordre":62,"path":63,"resume":64,"seo":65,"stack":66,"stem":71,"type":72,"__hash__":73},"projets_fr\u002Fprojets\u002Felis-esic.md","Systèmes nationaux eLIS & eSIC","2024 – aujourd'hui",{"type":9,"value":10,"toc":50},"minimark",[11,16,29,33,36,39,43],[12,13,15],"h2",{"id":14},"contexte","Contexte",[17,18,19,20,24,25,28],"p",{},"Dans le cadre de la digitalisation du système de santé congolais portée par\nla Direction des Technologies de l'Information et de la Communication (DTIC)\ndu Ministère de la Santé, et sur financement du Fonds mondial (subvention\nC19RM) avec l'appui de Catholic Relief Services, deux systèmes nationaux ont\nété développés : ",[21,22,23],"strong",{},"eLIS",", le système d'information du réseau des\nlaboratoires, et ",[21,26,27],{},"eSIC",", le système d'information communautaire.",[12,30,32],{"id":31},"ce-qui-a-été-livré","Ce qui a été livré",[17,34,35],{},"En tant que consultant expert IT, j'ai participé à la conception, au\ndéveloppement et au déploiement des deux plateformes : couverture du circuit\ncomplet de l'échantillon pour eLIS (réception du patient, prélèvement,\nconditionnement, transport, analyse, conservation) et des paquets de soins\ncommunautaires pour eSIC (consultations, prise en charge VIH et\ntuberculose, suivi de la grossesse, caisse). L'architecture associe un\nmodule mobile sur tablettes pour la saisie terrain, un back-office web et\ndes tableaux de bord, avec une interopérabilité DHIS2 en ligne de mire.",[17,37,38],{},"La phase pilote a été déployée fin 2024 dans les départements de\nBrazzaville, Pointe-Noire et de la Bouenza : hôpitaux généraux, CHU de\nBrazzaville, CNTS, LNSP et centres de santé intégrés, avec formation des\nutilisateurs sur site et support continu.",[12,40,42],{"id":41},"et-maintenant","Et maintenant",[17,44,45,46,49],{},"Depuis juillet 2026, j'accompagne le Ministère comme consultant indépendant\nsur la ",[21,47,48],{},"phase 2",", en collaboration continue avec CRS : extensions\nfonctionnelles issues des retours de la phase pilote, amélioration de la\nqualité des données et poursuite du déploiement national.",{"title":51,"searchDepth":52,"depth":52,"links":53},"",2,[54,55,56],{"id":14,"depth":52,"text":15},{"id":31,"depth":52,"text":32},{"id":41,"depth":52,"text":42},"Ministère de la Santé et de la Population (DTIC), appui CRS \u002F Fonds mondial","md",null,{},true,1,"\u002Fprojets\u002Felis-esic","Conception et déploiement des systèmes nationaux d'information des laboratoires (eLIS) et communautaire (eSIC) pour le secteur public de santé congolais.",{"title":6,"description":51},[67,68,69,70],"Application mobile (tablettes)","Back-office web","Dashboards","Interopérabilité DHIS2","projets\u002Felis-esic","Web & Mobile","yGlkIm4b5gL0WrSJSpIJimeOk-KwTazj3nWKcir8-VU",{"id":75,"title":76,"annee":77,"body":78,"client":94,"description":51,"extension":58,"image":95,"imageAlt":96,"lien":51,"meta":97,"navigation":61,"ordre":52,"path":98,"resume":99,"seo":100,"stack":101,"stem":105,"type":106,"__hash__":107},"projets_fr\u002Fprojets\u002Fartf.md","Refonte du portail de l'ARTF","2025 – 2026",{"type":9,"value":79,"toc":90},[80,82,85,87],[12,81,15],{"id":14},[17,83,84],{},"L'ARTF est l'organe technique du Ministère des Finances chargé de la\nrégulation du secteur des transferts de fonds en République du Congo. Son\nportail porte la parole institutionnelle de l'agence : missions et\nhistorique, textes réglementaires, notes de conjoncture trimestrielles sur\nle secteur, information des opérateurs et du public.",[12,86,32],{"id":31},[17,88,89],{},"Développeur logiciel au sein de l'agence depuis septembre 2025, j'ai conçu\net développé la refonte complète du portail : nouvelle architecture de\nl'information (institution, régulation, publications, actualités), espace\ndocumentaire structuré pour les notes de conjoncture et les textes, et une\ninterface modernisée. La mise en production est en cours de finalisation.",{"title":51,"searchDepth":52,"depth":52,"links":91},[92,93],{"id":14,"depth":52,"text":15},{"id":31,"depth":52,"text":32},"ARTF - Agence de Régulation des Transferts de Fonds","\u002Fimages\u002Fprojets\u002Fartf.jpg","Page d'accueil du portail institutionnel de l'ARTF",{},"\u002Fprojets\u002Fartf","Refonte complète du portail de l'autorité de régulation des transferts de fonds : institution, publications réglementaires et notes de conjoncture.",{"title":76,"description":51},[102,103,104],"Portail institutionnel","Publications réglementaires","Données sectorielles","projets\u002Fartf","Web","22Pef_daJ40RDX5Ifnrjdu2PND-v5U1Suk-Xgl5hUMY",{"id":109,"title":110,"annee":111,"body":112,"client":128,"description":51,"extension":58,"image":129,"imageAlt":130,"lien":131,"meta":132,"navigation":61,"ordre":133,"path":134,"resume":135,"seo":136,"stack":137,"stem":139,"type":106,"__hash__":140},"projets_fr\u002Fprojets\u002Farpce.md","Portail officiel de l'ARPCE","2024",{"type":9,"value":113,"toc":124},[114,116,119,121],[12,115,15],{"id":14},[17,117,118],{},"L'ARPCE (Agence de Régulation des Postes et des Communications\nÉlectroniques) est le régulateur national du secteur des postes et des\ntélécommunications en République du Congo. Son portail est la vitrine\nofficielle de l'institution : missions, réglementation, publications et\nactualités du secteur.",[12,120,32],{"id":31},[17,122,123],{},"Dans le cadre d'une mission menée avec Elonga Victorious Communication\n(oct.–déc. 2024), j'ai assuré la mise en place du portail officiel de\nl'agence ainsi que de ses sites rattachés : architecture de l'information\ninstitutionnelle, rubriques réglementaires et espaces de publication.",{"title":51,"searchDepth":52,"depth":52,"links":125},[126,127],{"id":14,"depth":52,"text":15},{"id":31,"depth":52,"text":32},"ARPCE, via Elonga Victorious Communication (EVC)","\u002Fimages\u002Fprojets\u002Farpce.jpg","Page d'accueil du portail de l'ARPCE","https:\u002F\u002Farpce.cg",{},3,"\u002Fprojets\u002Farpce","Mise en place du portail officiel du régulateur des postes et communications électroniques du Congo, et de ses sites rattachés.",{"title":110,"description":51},[102,138],"Sites rattachés","projets\u002Farpce","NfqZ1Egl_CKmjhwvZFTXN_6YLm8YGe8FpmgUdZKG5yM",{"id":142,"title":143,"annee":144,"body":145,"client":183,"description":51,"extension":58,"image":184,"imageAlt":185,"lien":59,"meta":186,"navigation":61,"ordre":187,"path":188,"resume":189,"seo":190,"stack":191,"stem":194,"type":72,"__hash__":195},"projets_fr\u002Fprojets\u002Fpdac.md","Plateforme et application mobile du PDAC","2019 – 2024",{"type":9,"value":146,"toc":179},[147,149,152,154,157],[12,148,15],{"id":14},[17,150,151],{},"Le PDAC est un projet financé par la Banque mondiale pour appuyer\nl'agriculture commerciale en République du Congo. Le projet avait besoin\nde trois choses : un site d'information pour sa communication officielle,\nun outil mobile utilisable sur le terrain, et un référentiel fiable de ses\nbénéficiaires pour le suivi et le reporting.",[12,153,32],{"id":31},[17,155,156],{},"Consultant IT à temps plein sur le projet de 2019 à 2024, j'ai conçu et\nlivré les trois volets :",[158,159,160,167,173],"ul",{},[161,162,163,166],"li",{},[21,164,165],{},"Le site officiel du projet"," : actualités, appels à propositions,\nprésentation des fonds à coûts partagés et information des porteurs de\nprojets agricoles.",[161,168,169,172],{},[21,170,171],{},"L'application mobile Android",", pensée pour des conditions de\nconnectivité irrégulières, avec notamment la rubrique « Parole aux\nbénéficiaires » : des interviews réalisées sur le terrain qui donnent\ndirectement la voix aux agriculteurs et entrepreneurs accompagnés par le\nprojet.",[161,174,175,178],{},[21,176,177],{},"La base de données centralisée des bénéficiaires"," : structuration de\nvolumes importants de données de terrain en un référentiel unique,\nutilisé pour le suivi des appuis et le reporting du projet.",{"title":51,"searchDepth":52,"depth":52,"links":180},[181,182],{"id":14,"depth":52,"text":15},{"id":31,"depth":52,"text":32},"PDAC - Projet d'Appui au Développement de l'Agriculture Commerciale (Banque mondiale)","\u002Fimages\u002Fprojets\u002Fpdac.jpg","Plateforme web du projet agricole PDAC",{},4,"\u002Fprojets\u002Fpdac","Site officiel, application mobile de terrain et base de données centralisée des bénéficiaires pour un projet agricole financé par la Banque mondiale.",{"title":143,"description":51},[106,192,193],"Android","Base de données bénéficiaires","projets\u002Fpdac","8R_uDJ-y_IP3OZ3Nk53QKne5BproYqYRC_gYD4e3ssA",{"id":197,"title":198,"annee":199,"body":200,"client":216,"description":51,"extension":58,"image":217,"imageAlt":218,"lien":219,"meta":220,"navigation":61,"ordre":221,"path":222,"resume":223,"seo":224,"stack":225,"stem":228,"type":106,"__hash__":229},"projets_fr\u002Fprojets\u002Fcongo-telecom.md","Site de Congo Télécom","2020 – 2024",{"type":9,"value":201,"toc":212},[202,204,207,209],[12,203,15],{"id":14},[17,205,206],{},"Congo Télécom est l'opérateur historique des télécommunications de la\nRépublique du Congo, détenu à 100 % par l'État, opérateur dominant de la\ntéléphonie fixe et de la fibre optique, avec plus de 3 500 km de backbone\ndéployés sur le territoire. Son site s'adresse à trois publics distincts :\nparticuliers, professionnels et grands comptes.",[12,208,32],{"id":31},[17,210,211],{},"En tant que consultant pour Elonga Victorious Communication (l'agence qui\nassure la gestion des sites de Congo Télécom et de l'ARPCE), j'ai conçu et\ndéveloppé le site de l'opérateur, puis assuré sa maintenance et ses\névolutions de 2020 à 2024 : espaces Particulier \u002F Professionnel \u002F\nCorporate, présentation des offres fibre et fixe, actualités de\nl'opérateur et intégration du service de paiement SpeedPay.",{"title":51,"searchDepth":52,"depth":52,"links":213},[214,215],{"id":14,"depth":52,"text":15},{"id":31,"depth":52,"text":32},"Congo Télécom, via Elonga Victorious Communication (EVC)","\u002Fimages\u002Fprojets\u002Fcongotelecom.jpg","Page d'accueil du site de Congo Télécom","https:\u002F\u002Fcongotelecom.cg",{},5,"\u002Fprojets\u002Fcongo-telecom","Conception du site de l'opérateur historique des télécommunications du Congo, puis maintenance et évolutions continues pendant quatre ans.",{"title":198,"description":51},[226,227],"Site corporate multi-espaces","Conception & maintenance","projets\u002Fcongo-telecom","OwuM64ps0QYan189Z05Z1G0TddO30yxKVD0azFB-TmY",{"id":231,"title":232,"annee":233,"body":234,"client":252,"description":51,"extension":58,"image":267,"imageAlt":268,"lien":269,"meta":270,"navigation":61,"ordre":271,"path":272,"resume":273,"seo":274,"stack":275,"stem":278,"type":279,"__hash__":280},"projets_fr\u002Fprojets\u002Fsantef.md","SantéF & SantéF-Doc","2020 – 2022",{"type":9,"value":235,"toc":263},[236,238,241,243,246,260],[12,237,15],{"id":14},[17,239,240],{},"SantéF est une initiative congolaise de santé numérique visant à améliorer\nl'accès aux services de santé par des solutions mobiles, à destination du\ngrand public comme des professionnels.",[12,242,32],{"id":31},[17,244,245],{},"Direction technique (avril 2020 – novembre 2022) du développement de deux\napplications complémentaires :",[158,247,248,254],{},[161,249,250,253],{},[21,251,252],{},"SantéF",", l'application grand public : carnet de vaccination\nélectronique et conseils santé, y compris pendant la période COVID-19 ;",[161,255,256,259],{},[21,257,258],{},"SantéF-Doc",", la plateforme des professionnels de santé : réception et\ntraitement des demandes de consultation et des rendez-vous, dans un\nparcours de prise en charge interconnecté entre médecins, pharmacies,\nlaboratoires, cliniques et patients.",[17,261,262],{},"Ce projet a été ma première immersion dans la santé numérique congolaise :\nle terrain qui est devenu depuis le cœur de mon activité de consultant.",{"title":51,"searchDepth":52,"depth":52,"links":264},[265,266],{"id":14,"depth":52,"text":15},{"id":31,"depth":52,"text":32},"\u002Fimages\u002Fprojets\u002Fsantef_app.jpg","Écran de l'application mobile SantéF","https:\u002F\u002Fplay.google.com\u002Fstore\u002Fapps\u002Fdetails?id=com.santef",{},6,"\u002Fprojets\u002Fsantef","Direction technique de deux applications de santé numérique : carnet de vaccination électronique grand public et plateforme de prise en charge pour les professionnels.",{"title":232,"description":51},[192,276,277],"Carnet de vaccination électronique","Parcours de soins","projets\u002Fsantef","Mobile","rrm2MvuA8lQz7fKEDJTNDy5iLoNOrTMxaTGkPcg6RtM",{"id":282,"title":283,"annee":284,"body":285,"client":307,"description":51,"extension":58,"image":308,"imageAlt":309,"lien":310,"meta":311,"navigation":61,"ordre":312,"path":313,"resume":314,"seo":315,"stack":316,"stem":320,"type":106,"__hash__":321},"projets_fr\u002Fprojets\u002Fafribuz.md","Site du groupe AFRIBUZ","2026",{"type":9,"value":286,"toc":303},[287,289,292,295,297,300],[12,288,15],{"id":14},[17,290,291],{},"AFRIBUZ GROUP est né d'un constat simple : les professionnels et\nentreprises de Brazzaville consacrent trop d'énergie à gérer des\nactivités annexes au détriment de leur cœur de métier. Le groupe prend\nen charge l'ensemble de ces besoins, de la propreté à la logistique,\npour permettre à ses clients, particuliers, PME et grandes entreprises,\nde se concentrer pleinement sur leur activité principale.",[17,293,294],{},"Pour porter cette promesse, il fallait une présence en ligne à la\nhauteur : professionnelle, rassurante et orientée vers l'action.",[12,296,32],{"id":31},[17,298,299],{},"Un site pensé comme un outil commercial plus qu'une simple vitrine :\nchaque pôle de services dispose de sa page dédiée, les valeurs du\ngroupe (rigueur, réactivité, confiance, excellence, proximité)\nstructurent le discours, et le parcours entier converge vers la demande\nde devis, accessible depuis chaque page. Le site est en ligne depuis\navril 2026 et sert de point d'entrée commercial du groupe.",[17,301,302],{},"Côté technique : développé en Nuxt avec rendu serveur, pour un site\nrapide et bien référencé.",{"title":51,"searchDepth":52,"depth":52,"links":304},[305,306],{"id":14,"depth":52,"text":15},{"id":31,"depth":52,"text":32},"AFRIBUZ GROUP","\u002Fimages\u002Fprojets\u002Fafribuz.jpg","Page d'accueil du site du groupe AFRIBUZ","https:\u002F\u002Fafribuz.cg",{},7,"\u002Fprojets\u002Fafribuz","La vitrine digitale d'un groupe multi-services congolais : présenter les pôles d'expertise, inspirer confiance et transformer les visiteurs en demandes de devis.",{"title":283,"description":51},[317,318,319],"Nuxt","Site vitrine B2B","Demandes de devis","projets\u002Fafribuz","z4sTPTJ01q09LXaBj6BuS2oDRaDYDuKiXiY6Wn932w0",{"id":323,"title":324,"annee":325,"body":326,"client":342,"description":51,"extension":58,"image":343,"imageAlt":344,"lien":59,"meta":345,"navigation":61,"ordre":346,"path":347,"resume":348,"seo":349,"stack":350,"stem":352,"type":106,"__hash__":353},"projets_fr\u002Fprojets\u002Fambassade-guinee-equatoriale.md","Site de l'Ambassade de Guinée équatoriale au Congo","2022",{"type":9,"value":327,"toc":338},[328,330,333,335],[12,329,15],{"id":14},[17,331,332],{},"La représentation diplomatique de la Guinée équatoriale à Brazzaville avait\nbesoin d'un site officiel pour ses services consulaires et la promotion du\npays auprès des voyageurs et investisseurs. Basée à Brazzaville, l'ambassade\na une compétence régionale : elle couvre le Congo-Brazzaville, la République\ndémocratique du Congo (Congo-Kinshasa), le Rwanda et le Burundi.",[12,334,32],{"id":31},[17,336,337],{},"Un site institutionnel présentant les services de l'ambassade et des\nrubriques dédiées (visiter, investir et vivre en Guinée équatoriale). Le site\nprécise aussi les pays relevant de sa compétence consulaire, Congo-Brazzaville,\nCongo-Kinshasa, Rwanda et Burundi, afin que chaque ressortissant sache à\nquelle représentation s'adresser.",{"title":51,"searchDepth":52,"depth":52,"links":339},[340,341],{"id":14,"depth":52,"text":15},{"id":31,"depth":52,"text":32},"Ambassade de la République de Guinée équatoriale","\u002Fimages\u002Fprojets\u002Fambassade-guinee-equatoriale.jpg","Page d'accueil du site de l'Ambassade de Guinée équatoriale au Congo",{},8,"\u002Fprojets\u002Fambassade-guinee-equatoriale","Site officiel d'une ambassade à compétence régionale (Congo, RDC, Rwanda, Burundi) : services consulaires, information aux voyageurs et promotion du pays.",{"title":324,"description":51},[106,351],"Multilingue","projets\u002Fambassade-guinee-equatoriale","dsH7zRgnUVMQl7lqyjE_MucnzpRT3AW_84jkWiULpUw",{"id":355,"title":356,"annee":357,"body":358,"client":374,"description":51,"extension":58,"image":375,"imageAlt":376,"lien":377,"meta":378,"navigation":61,"ordre":379,"path":380,"resume":381,"seo":382,"stack":383,"stem":386,"type":106,"__hash__":387},"projets_fr\u002Fprojets\u002Ffae-africa.md","Site de la Fondation Africaine pour l'Éducation","2022 – 2023",{"type":9,"value":359,"toc":370},[360,362,365,367],[12,361,15],{"id":14},[17,363,364],{},"Créée en 2011 par des cadres congolais et reconnue Club UNESCO depuis\noctobre 2022, la Fondation Africaine pour l'Éducation œuvre à améliorer\nl'offre éducative au Congo et en Afrique : orientation post-bac (CIO),\ncentre linguistique, programme ESMBA, ateliers FAE Meet, et des\nréalisations comme l'American International School of Brazzaville.",[12,366,32],{"id":31},[17,368,369],{},"Mission de consultant développeur (2022–2023) sur la présence numérique de\nla fondation : développement du site institutionnel présentant les\nmissions, les programmes, les réalisations et les actualités de la FAE,\navec une structure éditoriale pensée pour une organisation à programmes\nmultiples.",{"title":51,"searchDepth":52,"depth":52,"links":371},[372,373],{"id":14,"depth":52,"text":15},{"id":31,"depth":52,"text":32},"FAE - Fondation Africaine pour l'Éducation","\u002Fimages\u002Fprojets\u002Ffae.jpg","Site web de l'organisation éducative FAE Africa","https:\u002F\u002Ffaeafrica.org",{},9,"\u002Fprojets\u002Ffae-africa","Développement de la présence numérique d'une fondation éducative congolaise reconnue Club UNESCO : programmes, réalisations et actualités.",{"title":356,"description":51},[384,385],"Site institutionnel","Programmes & actualités","projets\u002Ffae-africa","-tCi93XwvIsrFGhdMv-ompn6nURX2t1nDewxm3CvOi8",{"id":389,"title":390,"annee":391,"body":392,"client":408,"description":51,"extension":58,"image":409,"imageAlt":410,"lien":411,"meta":412,"navigation":61,"ordre":413,"path":414,"resume":415,"seo":416,"stack":417,"stem":420,"type":106,"__hash__":421},"projets_fr\u002Fprojets\u002Ffhic.md","Site du Forum Horizon Initiative et Créativité (FHIC)","2025",{"type":9,"value":393,"toc":404},[394,396,399,401],[12,395,15],{"id":14},[17,397,398],{},"Le Forum Horizon Initiative et Créativité (FHIC) est une initiative\ncongolaise qui promeut l'entrepreneuriat, la créativité et le dialogue\nentrepreneurial au sein des communautés. Fondé et présidé par Aline France\nEtokabeka, il agit comme un accélérateur de création d'emplois : panels et\nconférences, ateliers de formation, expositions, mentorat et accompagnement\nde porteurs de projets. Ses éditions se tiennent successivement dans\nplusieurs villes du pays (Brazzaville, Dolisie, Impfondo, Pointe-Noire)\ndepuis 2023, et le forum revendique plus de 250 startups accompagnées et un\nmillier d'emplois directs créés.",[12,400,32],{"id":31},[17,402,403],{},"Conception et développement du site officiel du forum, mis en ligne en\noctobre 2025 : présentation de l'initiative et de ses missions, mise en avant de l'édition en cours,\narchive des éditions précédentes, galerie photo et parcours d'inscription\naux événements. Le site porte la parole publique du FHIC et sert de point\nd'entrée pour les participants, les partenaires et les porteurs de projets.",{"title":51,"searchDepth":52,"depth":52,"links":405},[406,407],{"id":14,"depth":52,"text":15},{"id":31,"depth":52,"text":32},"FHIC - Forum Horizon Initiative et Créativité","\u002Fimages\u002Fprojets\u002Ffhic.jpg","Page d'accueil du site du FHIC","https:\u002F\u002Ffhic.cg",{},10,"\u002Fprojets\u002Ffhic","Site du forum congolais dédié à l'entrepreneuriat et à la création d'emplois : présentation, éditions successives et inscription aux événements.",{"title":390,"description":51},[384,418,419],"Éditions & inscriptions","Galerie","projets\u002Ffhic","TfJaFIRLtcGX2FHYEPTdJJnqk7tPEvUUUNbc4Urq1W8",{"id":423,"title":424,"annee":425,"body":426,"client":442,"description":51,"extension":58,"image":443,"imageAlt":444,"lien":445,"meta":446,"navigation":61,"ordre":447,"path":448,"resume":449,"seo":450,"stack":451,"stem":453,"type":106,"__hash__":454},"projets_fr\u002Fprojets\u002Fcabinet-mayabel.md","Site du Cabinet Mayabel","2023",{"type":9,"value":427,"toc":438},[428,430,433,435],[12,429,15],{"id":14},[17,431,432],{},"Cabinet d'expertise comptable, d'audit et de conseil aux entreprises\nimplanté au Congo depuis plus de dix ans, avec des bureaux à Brazzaville et\nPointe-Noire et une couverture régionale CEMAC. Mayabel avait besoin d'une\nprésence en ligne à la hauteur de son positionnement de « partenaire de\ncroissance », couvrant l'ensemble de ses métiers.",[12,434,32],{"id":31},[17,436,437],{},"Un site vitrine clair présentant les différents pôles du cabinet :\nexpertise comptable et commissariat aux comptes, audit interne, gestion\nfiscale, conseil en gestion et conseil stratégique, gestion de la paie,\naccompagnement juridique et formation professionnelle. Le site met en avant\nl'expérience et les implantations du cabinet, avec un dossier de presse et\nun parcours de prise de contact.",{"title":51,"searchDepth":52,"depth":52,"links":439},[440,441],{"id":14,"depth":52,"text":15},{"id":31,"depth":52,"text":32},"Cabinet Mayabel","\u002Fimages\u002Fprojets\u002Fmayabel.jpg","Page d'accueil du site du Cabinet Mayabel","https:\u002F\u002Fcabinet-mayabel.com",{},11,"\u002Fprojets\u002Fcabinet-mayabel","Site vitrine d'un cabinet d'expertise comptable, d'audit et de conseil au Congo (Brazzaville, Pointe-Noire) : présentation de l'offre de services et prise de contact.",{"title":424,"description":51},[106,452],"Vitrine","projets\u002Fcabinet-mayabel","ia53iI6sp6jOtbc6tALmxgzr783HdRRjbVghMDBoTFQ",{"id":456,"title":457,"annee":425,"body":458,"client":480,"description":51,"extension":58,"image":481,"imageAlt":482,"lien":59,"meta":483,"navigation":61,"ordre":484,"path":485,"resume":486,"seo":487,"stack":488,"stem":490,"type":106,"__hash__":491},"projets_fr\u002Fprojets\u002Fbic-immo-assurance.md","Portail BIC Immo Assurance",{"type":9,"value":459,"toc":476},[460,462,465,467,470],[12,461,15],{"id":14},[17,463,464],{},"BIC Immo Assurance intervient sur l'immobilier et l'assurance. Le portail\ndoit présenter les biens et les services et faciliter la mise en relation\navec la clientèle.",[12,466,32],{"id":31},[17,468,469],{},"Un portail présentant l'offre (biens, services) et un parcours de prise de\ncontact adapté à l'activité.",[17,471,472],{},[473,474,475],"em",{},"(Fiche à enrichir : périmètre exact, année à confirmer.)",{"title":51,"searchDepth":52,"depth":52,"links":477},[478,479],{"id":14,"depth":52,"text":15},{"id":31,"depth":52,"text":32},"BIC Immo Assurance","\u002Fimages\u002Fprojets\u002Fbic.jpg","Portail immobilier et assurance BIC Immo Assurance",{},12,"\u002Fprojets\u002Fbic-immo-assurance","Portail immobilier et assurance : présentation des biens et des services, et mise en relation avec les clients.",{"title":457,"description":51},[106,489],"Immobilier","projets\u002Fbic-immo-assurance","nVDyt75-GT1vtL6Y5pmpE-VneOnwgnJcaaPws9S9I20",{"id":493,"title":494,"annee":495,"body":496,"client":516,"description":51,"extension":58,"image":59,"imageAlt":59,"lien":59,"meta":517,"navigation":61,"ordre":518,"path":519,"resume":520,"seo":521,"stack":522,"stem":525,"type":106,"__hash__":526},"projets_fr\u002Fprojets\u002Fkongossa-buzz.md","Kongossa Buzz : média en ligne","2021",{"type":9,"value":497,"toc":512},[498,500,503,505,508],[12,499,15],{"id":14},[17,501,502],{},"Kongossa Buzz est un média en ligne diffusant actualités et contenus vers\nun large public. Il fallait un site capable de publier fréquemment et de\ntenir la charge des pics de fréquentation.",[12,504,32],{"id":31},[17,506,507],{},"Une plateforme de publication éditoriale : catégories, articles, mise en\navant des contenus et navigation pensée pour la lecture.",[17,509,510],{},[473,511,475],{},{"title":51,"searchDepth":52,"depth":52,"links":513},[514,515],{"id":14,"depth":52,"text":15},{"id":31,"depth":52,"text":32},"Kongossa Buzz",{},13,"\u002Fprojets\u002Fkongossa-buzz","Média en ligne : publication d'actualités et de contenus, avec une organisation éditoriale pensée pour la fréquentation.",{"title":494,"description":51},[106,523,524],"Média","Publication","projets\u002Fkongossa-buzz","eax4PkwTJCClYk0-GZaPWKkAnIgS0ji1VtibGxwnxBc",{"id":528,"title":529,"annee":530,"body":531,"client":551,"description":51,"extension":58,"image":552,"imageAlt":553,"lien":554,"meta":555,"navigation":61,"ordre":556,"path":557,"resume":558,"seo":559,"stack":560,"stem":562,"type":279,"__hash__":563},"projets_fr\u002Fprojets\u002Fcine-plus.md","Cine+ : l'actualité du cinéma sur mobile","2019",{"type":9,"value":532,"toc":547},[533,535,538,540],[12,534,15],{"id":14},[17,536,537],{},"Cine+ est une application d'actualité cinématographique publiée sous\nRicebean, mon label de projets personnels : suivre les nouveautés du\ncinéma et découvrir les acteurs, dans une interface volontairement sobre.",[12,539,32],{"id":31},[17,541,542,543,546],{},"Conception et développement complets de l'application Android, publiée sur\nle Play Store en 2019, où elle a dépassé les ",[21,544,545],{},"10 000 téléchargements",".\nUn projet personnel formateur sur tout le cycle de vie d'une application\npubliée : design, développement, publication et itérations.",{"title":51,"searchDepth":52,"depth":52,"links":548},[549,550],{"id":14,"depth":52,"text":15},{"id":31,"depth":52,"text":32},"Ricebean (projet personnel)","\u002Fimages\u002Fprojets\u002Fcineplus_app.jpg","Écran de l'application mobile Cine+","https:\u002F\u002Fplay.google.com\u002Fstore\u002Fapps\u002Fdetails?id=ricebean.cine_app",{},14,"\u002Fprojets\u002Fcine-plus","Application Android d'actualité cinéma. Plus de 10 000 téléchargements sur le Play Store.",{"title":529,"description":51},[192,561],"API de contenus","projets\u002Fcine-plus","w9eb-eFKAIXrCQPH391l8Km6pCZQx1u8cfrK3dxjnKg",{"id":565,"title":566,"annee":425,"body":567,"client":583,"description":51,"extension":58,"image":584,"imageAlt":585,"lien":586,"meta":587,"navigation":61,"ordre":588,"path":589,"resume":590,"seo":591,"stack":592,"stem":594,"type":279,"__hash__":595},"projets_fr\u002Fprojets\u002Fmy-icc.md","myICC : l'application communautaire d'ICC",{"type":9,"value":568,"toc":579},[569,571,574,576],[12,570,15],{"id":14},[17,572,573],{},"Impact Centre Chrétien est une église évangélique internationale présente\nen Europe, en Afrique, en Amérique, aux Caraïbes et dans l'océan Indien.\nmyICC est son application communautaire mobile.",[12,575,32],{"id":31},[17,577,578],{},"Conception et développement de l'application Android, publiée sur le Play\nStore en 2023 : accès aux messages et enseignements, témoignages,\nactualités de la communauté et localisation des églises locales et églises\nde maison. Un projet représentatif des applications de contenus et de\ncommunauté que je livre pour des organisations.",{"title":51,"searchDepth":52,"depth":52,"links":580},[581,582],{"id":14,"depth":52,"text":15},{"id":31,"depth":52,"text":32},"Impact Centre Chrétien (via Ricebean)","\u002Fimages\u002Fprojets\u002FmyIcc.jpg","Écran de l'application mobile myICC","https:\u002F\u002Fplay.google.com\u002Fstore\u002Fapps\u002Fdetails?id=com.ricebean.icc_app",{},15,"\u002Fprojets\u002Fmy-icc","Application mobile communautaire d'une église internationale : messages, témoignages, actualités et annuaire des implantations locales.",{"title":566,"description":51},[192,593],"Contenus & communauté","projets\u002Fmy-icc","aoCXEu3ntFX6-_SKY4oXO6K2nnYkdONCWtyj9I3kYfs",[597,1640,2280],{"id":598,"title":599,"body":600,"brouillon":1629,"date":1630,"description":1631,"extension":58,"image":59,"imageAlt":59,"meta":1632,"navigation":61,"path":1633,"seo":1634,"stem":1635,"tags":1636,"__hash__":1639},"blog\u002Fblog\u002Fapp-fullstack-nuxt-nitro-prisma.md","Une app fullstack avec Nuxt : server routes Nitro et Prisma",{"type":9,"value":601,"toc":1619},[602,605,613,627,654,665,763,791,795,798,845,1021,1024,1047,1051,1058,1107,1114,1118,1194,1197,1330,1340,1344,1351,1589,1598,1602,1605,1609,1615],[17,603,604],{},"L'un des atouts les plus sous-estimés de Nuxt, c'est qu'il n'a pas besoin\nd'un backend séparé pour être fullstack. Le serveur est déjà là, sous le nom\nde Nitro. Ajoutez Prisma pour parler à une base de données, et vous tenez\nune application complète dans un seul dépôt, en TypeScript du front jusqu'à\nla requête SQL. Voici comment les pièces s'emboîtent.",[12,606,608,609],{"id":607},"le-dossier-server","Le dossier ",[610,611,612],"code",{},"server\u002F",[17,614,615,616,618,619,622,623,626],{},"Tout le code serveur vit dans ",[610,617,612],{},", à la racine du projet (hors de\n",[610,620,621],{},"app\u002F",", car il ne tourne jamais dans le navigateur). La convention la plus\nutile est ",[610,624,625],{},"server\u002Fapi\u002F"," : chaque fichier y devient un point d'accès HTTP.",[158,628,629,638,646],{},[161,630,631,634,635],{},[610,632,633],{},"server\u002Fapi\u002Farticles.get.ts"," répond à ",[610,636,637],{},"GET \u002Fapi\u002Farticles",[161,639,640,634,643],{},[610,641,642],{},"server\u002Fapi\u002Farticles.post.ts",[610,644,645],{},"POST \u002Fapi\u002Farticles",[161,647,648,634,651],{},[610,649,650],{},"server\u002Fapi\u002Farticles\u002F[id].get.ts",[610,652,653],{},"GET \u002Fapi\u002Farticles\u002F42",[17,655,656,657,660,661,664],{},"Le suffixe (",[610,658,659],{},".get",", ",[610,662,663],{},".post",") fixe la méthode HTTP, et l'arborescence fixe\nl'URL. Comme pour les pages : le disque décrit l'API.",[666,667,671],"pre",{"className":668,"code":669,"language":670,"meta":51,"style":51},"language-typescript shiki shiki-themes github-dark","\u002F\u002F server\u002Fapi\u002Fhello.get.ts\nexport default defineEventHandler((event) => {\n  const { nom } = getQuery(event)\n  return { message: `Bonjour ${nom ?? 'le monde'}` }\n})\n","typescript",[610,672,673,681,711,735,758],{"__ignoreMap":51},[674,675,677],"span",{"class":676,"line":62},"line",[674,678,680],{"class":679},"sAwPA","\u002F\u002F server\u002Fapi\u002Fhello.get.ts\n",[674,682,683,687,690,694,698,702,705,708],{"class":676,"line":52},[674,684,686],{"class":685},"snl16","export",[674,688,689],{"class":685}," default",[674,691,693],{"class":692},"svObZ"," defineEventHandler",[674,695,697],{"class":696},"s95oV","((",[674,699,701],{"class":700},"s9osk","event",[674,703,704],{"class":696},") ",[674,706,707],{"class":685},"=>",[674,709,710],{"class":696}," {\n",[674,712,713,716,719,723,726,729,732],{"class":676,"line":133},[674,714,715],{"class":685},"  const",[674,717,718],{"class":696}," { ",[674,720,722],{"class":721},"sDLfK","nom",[674,724,725],{"class":696}," } ",[674,727,728],{"class":685},"=",[674,730,731],{"class":692}," getQuery",[674,733,734],{"class":696},"(event)\n",[674,736,737,740,743,747,749,752,755],{"class":676,"line":187},[674,738,739],{"class":685},"  return",[674,741,742],{"class":696}," { message: ",[674,744,746],{"class":745},"sU2Wk","`Bonjour ${",[674,748,722],{"class":696},[674,750,751],{"class":685}," ??",[674,753,754],{"class":745}," 'le monde'}`",[674,756,757],{"class":696}," }\n",[674,759,760],{"class":676,"line":221},[674,761,762],{"class":696},"})\n",[17,764,765,768,769,771,772,775,776,779,780,660,783,786,787,790],{},[610,766,767],{},"defineEventHandler"," reçoit un ",[610,770,701],{}," (la requête), et ",[21,773,774],{},"ce que vous\nretournez devient la réponse JSON",". Pas de ",[610,777,778],{},"res.json()",", pas de\nsérialisation manuelle. Les utilitaires comme ",[610,781,782],{},"getQuery",[610,784,785],{},"readBody"," ou\n",[610,788,789],{},"getRouterParam"," sont auto-importés.",[12,792,794],{"id":793},"brancher-prisma","Brancher Prisma",[17,796,797],{},"Prisma est un ORM TypeScript : vous décrivez vos tables dans un schéma, il\ngénère un client typé et les migrations. Installation et schéma minimal :",[666,799,803],{"className":800,"code":801,"language":802,"meta":51,"style":51},"language-bash shiki shiki-themes github-dark","npm install -D prisma\nnpm install @prisma\u002Fclient\nnpx prisma init --datasource-provider sqlite\n","bash",[610,804,805,819,828],{"__ignoreMap":51},[674,806,807,810,813,816],{"class":676,"line":62},[674,808,809],{"class":692},"npm",[674,811,812],{"class":745}," install",[674,814,815],{"class":721}," -D",[674,817,818],{"class":745}," prisma\n",[674,820,821,823,825],{"class":676,"line":52},[674,822,809],{"class":692},[674,824,812],{"class":745},[674,826,827],{"class":745}," @prisma\u002Fclient\n",[674,829,830,833,836,839,842],{"class":676,"line":133},[674,831,832],{"class":692},"npx",[674,834,835],{"class":745}," prisma",[674,837,838],{"class":745}," init",[674,840,841],{"class":721}," --datasource-provider",[674,843,844],{"class":745}," sqlite\n",[666,846,850],{"className":847,"code":848,"language":849,"meta":51,"style":51},"language-prisma shiki shiki-themes github-dark","\u002F\u002F prisma\u002Fschema.prisma\ngenerator client {\n  provider = \"prisma-client-js\"\n}\n\ndatasource db {\n  provider = \"sqlite\"\n  url      = env(\"DATABASE_URL\")\n}\n\nmodel Article {\n  id      Int      @id @default(autoincrement())\n  titre   String\n  contenu String\n  publie  Boolean  @default(false)\n  cree_le DateTime @default(now())\n}\n","prisma",[610,851,852,857,867,877,882,887,897,906,925,929,933,943,965,973,980,998,1016],{"__ignoreMap":51},[674,853,854],{"class":676,"line":62},[674,855,856],{"class":679},"\u002F\u002F prisma\u002Fschema.prisma\n",[674,858,859,862,865],{"class":676,"line":52},[674,860,861],{"class":685},"generator",[674,863,864],{"class":692}," client",[674,866,710],{"class":696},[674,868,869,872,874],{"class":676,"line":133},[674,870,871],{"class":696},"  provider ",[674,873,728],{"class":685},[674,875,876],{"class":745}," \"prisma-client-js\"\n",[674,878,879],{"class":676,"line":187},[674,880,881],{"class":696},"}\n",[674,883,884],{"class":676,"line":221},[674,885,886],{"emptyLinePlaceholder":61},"\n",[674,888,889,892,895],{"class":676,"line":271},[674,890,891],{"class":685},"datasource",[674,893,894],{"class":692}," db",[674,896,710],{"class":696},[674,898,899,901,903],{"class":676,"line":312},[674,900,871],{"class":696},[674,902,728],{"class":685},[674,904,905],{"class":745}," \"sqlite\"\n",[674,907,908,911,913,916,919,922],{"class":676,"line":346},[674,909,910],{"class":696},"  url      ",[674,912,728],{"class":685},[674,914,915],{"class":721}," env",[674,917,918],{"class":696},"(",[674,920,921],{"class":745},"\"DATABASE_URL\"",[674,923,924],{"class":696},")\n",[674,926,927],{"class":676,"line":379},[674,928,881],{"class":696},[674,930,931],{"class":676,"line":413},[674,932,886],{"emptyLinePlaceholder":61},[674,934,935,938,941],{"class":676,"line":447},[674,936,937],{"class":685},"model",[674,939,940],{"class":692}," Article",[674,942,710],{"class":696},[674,944,945,948,951,954,957,959,962],{"class":676,"line":484},[674,946,947],{"class":696},"  id      ",[674,949,950],{"class":721},"Int",[674,952,953],{"class":692},"      @id",[674,955,956],{"class":692}," @default",[674,958,918],{"class":696},[674,960,961],{"class":721},"autoincrement",[674,963,964],{"class":696},"())\n",[674,966,967,970],{"class":676,"line":518},[674,968,969],{"class":696},"  titre   ",[674,971,972],{"class":721},"String\n",[674,974,975,978],{"class":676,"line":556},[674,976,977],{"class":696},"  contenu ",[674,979,972],{"class":721},[674,981,982,985,988,991,993,996],{"class":676,"line":588},[674,983,984],{"class":696},"  publie  ",[674,986,987],{"class":721},"Boolean",[674,989,990],{"class":692},"  @default",[674,992,918],{"class":696},[674,994,995],{"class":721},"false",[674,997,924],{"class":696},[674,999,1001,1004,1007,1009,1011,1014],{"class":676,"line":1000},16,[674,1002,1003],{"class":696},"  cree_le ",[674,1005,1006],{"class":721},"DateTime",[674,1008,956],{"class":692},[674,1010,918],{"class":696},[674,1012,1013],{"class":721},"now",[674,1015,964],{"class":696},[674,1017,1019],{"class":676,"line":1018},17,[674,1020,881],{"class":696},[17,1022,1023],{},"On applique le schéma à la base et on génère le client :",[666,1025,1027],{"className":800,"code":1026,"language":802,"meta":51,"style":51},"npx prisma migrate dev --name init\n",[610,1028,1029],{"__ignoreMap":51},[674,1030,1031,1033,1035,1038,1041,1044],{"class":676,"line":62},[674,1032,832],{"class":692},[674,1034,835],{"class":745},[674,1036,1037],{"class":745}," migrate",[674,1039,1040],{"class":745}," dev",[674,1042,1043],{"class":721}," --name",[674,1045,1046],{"class":745}," init\n",[12,1048,1050],{"id":1049},"le-client-prisma-en-singleton","Le client Prisma en singleton",[17,1052,1053,1054,1057],{},"Piège classique : instancier ",[610,1055,1056],{},"new PrismaClient()"," dans chaque route ouvre\nune nouvelle connexion à chaque appel et épuise la base. On l'instancie une\nseule fois, dans un utilitaire serveur réutilisable :",[666,1059,1061],{"className":668,"code":1060,"language":670,"meta":51,"style":51},"\u002F\u002F server\u002Futils\u002Fprisma.ts\nimport { PrismaClient } from '@prisma\u002Fclient'\n\nexport const prisma = new PrismaClient()\n",[610,1062,1063,1068,1082,1086],{"__ignoreMap":51},[674,1064,1065],{"class":676,"line":62},[674,1066,1067],{"class":679},"\u002F\u002F server\u002Futils\u002Fprisma.ts\n",[674,1069,1070,1073,1076,1079],{"class":676,"line":52},[674,1071,1072],{"class":685},"import",[674,1074,1075],{"class":696}," { PrismaClient } ",[674,1077,1078],{"class":685},"from",[674,1080,1081],{"class":745}," '@prisma\u002Fclient'\n",[674,1083,1084],{"class":676,"line":133},[674,1085,886],{"emptyLinePlaceholder":61},[674,1087,1088,1090,1093,1095,1098,1101,1104],{"class":676,"line":187},[674,1089,686],{"class":685},[674,1091,1092],{"class":685}," const",[674,1094,835],{"class":721},[674,1096,1097],{"class":685}," =",[674,1099,1100],{"class":685}," new",[674,1102,1103],{"class":692}," PrismaClient",[674,1105,1106],{"class":696},"()\n",[17,1108,1109,1110,1113],{},"Placé dans ",[610,1111,1112],{},"server\u002Futils\u002F",", il est auto-importé dans toutes les routes\nserveur. Une instance, une réserve de connexions, partagée proprement.",[12,1115,1117],{"id":1116},"une-vraie-route-qui-lit-la-base","Une vraie route qui lit la base",[666,1119,1121],{"className":668,"code":1120,"language":670,"meta":51,"style":51},"\u002F\u002F server\u002Fapi\u002Farticles.get.ts\nexport default defineEventHandler(async () => {\n  return await prisma.article.findMany({\n    where: { publie: true },\n    orderBy: { cree_le: 'desc' }\n  })\n})\n",[610,1122,1123,1128,1148,1164,1175,1185,1190],{"__ignoreMap":51},[674,1124,1125],{"class":676,"line":62},[674,1126,1127],{"class":679},"\u002F\u002F server\u002Fapi\u002Farticles.get.ts\n",[674,1129,1130,1132,1134,1136,1138,1141,1144,1146],{"class":676,"line":52},[674,1131,686],{"class":685},[674,1133,689],{"class":685},[674,1135,693],{"class":692},[674,1137,918],{"class":696},[674,1139,1140],{"class":685},"async",[674,1142,1143],{"class":696}," () ",[674,1145,707],{"class":685},[674,1147,710],{"class":696},[674,1149,1150,1152,1155,1158,1161],{"class":676,"line":133},[674,1151,739],{"class":685},[674,1153,1154],{"class":685}," await",[674,1156,1157],{"class":696}," prisma.article.",[674,1159,1160],{"class":692},"findMany",[674,1162,1163],{"class":696},"({\n",[674,1165,1166,1169,1172],{"class":676,"line":187},[674,1167,1168],{"class":696},"    where: { publie: ",[674,1170,1171],{"class":721},"true",[674,1173,1174],{"class":696}," },\n",[674,1176,1177,1180,1183],{"class":676,"line":221},[674,1178,1179],{"class":696},"    orderBy: { cree_le: ",[674,1181,1182],{"class":745},"'desc'",[674,1184,757],{"class":696},[674,1186,1187],{"class":676,"line":271},[674,1188,1189],{"class":696},"  })\n",[674,1191,1192],{"class":676,"line":312},[674,1193,762],{"class":696},[17,1195,1196],{},"Et une route qui écrit, avec validation de l'entrée :",[666,1198,1200],{"className":668,"code":1199,"language":670,"meta":51,"style":51},"\u002F\u002F server\u002Fapi\u002Farticles.post.ts\nexport default defineEventHandler(async (event) => {\n  const body = await readBody(event)\n\n  if (!body?.titre || !body?.contenu) {\n    throw createError({ statusCode: 400, message: 'Titre et contenu requis' })\n  }\n\n  return await prisma.article.create({\n    data: { titre: body.titre, contenu: body.contenu }\n  })\n})\n",[610,1201,1202,1207,1230,1246,1250,1272,1295,1300,1304,1317,1322,1326],{"__ignoreMap":51},[674,1203,1204],{"class":676,"line":62},[674,1205,1206],{"class":679},"\u002F\u002F server\u002Fapi\u002Farticles.post.ts\n",[674,1208,1209,1211,1213,1215,1217,1219,1222,1224,1226,1228],{"class":676,"line":52},[674,1210,686],{"class":685},[674,1212,689],{"class":685},[674,1214,693],{"class":692},[674,1216,918],{"class":696},[674,1218,1140],{"class":685},[674,1220,1221],{"class":696}," (",[674,1223,701],{"class":700},[674,1225,704],{"class":696},[674,1227,707],{"class":685},[674,1229,710],{"class":696},[674,1231,1232,1234,1237,1239,1241,1244],{"class":676,"line":133},[674,1233,715],{"class":685},[674,1235,1236],{"class":721}," body",[674,1238,1097],{"class":685},[674,1240,1154],{"class":685},[674,1242,1243],{"class":692}," readBody",[674,1245,734],{"class":696},[674,1247,1248],{"class":676,"line":187},[674,1249,886],{"emptyLinePlaceholder":61},[674,1251,1252,1255,1257,1260,1263,1266,1269],{"class":676,"line":221},[674,1253,1254],{"class":685},"  if",[674,1256,1221],{"class":696},[674,1258,1259],{"class":685},"!",[674,1261,1262],{"class":696},"body?.titre ",[674,1264,1265],{"class":685},"||",[674,1267,1268],{"class":685}," !",[674,1270,1271],{"class":696},"body?.contenu) {\n",[674,1273,1274,1277,1280,1283,1286,1289,1292],{"class":676,"line":271},[674,1275,1276],{"class":685},"    throw",[674,1278,1279],{"class":692}," createError",[674,1281,1282],{"class":696},"({ statusCode: ",[674,1284,1285],{"class":721},"400",[674,1287,1288],{"class":696},", message: ",[674,1290,1291],{"class":745},"'Titre et contenu requis'",[674,1293,1294],{"class":696}," })\n",[674,1296,1297],{"class":676,"line":312},[674,1298,1299],{"class":696},"  }\n",[674,1301,1302],{"class":676,"line":346},[674,1303,886],{"emptyLinePlaceholder":61},[674,1305,1306,1308,1310,1312,1315],{"class":676,"line":379},[674,1307,739],{"class":685},[674,1309,1154],{"class":685},[674,1311,1157],{"class":696},[674,1313,1314],{"class":692},"create",[674,1316,1163],{"class":696},[674,1318,1319],{"class":676,"line":413},[674,1320,1321],{"class":696},"    data: { titre: body.titre, contenu: body.contenu }\n",[674,1323,1324],{"class":676,"line":447},[674,1325,1189],{"class":696},[674,1327,1328],{"class":676,"line":484},[674,1329,762],{"class":696},[17,1331,1332,1335,1336,1339],{},[610,1333,1334],{},"createError"," renvoie une vraie erreur HTTP, que le client saura interpréter\nplutôt que de recevoir un ",[610,1337,1338],{},"200"," avec un objet d'erreur déguisé.",[12,1341,1343],{"id":1342},"le-front-consomme-sa-propre-api","Le front consomme sa propre API",[17,1345,1346,1347,1350],{},"Côté page, on ne fait pas d'appel réseau exotique : la même application\nappelle sa route avec ",[610,1348,1349],{},"useFetch",". L'URL est relative, il n'y a pas de\nproblème de CORS, et le rendu serveur pré-remplit déjà les données.",[666,1352,1356],{"className":1353,"code":1354,"language":1355,"meta":51,"style":51},"language-vue shiki shiki-themes github-dark","\u003Cscript setup lang=\"ts\">\nconst { data: articles, refresh } = await useFetch('\u002Fapi\u002Farticles')\n\nasync function publier(nouvel: { titre: string; contenu: string }) {\n  await $fetch('\u002Fapi\u002Farticles', { method: 'POST', body: nouvel })\n  await refresh()\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Carticle v-for=\"a in articles\" :key=\"a.id\">\n    \u003Ch2>{{ a.titre }}\u003C\u002Fh2>\n    \u003Cp>{{ a.contenu }}\u003C\u002Fp>\n  \u003C\u002Farticle>\n\u003C\u002Ftemplate>\n","vue",[610,1357,1358,1381,1418,1422,1463,1484,1493,1497,1506,1510,1519,1545,1559,1572,1581],{"__ignoreMap":51},[674,1359,1360,1363,1367,1370,1373,1375,1378],{"class":676,"line":62},[674,1361,1362],{"class":696},"\u003C",[674,1364,1366],{"class":1365},"s4JwU","script",[674,1368,1369],{"class":692}," setup",[674,1371,1372],{"class":692}," lang",[674,1374,728],{"class":696},[674,1376,1377],{"class":745},"\"ts\"",[674,1379,1380],{"class":696},">\n",[674,1382,1383,1386,1388,1391,1394,1397,1399,1402,1404,1406,1408,1411,1413,1416],{"class":676,"line":52},[674,1384,1385],{"class":685},"const",[674,1387,718],{"class":696},[674,1389,1390],{"class":700},"data",[674,1392,1393],{"class":696},": ",[674,1395,1396],{"class":721},"articles",[674,1398,660],{"class":696},[674,1400,1401],{"class":721},"refresh",[674,1403,725],{"class":696},[674,1405,728],{"class":685},[674,1407,1154],{"class":685},[674,1409,1410],{"class":692}," useFetch",[674,1412,918],{"class":696},[674,1414,1415],{"class":745},"'\u002Fapi\u002Farticles'",[674,1417,924],{"class":696},[674,1419,1420],{"class":676,"line":133},[674,1421,886],{"emptyLinePlaceholder":61},[674,1423,1424,1426,1429,1432,1434,1437,1440,1442,1445,1447,1450,1453,1456,1458,1460],{"class":676,"line":187},[674,1425,1140],{"class":685},[674,1427,1428],{"class":685}," function",[674,1430,1431],{"class":692}," publier",[674,1433,918],{"class":696},[674,1435,1436],{"class":700},"nouvel",[674,1438,1439],{"class":685},":",[674,1441,718],{"class":696},[674,1443,1444],{"class":700},"titre",[674,1446,1439],{"class":685},[674,1448,1449],{"class":721}," string",[674,1451,1452],{"class":696},"; ",[674,1454,1455],{"class":700},"contenu",[674,1457,1439],{"class":685},[674,1459,1449],{"class":721},[674,1461,1462],{"class":696}," }) {\n",[674,1464,1465,1468,1471,1473,1475,1478,1481],{"class":676,"line":221},[674,1466,1467],{"class":685},"  await",[674,1469,1470],{"class":692}," $fetch",[674,1472,918],{"class":696},[674,1474,1415],{"class":745},[674,1476,1477],{"class":696},", { method: ",[674,1479,1480],{"class":745},"'POST'",[674,1482,1483],{"class":696},", body: nouvel })\n",[674,1485,1486,1488,1491],{"class":676,"line":271},[674,1487,1467],{"class":685},[674,1489,1490],{"class":692}," refresh",[674,1492,1106],{"class":696},[674,1494,1495],{"class":676,"line":312},[674,1496,881],{"class":696},[674,1498,1499,1502,1504],{"class":676,"line":346},[674,1500,1501],{"class":696},"\u003C\u002F",[674,1503,1366],{"class":1365},[674,1505,1380],{"class":696},[674,1507,1508],{"class":676,"line":379},[674,1509,886],{"emptyLinePlaceholder":61},[674,1511,1512,1514,1517],{"class":676,"line":413},[674,1513,1362],{"class":696},[674,1515,1516],{"class":1365},"template",[674,1518,1380],{"class":696},[674,1520,1521,1524,1527,1530,1532,1535,1538,1540,1543],{"class":676,"line":447},[674,1522,1523],{"class":696},"  \u003C",[674,1525,1526],{"class":1365},"article",[674,1528,1529],{"class":692}," v-for",[674,1531,728],{"class":696},[674,1533,1534],{"class":745},"\"a in articles\"",[674,1536,1537],{"class":692}," :key",[674,1539,728],{"class":696},[674,1541,1542],{"class":745},"\"a.id\"",[674,1544,1380],{"class":696},[674,1546,1547,1550,1552,1555,1557],{"class":676,"line":484},[674,1548,1549],{"class":696},"    \u003C",[674,1551,12],{"class":1365},[674,1553,1554],{"class":696},">{{ a.titre }}\u003C\u002F",[674,1556,12],{"class":1365},[674,1558,1380],{"class":696},[674,1560,1561,1563,1565,1568,1570],{"class":676,"line":518},[674,1562,1549],{"class":696},[674,1564,17],{"class":1365},[674,1566,1567],{"class":696},">{{ a.contenu }}\u003C\u002F",[674,1569,17],{"class":1365},[674,1571,1380],{"class":696},[674,1573,1574,1577,1579],{"class":676,"line":556},[674,1575,1576],{"class":696},"  \u003C\u002F",[674,1578,1526],{"class":1365},[674,1580,1380],{"class":696},[674,1582,1583,1585,1587],{"class":676,"line":588},[674,1584,1501],{"class":696},[674,1586,1516],{"class":1365},[674,1588,1380],{"class":696},[17,1590,1591,1593,1594,1597],{},[610,1592,1349],{}," pour lire au rendu (avec cache et hydratation), ",[610,1595,1596],{},"$fetch"," pour les\nactions ponctuelles comme un POST. La boucle est complète : la page lit une\nroute Nitro, qui interroge Prisma, qui parle à la base, et tout est typé du\nmême bout à l'autre.",[12,1599,1601],{"id":1600},"ce-que-ça-change-au-déploiement","Ce que ça change au déploiement",[17,1603,1604],{},"Attention : dès qu'il y a des routes serveur et une base, on quitte le monde\npurement statique. Il faut une cible capable d'exécuter du Node (un VPS, une\nplateforme applicative, une fonction serverless), et non plus un simple\nhébergement de fichiers. Nitro sait produire la bonne sortie pour chacune de\nces cibles, mais le choix d'hébergement, lui, n'est plus le même que pour un\nsite statique. C'est le prix, assumé, du fullstack.",[12,1606,1608],{"id":1607},"la-leçon","La leçon",[17,1610,1611,1612,1614],{},"Nuxt fullstack, ce n'est pas « Nuxt plus un backend » : c'est un seul\nprojet, un seul langage, une seule chaîne de types, du composant Vue jusqu'à\nla table SQL. Nitro fournit le serveur, Prisma la base, et ",[610,1613,1349],{}," relie\nles deux sans couture. Pour un produit mené en petite équipe, cette unité\nest un avantage décisif : moins de code de plomberie, moins de contrats\nd'API à maintenir, et le remboursement se fait sur chaque fonctionnalité.",[1616,1617,1618],"style",{},"html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}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 pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}",{"title":51,"searchDepth":52,"depth":52,"links":1620},[1621,1623,1624,1625,1626,1627,1628],{"id":607,"depth":52,"text":1622},"Le dossier server\u002F",{"id":793,"depth":52,"text":794},{"id":1049,"depth":52,"text":1050},{"id":1116,"depth":52,"text":1117},{"id":1342,"depth":52,"text":1343},{"id":1600,"depth":52,"text":1601},{"id":1607,"depth":52,"text":1608},false,"2026-07-18","Construire l'API dans le même projet que le front : les routes serveur de Nitro, Prisma pour la base de données, et useFetch côté client. Un seul dépôt, une seule langue.",{},"\u002Fblog\u002Fapp-fullstack-nuxt-nitro-prisma",{"title":599,"description":1631},"blog\u002Fapp-fullstack-nuxt-nitro-prisma",[317,1637,1638],"Prisma","Fullstack","t2G8Y9IU16PitwCOlzmh_bflZslEo8SfxVkYuzq7oA4",{"id":1641,"title":1642,"body":1643,"brouillon":1629,"date":2271,"description":2272,"extension":58,"image":59,"imageAlt":59,"meta":2273,"navigation":61,"path":2274,"seo":2275,"stem":2276,"tags":2277,"__hash__":2279},"blog\u002Fblog\u002Fgestion-etat-nuxt-vs-flutter.md","Gestion d'état : Nuxt face à Flutter, le parallèle qui éclaire",{"type":9,"value":1644,"toc":2263},[1645,1648,1652,1655,1670,1700,1707,1711,1778,1828,1845,1849,1955,1959,1962,2109,2112,2228,2239,2243,2252,2255,2257,2260],[17,1646,1647],{},"Je travaille des deux côtés : des applications Flutter au long cours et des\nprojets web sous Nuxt. La bonne nouvelle pour qui connaît déjà l'un, c'est\nque les concepts de gestion d'état de l'autre ne sont pas nouveaux : ce sont\nles mêmes idées sous d'autres noms. Voici la table de correspondance que\nj'aurais aimé avoir en tête plus tôt.",[12,1649,1651],{"id":1650},"le-socle-réactivité-contre-notification","Le socle : réactivité contre notification",[17,1653,1654],{},"La différence de fond tient au modèle de réactivité.",[17,1656,1657,1658,1661,1662,1665,1666,1669],{},"En ",[21,1659,1660],{},"Vue\u002FNuxt",", la réactivité est implicite et automatique. Un ",[610,1663,1664],{},"ref"," est\nune valeur observable, et tout ce qui la lit se réabonne tout seul. Vous\nchangez ",[610,1667,1668],{},"compteur.value",", l'interface se met à jour, sans rien déclarer.",[17,1671,1657,1672,1675,1676,1679,1680,1683,1684,1687,1688,1691,1692,1695,1696,1699],{},[21,1673,1674],{},"Flutter",", la réactivité est explicite. Un ",[610,1677,1678],{},"ValueNotifier"," détient une\nvaleur, mais il faut appeler ",[610,1681,1682],{},"notifyListeners()"," (ou assigner ",[610,1685,1686],{},".value",") pour\nprévenir, et un widget doit s'abonner explicitement (via ",[610,1689,1690],{},"ValueListenableBuilder",",\n",[610,1693,1694],{},"ListenableBuilder",", ou le ",[610,1697,1698],{},"watch"," de Riverpod).",[17,1701,1702,1703,1706],{},"Retenez cette phrase : ",[21,1704,1705],{},"Vue observe, Flutter notifie",". Tout le reste en\ndécoule.",[12,1708,1710],{"id":1709},"la-brique-de-base","La brique de base",[666,1712,1714],{"className":668,"code":1713,"language":670,"meta":51,"style":51},"\u002F\u002F Nuxt \u002F Vue\nconst compteur = ref(0)\nconst doubler = computed(() => compteur.value * 2)\ncompteur.value++   \u002F\u002F l'UI qui lit compteur se met à jour\n",[610,1715,1716,1721,1740,1768],{"__ignoreMap":51},[674,1717,1718],{"class":676,"line":62},[674,1719,1720],{"class":679},"\u002F\u002F Nuxt \u002F Vue\n",[674,1722,1723,1725,1728,1730,1733,1735,1738],{"class":676,"line":52},[674,1724,1385],{"class":685},[674,1726,1727],{"class":721}," compteur",[674,1729,1097],{"class":685},[674,1731,1732],{"class":692}," ref",[674,1734,918],{"class":696},[674,1736,1737],{"class":721},"0",[674,1739,924],{"class":696},[674,1741,1742,1744,1747,1749,1752,1755,1757,1760,1763,1766],{"class":676,"line":133},[674,1743,1385],{"class":685},[674,1745,1746],{"class":721}," doubler",[674,1748,1097],{"class":685},[674,1750,1751],{"class":692}," computed",[674,1753,1754],{"class":696},"(() ",[674,1756,707],{"class":685},[674,1758,1759],{"class":696}," compteur.value ",[674,1761,1762],{"class":685},"*",[674,1764,1765],{"class":721}," 2",[674,1767,924],{"class":696},[674,1769,1770,1772,1775],{"class":676,"line":187},[674,1771,1668],{"class":696},[674,1773,1774],{"class":685},"++",[674,1776,1777],{"class":679},"   \u002F\u002F l'UI qui lit compteur se met à jour\n",[666,1779,1783],{"className":1780,"code":1781,"language":1782,"meta":51,"style":51},"language-dart shiki shiki-themes github-dark","\u002F\u002F Flutter\nfinal compteur = ValueNotifier\u003Cint>(0);\ncompteur.value++;  \u002F\u002F les ValueListenableBuilder abonnés se reconstruisent\n","dart",[610,1784,1785,1790,1816],{"__ignoreMap":51},[674,1786,1787],{"class":676,"line":62},[674,1788,1789],{"class":679},"\u002F\u002F Flutter\n",[674,1791,1792,1795,1798,1800,1803,1805,1808,1811,1813],{"class":676,"line":52},[674,1793,1794],{"class":685},"final",[674,1796,1797],{"class":696}," compteur ",[674,1799,728],{"class":685},[674,1801,1802],{"class":721}," ValueNotifier",[674,1804,1362],{"class":696},[674,1806,1807],{"class":721},"int",[674,1809,1810],{"class":696},">(",[674,1812,1737],{"class":721},[674,1814,1815],{"class":696},");\n",[674,1817,1818,1820,1822,1825],{"class":676,"line":133},[674,1819,1668],{"class":696},[674,1821,1774],{"class":685},[674,1823,1824],{"class":696},";  ",[674,1826,1827],{"class":679},"\u002F\u002F les ValueListenableBuilder abonnés se reconstruisent\n",[17,1829,1830,1831,1833,1834,1836,1837,1840,1841,1844],{},"Le ",[610,1832,1664],{}," de Vue correspond au ",[610,1835,1678],{}," de Flutter : une valeur\nobservable. Le ",[610,1838,1839],{},"computed"," de Vue, une valeur dérivée qui se recalcule seule,\ncorrespond à un ",[610,1842,1843],{},"Provider"," dérivé de Riverpod ou à un getter recalculé. Dans\nles deux mondes, on ne recopie jamais une valeur dérivée à la main : on la\ndéclare comme dépendant d'une autre.",[12,1846,1848],{"id":1847},"la-table-de-correspondance","La table de correspondance",[158,1850,1851,1871,1886,1906,1924,1938],{},[161,1852,1853,1861,1862,1870],{},[21,1854,1855,1857,1858],{},[610,1856,1664],{}," \u002F ",[610,1859,1860],{},"reactive"," (Vue) correspond à ",[21,1863,1864,1866,1867],{},[610,1865,1678],{}," \u002F\n",[610,1868,1869],{},"ChangeNotifier"," (Flutter) : la valeur observable de base.",[161,1872,1873,1877,1878,1881,1882,1885],{},[21,1874,1875],{},[610,1876,1839],{}," correspond à un ",[21,1879,1880],{},"provider dérivé"," (Riverpod) ou un\n",[21,1883,1884],{},"getter"," recalculé : l'état dérivé.",[161,1887,1888,1895,1896,1901,1902,1905],{},[21,1889,1890,1857,1892],{},[610,1891,1698],{},[610,1893,1894],{},"watchEffect"," correspond à ",[21,1897,1898],{},[610,1899,1900],{},"ref.listen"," (Riverpod) ou à\nl'écoute d'un ",[610,1903,1904],{},"Listenable"," : réagir à un changement pour déclencher un\neffet.",[161,1907,1908,1913,1914,1919,1920,1923],{},[21,1909,1910],{},[610,1911,1912],{},"useState"," (état partagé Nuxt) correspond à un ",[21,1915,1916,1918],{},[610,1917,1843],{}," global","\n(Riverpod) ou à un ",[610,1921,1922],{},"ChangeNotifierProvider"," (Provider) : un état accessible\ndepuis plusieurs endroits.",[161,1925,1926,1929,1930,1933,1934,1937],{},[21,1927,1928],{},"Pinia"," (store avec state, actions, getters) correspond à ",[21,1931,1932],{},"Riverpod\nstructuré"," ou à un ensemble de ",[21,1935,1936],{},"Cubit\u002FBLoC"," : le domaine métier avec sa\nlogique.",[161,1939,1940,1833,1949,1954],{},[21,1941,1942,1943,1857,1946],{},"L'injection par ",[610,1944,1945],{},"provide",[610,1947,1948],{},"inject",[21,1950,1951,1953],{},[610,1952,1843],{},"\nd'arbre de widgets"," de Flutter : fournir une dépendance à un sous-arbre.",[12,1956,1958],{"id":1957},"un-store-deux-écritures","Un store, deux écritures",[17,1960,1961],{},"Un panier, côté Pinia :",[666,1963,1965],{"className":668,"code":1964,"language":670,"meta":51,"style":51},"\u002F\u002F stores\u002Fpanier.ts\nexport const usePanier = defineStore('panier', () => {\n  const articles = ref\u003CArticle[]>([])\n  const total = computed(() =>\n    articles.value.reduce((s, a) => s + a.prix, 0)\n  )\n  function ajouter(a: Article) { articles.value.push(a) }\n  return { articles, total, ajouter }\n})\n",[610,1966,1967,1972,1998,2017,2033,2068,2073,2098,2105],{"__ignoreMap":51},[674,1968,1969],{"class":676,"line":62},[674,1970,1971],{"class":679},"\u002F\u002F stores\u002Fpanier.ts\n",[674,1973,1974,1976,1978,1981,1983,1986,1988,1991,1994,1996],{"class":676,"line":52},[674,1975,686],{"class":685},[674,1977,1092],{"class":685},[674,1979,1980],{"class":721}," usePanier",[674,1982,1097],{"class":685},[674,1984,1985],{"class":692}," defineStore",[674,1987,918],{"class":696},[674,1989,1990],{"class":745},"'panier'",[674,1992,1993],{"class":696},", () ",[674,1995,707],{"class":685},[674,1997,710],{"class":696},[674,1999,2000,2002,2005,2007,2009,2011,2014],{"class":676,"line":133},[674,2001,715],{"class":685},[674,2003,2004],{"class":721}," articles",[674,2006,1097],{"class":685},[674,2008,1732],{"class":692},[674,2010,1362],{"class":696},[674,2012,2013],{"class":692},"Article",[674,2015,2016],{"class":696},"[]>([])\n",[674,2018,2019,2021,2024,2026,2028,2030],{"class":676,"line":187},[674,2020,715],{"class":685},[674,2022,2023],{"class":721}," total",[674,2025,1097],{"class":685},[674,2027,1751],{"class":692},[674,2029,1754],{"class":696},[674,2031,2032],{"class":685},"=>\n",[674,2034,2035,2038,2041,2043,2046,2048,2051,2053,2055,2058,2061,2064,2066],{"class":676,"line":221},[674,2036,2037],{"class":696},"    articles.value.",[674,2039,2040],{"class":692},"reduce",[674,2042,697],{"class":696},[674,2044,2045],{"class":700},"s",[674,2047,660],{"class":696},[674,2049,2050],{"class":700},"a",[674,2052,704],{"class":696},[674,2054,707],{"class":685},[674,2056,2057],{"class":696}," s ",[674,2059,2060],{"class":685},"+",[674,2062,2063],{"class":696}," a.prix, ",[674,2065,1737],{"class":721},[674,2067,924],{"class":696},[674,2069,2070],{"class":676,"line":271},[674,2071,2072],{"class":696},"  )\n",[674,2074,2075,2078,2081,2083,2085,2087,2089,2092,2095],{"class":676,"line":312},[674,2076,2077],{"class":685},"  function",[674,2079,2080],{"class":692}," ajouter",[674,2082,918],{"class":696},[674,2084,2050],{"class":700},[674,2086,1439],{"class":685},[674,2088,940],{"class":692},[674,2090,2091],{"class":696},") { articles.value.",[674,2093,2094],{"class":692},"push",[674,2096,2097],{"class":696},"(a) }\n",[674,2099,2100,2102],{"class":676,"line":346},[674,2101,739],{"class":685},[674,2103,2104],{"class":696}," { articles, total, ajouter }\n",[674,2106,2107],{"class":676,"line":379},[674,2108,762],{"class":696},[17,2110,2111],{},"Le même panier, côté Cubit (flutter_bloc) :",[666,2113,2115],{"className":1780,"code":2114,"language":1782,"meta":51,"style":51},"class PanierCubit extends Cubit\u003CList\u003CArticle>> {\n  PanierCubit() : super([]);\n\n  int get total => state.fold(0, (s, a) => s + a.prix);\n\n  void ajouter(Article a) => emit([...state, a]);\n}\n",[610,2116,2117,2143,2159,2163,2198,2202,2224],{"__ignoreMap":51},[674,2118,2119,2122,2125,2128,2131,2133,2136,2138,2140],{"class":676,"line":62},[674,2120,2121],{"class":685},"class",[674,2123,2124],{"class":721}," PanierCubit",[674,2126,2127],{"class":685}," extends",[674,2129,2130],{"class":721}," Cubit",[674,2132,1362],{"class":696},[674,2134,2135],{"class":721},"List",[674,2137,1362],{"class":696},[674,2139,2013],{"class":721},[674,2141,2142],{"class":696},">> {\n",[674,2144,2145,2148,2151,2153,2156],{"class":676,"line":52},[674,2146,2147],{"class":721},"  PanierCubit",[674,2149,2150],{"class":696},"() ",[674,2152,1439],{"class":685},[674,2154,2155],{"class":721}," super",[674,2157,2158],{"class":696},"([]);\n",[674,2160,2161],{"class":676,"line":133},[674,2162,886],{"emptyLinePlaceholder":61},[674,2164,2165,2168,2171,2174,2176,2179,2182,2184,2186,2189,2191,2193,2195],{"class":676,"line":187},[674,2166,2167],{"class":721},"  int",[674,2169,2170],{"class":685}," get",[674,2172,2173],{"class":696}," total ",[674,2175,707],{"class":685},[674,2177,2178],{"class":696}," state.",[674,2180,2181],{"class":692},"fold",[674,2183,918],{"class":696},[674,2185,1737],{"class":721},[674,2187,2188],{"class":696},", (s, a) ",[674,2190,707],{"class":685},[674,2192,2057],{"class":696},[674,2194,2060],{"class":685},[674,2196,2197],{"class":696}," a.prix);\n",[674,2199,2200],{"class":676,"line":221},[674,2201,886],{"emptyLinePlaceholder":61},[674,2203,2204,2207,2209,2211,2213,2216,2218,2221],{"class":676,"line":271},[674,2205,2206],{"class":685},"  void",[674,2208,2080],{"class":692},[674,2210,918],{"class":696},[674,2212,2013],{"class":721},[674,2214,2215],{"class":696}," a) ",[674,2217,707],{"class":685},[674,2219,2220],{"class":692}," emit",[674,2222,2223],{"class":696},"([...state, a]);\n",[674,2225,2226],{"class":676,"line":312},[674,2227,881],{"class":696},[17,2229,2230,2231,2234,2235,2238],{},"La forme diffère (Pinia expose un objet réactif, le Cubit émet un nouvel\nétat immuable), mais la structure est identique : un état, une valeur\ndérivée (",[610,2232,2233],{},"total","), une action (",[610,2236,2237],{},"ajouter","). Un développeur Flutter lit le\nstore Pinia sans effort, et réciproquement.",[12,2240,2242],{"id":2241},"la-dimension-que-flutter-na-pas-le-serveur","La dimension que Flutter n'a pas : le serveur",[17,2244,2245,2246,2248,2249,2251],{},"Il y a un endroit où le parallèle s'arrête, et c'est important. Nuxt fait du\nrendu serveur : le même code de gestion d'état s'exécute côté serveur puis\ncôté client. D'où la nécessité de ",[610,2247,1912],{}," plutôt qu'un simple ",[610,2250,1664],{},"\nglobal, pour isoler l'état par requête et éviter qu'un visiteur voie les\ndonnées d'un autre (j'en parle dans l'article sur les bonnes pratiques).",[17,2253,2254],{},"Flutter n'a pas ce problème : une application tourne sur un seul appareil,\npour un seul utilisateur. Un singleton d'état y est parfaitement légitime.\nEn Nuxt, le même singleton est un bug de sécurité. C'est la principale\nadaptation mentale à faire en passant de Flutter au web rendu côté serveur.",[12,2256,1608],{"id":1607},[17,2258,2259],{},"Ne réapprenez pas la gestion d'état, traduisez-la. « Observer une valeur »,\n« en dériver une autre », « réagir à un changement », « partager un\ndomaine » : ces quatre gestes existent des deux côtés, avec des noms\ndifférents. Gardez en tête que Vue observe quand Flutter notifie, et\nn'oubliez jamais la dimension serveur de Nuxt, absente de Flutter. Avec ces\ndeux repères, passer d'un monde à l'autre devient une simple traduction, pas\nun nouvel apprentissage.",[1616,2261,2262],{},"html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}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 pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":51,"searchDepth":52,"depth":52,"links":2264},[2265,2266,2267,2268,2269,2270],{"id":1650,"depth":52,"text":1651},{"id":1709,"depth":52,"text":1710},{"id":1847,"depth":52,"text":1848},{"id":1957,"depth":52,"text":1958},{"id":2241,"depth":52,"text":2242},{"id":1607,"depth":52,"text":1608},"2026-07-16","Pour un développeur Flutter qui apprend Nuxt (ou l'inverse) : la correspondance entre ref, computed, useState, Pinia et ValueNotifier, Provider, Riverpod, Bloc.",{},"\u002Fblog\u002Fgestion-etat-nuxt-vs-flutter",{"title":1642,"description":2272},"blog\u002Fgestion-etat-nuxt-vs-flutter",[317,1674,2278],"Gestion d'état","scuvq3Ifi-uadXy9ziFqGIkbxMqEKu9B-vLJ-MLkUsU",{"id":2281,"title":2282,"body":2283,"brouillon":1629,"date":2657,"description":2658,"extension":58,"image":59,"imageAlt":59,"meta":2659,"navigation":61,"path":2660,"seo":2661,"stem":2662,"tags":2663,"__hash__":2664},"blog\u002Fblog\u002Fgestion-etat-nuxt-bonnes-pratiques.md","Gestion d'état dans Nuxt : useState, bonnes pratiques et pièges",{"type":9,"value":2284,"toc":2646},[2285,2291,2298,2340,2353,2359,2369,2416,2427,2434,2444,2460,2525,2531,2535,2544,2562,2572,2576,2629,2631,2643],[17,2286,2287,2288,2290],{},"En Vue côté client, on partage souvent un état en déclarant un ",[610,2289,1664],{}," dans un\nmodule et en l'important où on veut. Ça marche très bien dans le navigateur,\net ça casse silencieusement dès qu'on passe au rendu serveur. Comprendre\npourquoi, c'est comprendre toute la gestion d'état de Nuxt.",[12,2292,2294,2295,2297],{"id":2293},"le-piège-du-ref-partagé-en-module","Le piège du ",[610,2296,1664],{}," partagé en module",[666,2299,2301],{"className":668,"code":2300,"language":670,"meta":51,"style":51},"\u002F\u002F state.ts  - À NE PAS FAIRE en SSR\nimport { ref } from 'vue'\nexport const utilisateur = ref(null)\n",[610,2302,2303,2308,2320],{"__ignoreMap":51},[674,2304,2305],{"class":676,"line":62},[674,2306,2307],{"class":679},"\u002F\u002F state.ts  - À NE PAS FAIRE en SSR\n",[674,2309,2310,2312,2315,2317],{"class":676,"line":52},[674,2311,1072],{"class":685},[674,2313,2314],{"class":696}," { ref } ",[674,2316,1078],{"class":685},[674,2318,2319],{"class":745}," 'vue'\n",[674,2321,2322,2324,2326,2329,2331,2333,2335,2338],{"class":676,"line":133},[674,2323,686],{"class":685},[674,2325,1092],{"class":685},[674,2327,2328],{"class":721}," utilisateur",[674,2330,1097],{"class":685},[674,2332,1732],{"class":692},[674,2334,918],{"class":696},[674,2336,2337],{"class":721},"null",[674,2339,924],{"class":696},[17,2341,2342,2343,2345,2346,2349,2350,2352],{},"Côté client, ce ",[610,2344,1664],{}," est unique : un seul navigateur, un seul état. Côté\nserveur, le processus Node est ",[21,2347,2348],{},"partagé entre tous les visiteurs",". Ce\n",[610,2351,1664],{}," unique devient alors un état commun à toutes les requêtes\nsimultanées : l'utilisateur A voit les données de l'utilisateur B. C'est une\nfuite de données inter-requêtes, et elle est très difficile à repérer parce\nqu'elle n'apparaît pas en développement local avec un seul onglet.",[12,2354,2356,2358],{"id":2355},"usestate-létat-partagé-fait-pour-le-ssr",[610,2357,1912],{}," : l'état partagé fait pour le SSR",[17,2360,2361,2362,2364,2365,2368],{},"La réponse de Nuxt est ",[610,2363,1912],{},". Il crée un état réactif ",[21,2366,2367],{},"isolé par\nrequête"," côté serveur, et correctement transféré (sérialisé puis hydraté)\nvers le client :",[666,2370,2372],{"className":668,"code":2371,"language":670,"meta":51,"style":51},"\u002F\u002F app\u002Fcomposables\u002FuseUtilisateur.ts\nexport function useUtilisateur() {\n  return useState('utilisateur', () => null)\n}\n",[610,2373,2374,2379,2391,2412],{"__ignoreMap":51},[674,2375,2376],{"class":676,"line":62},[674,2377,2378],{"class":679},"\u002F\u002F app\u002Fcomposables\u002FuseUtilisateur.ts\n",[674,2380,2381,2383,2385,2388],{"class":676,"line":52},[674,2382,686],{"class":685},[674,2384,1428],{"class":685},[674,2386,2387],{"class":692}," useUtilisateur",[674,2389,2390],{"class":696},"() {\n",[674,2392,2393,2395,2398,2400,2403,2405,2407,2410],{"class":676,"line":133},[674,2394,739],{"class":685},[674,2396,2397],{"class":692}," useState",[674,2399,918],{"class":696},[674,2401,2402],{"class":745},"'utilisateur'",[674,2404,1993],{"class":696},[674,2406,707],{"class":685},[674,2408,2409],{"class":721}," null",[674,2411,924],{"class":696},[674,2413,2414],{"class":676,"line":187},[674,2415,881],{"class":696},[17,2417,2418,2419,2422,2423,2426],{},"Deux détails comptent. Le ",[21,2420,2421],{},"premier argument est une clé"," unique : deux\nappels avec la même clé partagent le même état, c'est le mécanisme de\npartage. Le ",[21,2424,2425],{},"second argument est une fonction"," d'initialisation, pas une\nvaleur directe : elle n'est appelée qu'une fois, au bon moment du cycle SSR.",[17,2428,2429,2430,2433],{},"L'envelopper dans un composable ",[610,2431,2432],{},"useUtilisateur()"," évite de répéter la clé\npartout et centralise le point d'accès. Si un jour la clé change, un seul\nfichier bouge.",[12,2435,2437,2439,2440,2443],{"id":2436},"usefetch-et-useasyncdata-létat-cest-aussi-les-données",[610,2438,1349],{}," et ",[610,2441,2442],{},"useAsyncData"," : l'état, c'est aussi les données",[17,2445,2446,2447,2449,2450,2449,2453,2456,2457,2459],{},"La majorité de l'état d'une application, ce sont des données distantes. Ne\nles gérez pas à la main avec un ",[610,2448,1664],{}," plus un ",[610,2451,2452],{},"onMounted",[610,2454,2455],{},"try\u002Fcatch",".\n",[610,2458,1349],{}," fait tout cela, et de façon compatible SSR :",[666,2461,2463],{"className":1353,"code":2462,"language":1355,"meta":51,"style":51},"\u003Cscript setup lang=\"ts\">\nconst { data: articles, status, error } = await useFetch('\u002Fapi\u002Farticles')\n\u003C\u002Fscript>\n",[610,2464,2465,2481,2517],{"__ignoreMap":51},[674,2466,2467,2469,2471,2473,2475,2477,2479],{"class":676,"line":62},[674,2468,1362],{"class":696},[674,2470,1366],{"class":1365},[674,2472,1369],{"class":692},[674,2474,1372],{"class":692},[674,2476,728],{"class":696},[674,2478,1377],{"class":745},[674,2480,1380],{"class":696},[674,2482,2483,2485,2487,2489,2491,2493,2495,2498,2500,2503,2505,2507,2509,2511,2513,2515],{"class":676,"line":52},[674,2484,1385],{"class":685},[674,2486,718],{"class":696},[674,2488,1390],{"class":700},[674,2490,1393],{"class":696},[674,2492,1396],{"class":721},[674,2494,660],{"class":696},[674,2496,2497],{"class":721},"status",[674,2499,660],{"class":696},[674,2501,2502],{"class":721},"error",[674,2504,725],{"class":696},[674,2506,728],{"class":685},[674,2508,1154],{"class":685},[674,2510,1410],{"class":692},[674,2512,918],{"class":696},[674,2514,1415],{"class":745},[674,2516,924],{"class":696},[674,2518,2519,2521,2523],{"class":676,"line":133},[674,2520,1501],{"class":696},[674,2522,1366],{"class":1365},[674,2524,1380],{"class":696},[17,2526,2527,2528,2530],{},"Il récupère les données côté serveur au premier rendu (donc le HTML arrive\ndéjà rempli, bon pour le SEO), les transfère au client sans refetch, et\ngère le cache et la déduplication via une clé. Deux composants qui appellent\n",[610,2529,1349],{}," avec la même URL ne déclenchent qu'une seule requête. Traiter les\ndonnées comme un état géré par le framework, plutôt que comme du code\nimpératif, élimine une classe entière de bugs.",[12,2532,2534],{"id":2533},"quand-sortir-pinia","Quand sortir Pinia",[17,2536,2537,2539,2540,2543],{},[610,2538,1912],{}," couvre l'état simple et partagé. Passez à Pinia (le store\nofficiel de l'écosystème Vue) quand l'état devient un ",[21,2541,2542],{},"domaine"," à part\nentière :",[158,2545,2546,2549,2556,2559],{},[161,2547,2548],{},"de la logique métier autour de l'état (actions, règles, validations) et\npas seulement une valeur ;",[161,2550,2551,2552,2555],{},"des valeurs dérivées nombreuses (des ",[610,2553,2554],{},"getters"," qui se recalculent) ;",[161,2557,2558],{},"un besoin de structurer par modules (panier, session, préférences) avec\nchacun son état, ses actions, ses getters ;",[161,2560,2561],{},"l'envie d'outils de débogage dédiés et d'un point d'entrée testable\nisolément.",[17,2563,2564,2565,2568,2569,2571],{},"Le critère n'est pas la taille de l'application mais la ",[21,2566,2567],{},"nature de\nl'état"," : une poignée de valeurs partagées, ",[610,2570,1912],{}," suffit ; un domaine\navec de la logique, Pinia paie son coût.",[12,2573,2575],{"id":2574},"quelques-pièges-concrets","Quelques pièges concrets",[158,2577,2578,2591,2609],{},[161,2579,2580,2586,2587,2590],{},[21,2581,2582,2583,2585],{},"Ne mettez pas de valeur non sérialisable dans ",[610,2584,1912],{},"."," L'état\ntransite du serveur au client en JSON. Une instance de classe, une\nfonction ou une ",[610,2588,2589],{},"Date"," ne survivent pas au voyage. Stockez des données\nsimples et reconstruisez les objets riches côté client si besoin.",[161,2592,2593,2596,2597,2600,2601,660,2603,1691,2606,2585],{},[21,2594,2595],{},"Attention aux clés qui se chevauchent."," Deux ",[610,2598,2599],{},"useState('data', ...)","\ndans deux contextes différents partagent le même état par accident.\nNommez les clés comme un espace de noms : ",[610,2602,1990],{},[610,2604,2605],{},"'session-user'",[610,2607,2608],{},"'prefs-theme'",[161,2610,2611,2622,2623,2626,2627,2585],{},[21,2612,2613,2614,2617,2618,2621],{},"N'accédez pas à ",[610,2615,2616],{},"window"," ou ",[610,2619,2620],{},"localStorage"," à l'initialisation."," Ce code\ns'exécute aussi côté serveur, où ces objets n'existent pas. Pour un état\npersistant côté navigateur, ",[610,2624,2625],{},"useCookie"," fonctionne des deux côtés ; sinon,\ngardez l'accès au stockage dans ",[610,2628,2452],{},[12,2630,1608],{"id":1607},[17,2632,2633,2634,2636,2637,2639,2640,2642],{},"La gestion d'état en Nuxt se résume à une prise de conscience : votre code\ntourne côté serveur autant que côté client, et un état mal isolé fuit entre\nles requêtes. À partir de là, tout s'ordonne : ",[610,2635,1912],{}," pour l'état\npartagé simple, ",[610,2638,1349],{}," pour les données distantes, Pinia quand l'état\ndevient un domaine. Le ",[610,2641,1664],{}," global de module, lui, reste dans les projets\npurement client. Choisissez l'outil selon la portée de l'état, pas selon\nl'habitude.",[1616,2644,2645],{},"html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}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 pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":51,"searchDepth":52,"depth":52,"links":2647},[2648,2650,2652,2654,2655,2656],{"id":2293,"depth":52,"text":2649},"Le piège du ref partagé en module",{"id":2355,"depth":52,"text":2651},"useState : l'état partagé fait pour le SSR",{"id":2436,"depth":52,"text":2653},"useFetch et useAsyncData : l'état, c'est aussi les données",{"id":2533,"depth":52,"text":2534},{"id":2574,"depth":52,"text":2575},{"id":1607,"depth":52,"text":1608},"2026-07-14","useState, partage compatible SSR, pollution inter-requêtes, quand sortir Pinia : les règles pour gérer l'état d'une application Nuxt sans se tirer une balle dans le pied.",{},"\u002Fblog\u002Fgestion-etat-nuxt-bonnes-pratiques",{"title":2282,"description":2658},"blog\u002Fgestion-etat-nuxt-bonnes-pratiques",[317,2278],"ihNvcDqGs6Bw_Po635w67cxCJfCqxO-qiJgBxrfmGII",1784715924528]