# Gabriel Mustiere > CTO freelance basé à Nantes. 14 ans dans la tech, CTO depuis 2017. Architecture, leadership technique, SaaS & e-commerce. Site : https://mustiere.fr · GitHub : https://github.com/gabrielmustiere · LinkedIn : https://www.linkedin.com/in/gabriel-mustiere/ · Contact : mustiere.gabriel@gmail.com Localized variants: https://mustiere.fr/llms-full.txt (default) · https://mustiere.fr/en/llms-full.txt --- ## À propos Gabriel Mustiere est CTO freelance basé à Nantes. 14 ans dans la tech, CTO depuis 2017. Expert Symfony et Sylius, builder de produits SaaS et e-commerce from scratch. Il a bâti la filiale SaaS Progicar (Groupe GEMY) de zéro à plus de 25 personnes en cinq ans (CTO fondateur, 2017 → 2022), puis cofondé Passion Barbecue où il a porté la plateforme e-commerce Sylius de bout en bout (2022 → 2025). Depuis septembre 2025, freelance. Mission en cours : Anytime, conformité PSD2/SCA via WebAuthn (passkeys, biométrie, clés de sécurité matérielles). Disponibilité prochaine : Q3 2026, en mission longue, CTO fractional pour startup early stage, audit ou sparring. Parcours détaillé : https://mustiere.fr/parcours/ --- ## Articles ### PHP & Symfony en 2026 : la stack qui mérite l’attention des CTO *Publié le 2026-05-06 · Catégorie : Tech · URL : https://mustiere.fr/blog/php-symfony-2026-perspective-cto/* **Résumé.** Quatorze ans de PHP/Symfony, une cinquantaine de projets, et un constat : en 2026, l’écosystème couvre l’ensemble du spectre des produits web modernes. Le langage est devenu strict et performant — PHP 8.5 typé, property hooks, pipe operator, JIT IR, Fibers stables. Symfony fédère plus de cinquante composants découplés (HTTP, Messenger, Workflow, Security, Mailer, Notifier, Uid…) et un écosystème complet : API Platform pour les API REST/GraphQL, Sylius pour l’e‑commerce, EasyAdmin pour le back‑office, Mercure pour le temps réel, Live Components pour l’UI réactive, bridge Hotwire Native pour iOS/Android, Initiative Symfony AI pour les apps IA-first. L’outillage est industriel : PHPStan level 10, Rector, PHPUnit 13, PHP CS Fixer, `composer audit`, Deptrac. FrankenPHP en worker mode bouscule l’équation infra — 3 à 4× le throughput de PHP-FPM, p95 divisé par cinq, migration peu coûteuse. C’est aujourd’hui une option par défaut, pas un compromis nostalgique. Quatorze ans de PHP. Une cinquantaine de projets menés depuis 2012 — de la petite application de gestion au SaaS complexe couvrant l’intégralité des processus métiers d’une usine, en passant par un projet e‑commerce from scratch ou encore de l’architecture hexagonale pour une néo‑banque. Un fil constant : Symfony. Cet article n’a pas vocation à comparer ni à opposer les écosystèmes. Il propose un regard actuel, en 2026, depuis l’intérieur : celui d’un langage devenu strict et performant, d’un ensemble de frameworks capables de couvrir l’ensemble des besoins — API, SaaS, e‑commerce, back‑office, temps réel, mobile, IA —, d’un outillage mature, et d’une infrastructure qui a, discrètement mais définitivement, fait disparaître le débat sur la performance. Je m’adresse ici aux CTO et lead tech en quête d’une vision claire et à jour de l’écosystème Symfony, ainsi qu’aux développeurs PHP qui souhaitent réévaluer le potentiel réel de leur stack. Ce qui suit n’est pas un plaidoyer, mais un retour d’expérience ancré dans le terrain. ## Ce que j’ai vu se transformer en quatorze ans Je travaille en PHP/Symfony depuis 2012. C’est une spécialisation assumée : à force d’enchaîner les projets dans le même écosystème, on développe une intuition qui rend chaque nouveau cadrage plus rapide, chaque livraison plus prévisible, chaque maintenance plus simple. Ce n’est pas un choix d’opportunité — c’est ce qui permet, sur la plupart des projets que j’aborde, de livrer en quelques jours ce qui, sans cette maîtrise, prendrait souvent plusieurs semaines. Sur ces quatorze ans, l’écosystème a connu plusieurs transformations qu’il faut regarder ensemble plutôt que séparément : - Le langage est passé d’un terrain permissif à un environnement typé, strict et désormais rapide. PHP 8.5 n’a plus grand-chose à voir avec le PHP 5.3 du début de ma carrière. - Symfony s’est imposé comme socle, puis a structuré un écosystème complet — API Platform, Sylius, UX Initiative, Mercure — couvrant la plupart des besoins sans avoir à assembler des briques disparates. - L’outillage est devenu systémique : PHPStan, Rector, PHP CS Fixer, PHPUnit, Composer, Symfony CLI. La qualité ne repose plus uniquement sur la discipline individuelle, elle est intégrée au workflow. - L’infrastructure a franchi un cap avec FrankenPHP en worker mode. La performance, longtemps perçue comme une faiblesse structurelle, n’est plus un sujet. - Plus récemment, des initiatives comme Symfony UX, Symfony AI ou le bridge Hotwire Native étendent l’écosystème vers des terrains où on l’attendait moins : front réactif, apps IA-first, mobile. Les sections qui suivent détaillent ces transformations telles que je les utilise réellement en production : le langage, les frameworks, l’outillage, l’infrastructure, le front et le mobile, puis l’IA. En conclusion, une grille de lecture en cinq critères pour situer précisément les contextes où l’écosystème PHP est le plus pertinent. ## Le langage a rattrapé — et dépassé — ce qu'on en attendait Le PHP de 2026 n’a plus grand-chose à voir avec celui de 2012. La transformation s’est faite en deux ruptures, puis une phase d’accumulation. PHP 7.0 fin 2015 a refondu le moteur (Zend Engine 3) et acté un saut de performance d'environ ×2 sur des charges typiques. PHP 8.0 fin 2020 a posé le système de types moderne et introduit le JIT. Tout ce qui s'est ajouté depuis, de 8.1 à 8.5, relève de l'épaisseur, pas de la rupture. Côté **typage**, sept versions ont transformé un langage permissif en un langage strict : Types scalaires (7.0), propriétés typées (7.4), unions et mixed (8.0), enums et intersections (8.1), readonly classes et DNF (8.2), constantes de classe typées (8.3), property hooks et visibilité asymétrique (8.4). Concrètement, aujourd’hui, une entité métier peut s’écrire ainsi : ```php final readonly class Order { public function __construct( public OrderId $id, public Status $status, public private(set) array $lines = [], ) {} public string $reference { get => sprintf('ORD-%s-%s', $this->status->value, $this->id); } } ``` `readonly` sur la classe interdit la mutation des propriétés. `private(set)` (8.4) limite l'écriture à la classe tout en gardant la lecture publique. Le bloc `get => ...` (property hook, 8.4) remplace le getter trivial. Le constructeur emporte déclaration et affectation des propriétés (constructor promotion, 8.0). Cette densité d'expression est ce qu'un développeur Kotlin ou C# moderne attend, et elle est désormais native côté PHP. Côté **expressivité**, les ajouts syntaxiques de la branche 8.x sont moins commentés, mais immédiatement visibles à la lecture. `match` (8.0) remplace `switch` avec comparaison stricte et retour de valeur. L'opérateur nullsafe `?->` chaîne sans test null intermédiaire. Les attributs natifs `#[Attribute]` (8.0) ont enterré les annotations docblock. Le pipe operator `|>` (8.5) fait passer une valeur de gauche à droite sans variables intermédiaires : ```php $slug = $title |> trim(...) |> mb_strtolower(...) |> fn(string $s) => preg_replace('/[^a-z0-9]+/', '-', $s); ``` Pris isolément, chacun de ces ajouts est anodin. Cumulés, ils transforment la lecture d'un fichier PHP de 2026 par rapport à un fichier de 2018. Côté **performance**, la base de PHP 8.x est déjà environ trois fois plus rapide que le PHP 7.4 que beaucoup ont en tête quand ils parlent de « lenteur PHP ». Le JIT IR introduit en 8.4 et raffiné en 8.5 ajoute 5 à 15 % sur les charges arithmétiques, en consommant moins de mémoire que le précédent backend. Sur des applications web typiques, IO-bound, le JIT seul ne change pas grand-chose ; c'est l'ensemble — moteur 7.0, JIT 8.0, IR 8.4/8.5, OPcache désormais compilé statiquement dans 8.5 — qui a effacé l'argument structurel de lenteur. Côté **asynchrone**, les Fibers introduites en 8.1 sont stables depuis quatre versions majeures et utilisables en production via [AmPHP](https://amphp.org/) ou [ReactPHP](https://reactphp.org/). L’ergonomie n’est pas celle `d’async/await` en JavaScript, mais le résultat fonctionnel est le même : gérer des `I/O` concurrentes sans recourir aux threads. Le langage que je présente à un junior aujourd'hui est plus proche d'un C# moderne ou d'un Kotlin que du PHP 5 qu'il imagine. Ça change complètement la nature du débat : on ne parle plus de savoir si PHP est robuste, mais dans quels cas il est le meilleur choix. ## Symfony : un écosystème qui couvre tout Symfony est mon socle de travail depuis Symfony 2 — modulaire, fortement typé, chaque composant utilisable indépendamment. Le vrai argument en 2026, c'est ce qui s'est construit autour. Ces briques couvrent directement les besoins produits les plus courants : | Besoin | Composant principal | Ce qui sort de la boîte | | -------------------- | ------------------------- | ----------------------------------------------------------------------------------- | | API REST / GraphQL | API Platform | CRUD générés, OpenAPI auto, filtres, pagination, sérialisation, GraphQL natif | | E-commerce | Sylius | Catalogue, panier, checkout, taxes, promotions, multi-canal, multi-devise, headless | | Back-office admin | EasyAdmin | CRUD complet sur entités Doctrine en quelques lignes, dashboard, filtres, exports | | Temps réel | Mercure | Server-sent events, intégration native API Platform | | UI réactive sans JS | Symfony UX Live Component | Composants Twig avec état serveur, mises à jour AJAX automatiques | | Mobile (iOS/Android) | Turbo + Hotwire Native | App native qui réutilise le HTML serveur, sans React Native ni Flutter | Et derrière, le **framework lui-même repose sur plus de cinquante composants découplés**, chacun installable indépendamment dans n'importe quel projet PHP. Le panorama, par domaine, donne la mesure de la couverture : | Domaine | Composants | | --------------------- | --------------------------------------------------------------------------------------------------- | | HTTP & routing | HttpFoundation, HttpKernel, HttpClient (sync/async/HTTP/2), Routing, Mime, WebLink | | Console & runtime | Console, Process, Runtime, Dotenv | | Configuration & DI | Config, DependencyInjection, OptionsResolver, Yaml, ExpressionLanguage | | Sécurité | Security (Core/Bundle/Csrf/Http), PasswordHasher, RateLimiter, Lock, Semaphore, HtmlSanitizer, Ldap | | Données & types | Validator, Serializer, Form, PropertyAccess, PropertyInfo, TypeInfo, ObjectMapper, JsonPath | | Async & événements | Messenger, Scheduler, EventDispatcher, RemoteEvent, Webhook, Workflow (state machines) | | Communication | Mailer (Amazon/SendGrid/Mailgun/Postmark/Brevo…), Notifier (SMS, push, Slack, Telegram, Discord…) | | Cache & perf | Cache (PSR-6/16 + tags), Clock, Stopwatch | | Filesystem & assets | Filesystem, Finder, Asset, AssetMapper (sans bundler ni Node) | | I18n & identifiants | Translation, Intl, Emoji, Uid (UUID v1–v8, ULID) | | Test & debug | BrowserKit, DomCrawler, CssSelector, Panther (Chrome/Firefox), VarDumper, VarExporter, ErrorHandler | | Frontend (Symfony UX) | Turbo, LiveComponent, TwigComponent, Stimulus, Autocomplete, Icons, Chart.js, Map, React/Vue/Svelte | | IA (depuis 2025) | AI Platform, AI Agent, AI Chat/Store, bridges OpenAI/Anthropic/Gemini/Mistral/Cohere, MCP Bundle | Tout ce qui demande ailleurs un assemblage de plusieurs librairies tierces et de services externes — queue + worker, retry + DLQ, signed URL + storage, OAuth + 2FA + rate limit, OpenTelemetry + clock injecté pour les tests — existe ici sous une API homogène, maintenue par la même équipe, documentée au même endroit. Sur la plupart des produits que je vois passer — SaaS B2B, marketplace, plateforme métier — chacune de ces briques s'utilise sans glue code. **API Platform** en particulier mérite qu'on s'y arrête : on déclare une entité Doctrine, on annote, et on obtient une API REST + GraphQL + OpenAPI documentée + Mercure-pushable, sans écrire un controller. Dans certains contextes, le gain de productivité se compte en facteurs, pas en pourcentages. Je n’ai pas utilisé **Laravel** en profondeur sur la durée, mais il faut le mentionner : l'autre grand framework PHP, communauté massive, DX très soignée, écosystème (Forge, Vapor, Livewire, Reverb) cohérent et orienté product. Pour une petite équipe qui privilégie la convention sur la configuration, c'est un choix qui se défend très bien. **WordPress**, que je n'ai pas non plus en main au quotidien, reste un incontournable de l'écosystème — environ 40 % des sites web tournent dessus, le marché de plugins commerciaux est mature, et il propose désormais une vraie API REST/GraphQL via WPGraphQL pour qui veut l'utiliser comme headless CMS. L'écosystème PHP ne se résume pas à Symfony, et ses différentes branches couvrent l'ensemble du spectre des produits web modernes. ## L'outillage qui rend la qualité industrielle C’est probablement le segment où le décalage entre perception et réalité est le plus fort. En quatorze ans, j'ai vu l'outillage PHP passer d'un assemblage de scripts maison à un socle industriel — au point d'être aujourd'hui plus stable, à mes yeux, que de nombreux écosystèmes front modernes où les outils évoluent très vite. **[PHPStan](https://phpstan.org/)** est devenu la colonne vertébrale de l'analyse statique. Le mode strict (level 9) attrape une très large majorité des erreurs typées avant l'exécution ; le level 10, sorti avec la version 2.0, traque jusqu'aux types `mixed` non résolus et est devenu un badge qualité dans l'OSS PHP. Une codebase Symfony sérieuse passe au minimum level 8 en CI. L'extension [`phpstan/phpstan-symfony`](https://github.com/phpstan/phpstan-symfony) ajoute la connaissance fine du container, des services, du routing et des paramètres — concrètement, PHPStan sait que `$container->get('doctrine')` retourne un `Registry` et que tel paramètre est un `int`. C'est précieux. **[Rector](https://getrector.com/)** est l'outil qui m'a fait passer plusieurs codebases de PHP 7.4 + Symfony 4 à PHP 8.5 + Symfony 7 sans semaines perdues. Il connaît les règles de migration de PHP, de Symfony, de Doctrine, et il les applique sur des centaines de fichiers d'une commande. Le set `SymfonySetList::SYMFONY_70` enchaîne en quelques minutes des transformations qui prendraient des semaines à la main. Il permet notamment le passage des annotations aux attributs PHP 8, la migration des services et la mise à jour des signatures de contrôleurs. **[PHPUnit](https://phpunit.de/)** reste le framework de test de référence dans l'écosystème Symfony, et la version 13 sortie en février 2026 confirme qu'il évolue avec une grande rigueur — métadonnées par attributs PHP 8, attentes plus strictes, isolation par défaut. Couplé au `WebTestCase` et au `KernelTestCase` fournis par Symfony, il couvre l'intégralité du spectre, du test unitaire au test fonctionnel HTTP. Pour les tests d'API, [API Platform](https://api-platform.com/) fournit son propre `ApiTestCase` qui s'intègre nativement à PHPUnit. Et pour ceux qui veulent valider la pertinence de leurs tests, [Infection](https://infection.github.io/) complète l'arsenal via mutation testing. **[PHP CS Fixer](https://cs.symfony.com/)** est le formatter de référence côté Symfony, avec ses presets `@Symfony` et `@Symfony:risky` qui appliquent automatiquement les conventions de la communauté. Configuration en `.php-cs-fixer.dist.php`, intégration CI/IDE, fin du débat sur le style. La règle `@PER-CS2.0` permet en plus de s'aligner sur le standard PHP-FIG le plus récent. Côté sécurité, [`composer audit`](https://getcomposer.org/doc/03-cli.md#audit) est intégré nativement à Composer depuis la 2.4, et depuis la 2.9 il bloque automatiquement l'installation de dépendances vulnérables. À intégrer en CI, sans discussion. Pour aller plus loin, [Roave Security Advisories](https://github.com/Roave/SecurityAdvisories) verrouille au niveau du `composer.json` lui-même, et le [Local PHP Security Checker](https://github.com/fabpot/local-php-security-checker) maintenu par Symfony reste une option en complément. Pour l'architecture, [Deptrac](https://github.com/qossmic/deptrac) ou [PHPat](https://github.com/carlosas/phpat) (en extension PHPStan) permettent de tester les contrats entre couches — interdire qu'un contrôleur appelle directement un repository, par exemple — et de transformer la discipline d'architecture en règles vérifiées en CI. Sur les projets Symfony qui structurent leur code en bounded contexts ou en architecture hexagonale, c'est un filet de sécurité qui paie immédiatement. Une CI minimale, mais sérieuse, sur un projet Symfony en 2026 ressemble à ceci : ```yaml # .github/workflows/ci.yml (extrait) - run: composer install --no-progress - run: vendor/bin/php-cs-fixer fix --dry-run --diff # style - run: vendor/bin/phpstan analyse # types (level 8+) - run: vendor/bin/rector --dry-run # déprécations à venir - run: vendor/bin/phpunit --coverage-clover=clover.xml - run: composer audit # CVE ``` Six commandes, six garanties concrètes : style propre, types corrects, code aligné sur la prochaine version, comportement testé, pas de dépendance vulnérable. C'est exactement le niveau que j'attends d'un projet TypeScript sérieux. À cela j'intègre systématiquement la [Symfony CLI](https://symfony.com/download), qui embarque un serveur dev TLS, l'intégration Docker et un security checker, et [Castor](https://castor.jolicode.com/) (porté par JoliCode) comme task runner moderne pour orchestrer les commandes du projet — en remplacement de Make ou de scripts shell disparates, avec une vraie ergonomie PHP et l'autocomplétion qui va avec. ### Mentions spéciales En complément, quelques outils structurants de l’écosystème méritent d’être mentionnés : Côté testing, **[Pest](https://pestphp.com/)** mérite d'être mentionné même si je reste fidèle à PHPUnit : sa syntaxe expressive et sa version 4 — qui intègre [Playwright](https://pestphp.com/docs/browser-testing) pour les tests browser sans stack JavaScript séparée — en ont fait le framework qui monte le plus vite, particulièrement côté Laravel (plus de 39 millions d'installations début 2026). Pour une équipe qui démarre from scratch, c'est une option défendable. Côté analyse statique, **[Psalm](https://psalm.dev/)**, maintenu désormais par Daniil Gentili, reste l'alternative à PHPStan. Sa singularité tient à son taint analysis — détection statique des XSS, SQL injection et command injection — qui n'a pas d'équivalent gratuit dans l'écosystème. Côté runtimes, au-delà de FrankenPHP traité dans la section suivante, **[RoadRunner](https://roadrunner.dev/)** (serveur d'application en Go avec queue et gRPC intégrés) et **[Swoole](https://www.swoole.com/)** (extension coroutines/event-loop) sont les deux autres choix sérieux pour sortir du modèle PHP-FPM. Pour le serverless, **[Bref](https://bref.sh/)** est devenu le standard de fait pour exécuter du PHP sur AWS Lambda. Côté environnement de dev, **[DDEV](https://ddev.com/)** s'impose comme la solution Docker multi-stack la plus mature toutes communautés confondues (Drupal, WordPress, TYPO3, Symfony, Laravel), tandis que **[Laravel Herd](https://herd.laravel.com/)** offre une expérience native macOS/Windows particulièrement soignée pour qui veut éviter Docker en local. Côté distribution, **[PHIVE](https://phar.io/)** (Phar Installation and Verification Environment) permet d'installer des outils QA signés GPG sans polluer le `composer.json` du projet, et **[Box](https://github.com/box-project/box)** reste le standard pour packager une application PHP en PHAR autonome. Enfin, mention pour **[Mago](https://github.com/carthage-software/mago)**, un nouvel entrant écrit en Rust qui ambitionne d'unifier linter, formatter et analyseur statique dans un seul binaire, à la manière de ce que [Biome](https://biomejs.dev/) fait côté JavaScript. C’est encore jeune (pré-1.0 fin 2025), mais typiquement le genre d’outil à surveiller de près pour anticiper les prochaines évolutions de l’écosystème. ## FrankenPHP — la rupture qui change l'équation infra C’est probablement la transformation la plus structurante de ces deux dernières années. [FrankenPHP](https://frankenphp.dev/), porté par Kévin Dunglas (le créateur d’API Platform), est un serveur d’application PHP construit sur Caddy, distribué en binaire unique, et qui propose un **worker mode** : le process PHP reste en mémoire entre les requêtes, et le bootstrap du framework n’est exécuté qu’une fois au démarrage du worker. Le résultat n’est pas une amélioration marginale. Sur une application Symfony standard, voici un benchmark publié sur Symfony 7.4 ([source DEV.to, début 2026](https://dev.to/mattleads/from-45ms-to-8ms-benchmarking-symfony-74-on-frankenphp-ekk)) : | Setup | Throughput | Latence p95 | | ------------------------ | ---------- | ----------- | | Nginx + PHP-FPM | 1 240 RPS | 45 ms | | FrankenPHP (worker mode) | 3 850 RPS | 8 ms | Trois fois plus de requêtes par seconde, latence p95 divisée par cinq. Sur Laravel, certaines migrations en worker mode rapportées par la communauté montrent des gains très significatifs sur des endpoints lents (à contextualiser selon les cas). Ces résultats doivent être validés dans le contexte de votre charge, mais l’ordre de grandeur est représentatif. L’implication côté infrastructure est directe : à trafic constant, on divise par trois ou quatre le nombre de workers nécessaires. Sur un SaaS B2B servant quelques milliers d’utilisateurs concurrents, cela se traduit typiquement par un passage de quatre à six instances applicatives à une ou deux. Le coût mensuel suit. Le déploiement est également simplifié : un binaire FrankenPHP peut embarquer le serveur, le runtime PHP et la configuration HTTP (Caddy), avec HTTPS automatique, HTTP/3 et intégration native avec Mercure. On s’éloigne d’une stack classique Nginx + PHP-FPM + reverse proxy à orchestrer. La migration est étonnamment peu coûteuse. La plupart des applications Symfony tournent en worker mode sans changement, à condition de respecter une règle simple : ne pas conserver d’état mutable entre deux requêtes. Les rares services qui cachent de la donnée request-scoped doivent implémenter `ResetInterface`, ce qui est généralement rapide à corriger. L’argument historique de la « lenteur PHP » reposait sur le modèle process-par-requête de PHP-FPM. Ce modèle n’est plus la seule option — et, dans la majorité des projets web applicatifs, la performance n’est plus un facteur discriminant. ## Front, mobile et temps réel sans empiler de stack JS C’est probablement le segment où PHP a longtemps été à la traîne. Une application web moderne implique de l’interactivité côté client — et la réponse historique côté PHP n’a jamais été pleinement convaincante. C’est ce qui a changé. **[Symfony UX Live Components](https://ux.symfony.com/live-component)** est l’approche que j’utilise désormais par défaut sur les SaaS B2B. Un composant Twig porté par une classe PHP, qui se met à jour côté serveur en réponse aux interactions utilisateur, rendu HTML envoyé par fragments, état conservé côté serveur, synchronisé via des attributs HTML signés. Pas de framework JavaScript, pas de bundle, pas de duplication de logique métier entre front et back. Concrètement : ```php // src/Twig/Components/UserSearch.php #[AsLiveComponent] final class UserSearch { use DefaultActionTrait; #[LiveProp(writable: true)] public string $query = ''; public function __construct(private UserRepository $users) {} public function getResults(): array { return $this->users->searchByName($this->query, limit: 20); } } ``` ```twig {# templates/components/UserSearch.html.twig #}
``` C’est tout ce qu’il faut. Le `data-model` lie l’input à `$query`, le rendu se met à jour à chaque frappe (avec debounce). Dans la grande majorité des écrans d’admin et de SaaS B2B, ce niveau d’interactivité suffit largement — et on a écrit zéro JavaScript. Pour le **temps réel**, Mercure est intégré nativement à Symfony et API Platform. Un publisher côté serveur, un consommateur SSE côté navigateur, et on obtient des notifications, du collaborative editing ou des mises à jour temps réel — sans WebSocket à gérer. Pour le **mobile**, l’arrivée du **bridge Hotwire Native côté Symfony** (via `symfony/turbo` couplé au shell natif Hotwire de 37signals) change l’arbitrage. Au lieu de maintenir trois codebases — web React, app iOS Swift, app Android Kotlin — on maintient une codebase Symfony qui rend du HTML. Deux shells natifs minimaux chargent ce HTML dans des WebView optimisées. Quand on a besoin de natif (caméra, notifications push, capteurs), on l’expose via des bridges JS↔natif. C’est notamment l’approche utilisée par Basecamp pour HEY, et elle est désormais pleinement utilisable en stack Symfony. Sur un produit SaaS B2B avec une app mobile companion, c’est souvent le meilleur compromis : équipe unique, codebase unique, parité de feature quasi immédiate. ## L’IA côté produit : ce que change l’écosystème Symfony La dernière objection que j’entends de plus en plus, c’est que les apps IA-first se font en Python — c’est là que sont les bibliothèques, c’est là que sont les benchmarks. C’est en partie vrai pour la R&D et l’inférence custom. Ce n’est pas le cas pour la majorité des applications qui consomment des LLM via API — ce qui couvre une très large majorité des produits IA aujourd’hui : chatbots métier, RAG, agents, tool use, copilotes intégrés. L’[**Initiative Symfony AI**](https://symfony.com/blog/kicking-off-the-symfony-ai-initiative) lancée par Christopher Hertel formalise ce qui était déjà bricolé dans plusieurs projets. Concrètement, elle couvre : - appels aux LLM (OpenAI, Anthropic, Mistral, Ollama) - gestion des messages et des tool calls - embeddings et stores vectoriels (Pinecone, Qdrant, pgvector) - pipelines RAG - agents avec planification C’est encore jeune et en évolution rapide, mais déjà exploitable sur la majorité des cas applicatifs côté produit. Sur un projet récent où il fallait ajouter un assistant métier RAG sur une base documentaire client, j’ai monté la chose en Symfony : - ingestion des documents en background via Messenger - embeddings stockés en pgvector - retrieval + génération via Symfony AI - UI Live Component pour le chat avec streaming SSE via Mercure Tout dans la même codebase. Pas de service Python séparé à orchestrer, pas de double déploiement, pas de fragmentation de l’architecture. ### Quand PHP/Symfony est le bon choix en 2026 Voici comment je positionne aujourd’hui PHP/Symfony pour un nouveau produit : | Critère | Verdict PHP/Symfony en 2026 | | ------------------------ | ------------------------------------------------------------------------------------------ | | Vélocité de delivery | API Platform + EasyAdmin + Live Components → MVP complet et structuré en quelques semaines | | Coût d'infra | FrankenPHP worker mode → 3–4× le throughput de PHP-FPM, coûts divisés d’autant | | Profil de recrutement | Marché fluide : pool large, séniors stables, freelances disponibles | | Longévité du code | Rector + PHPStan → migrations automatisées, code maintenable sur dix ans et plus | | Couverture fonctionnelle | API, SaaS, e‑commerce, back‑office, temps réel, mobile, IA — tout est couvert | Il existe des cas où une autre stack est mieux placée. Pour de la recherche IA pure, du calcul scientifique, du temps réel sub‑milliseconde, ou du tooling système, PHP n’est pas le bon choix — et personne ne prétendra le contraire. Mais pour la grande majorité des produits modernes — SaaS B2B, e‑commerce, back‑office métier, API exposées, assistants IA connectés aux données — l’écosystème PHP est aujourd’hui une option **par défaut** parfaitement défendable. Pas un compromis nostalgique : un choix de praticien, ancré dans quatorze ans d’observation directe. C’est ce constat que je voulais poser, après quatorze ans de terrain. --- ### Comment j'ai construit ce site avec Claude et Astro *Publié le 2026-04-24 · Catégorie : Tech · URL : https://mustiere.fr/blog/construire-ce-site-avec-claude-et-astro/* **Résumé.** Claude Design a produit les maquettes, Claude Code les a transformées en site Astro statique. Le SEO sérieux tient dans six briques : schéma Zod, hreflang, JSON-LD, sitemap, llms.txt, et un site assez rapide pour que Google n'ait rien à redire. Ce site existait depuis trois ans sous forme d'une page HTML figée. Je voulais un vrai blog, bilingue, propre sur le SEO, sans me transformer en développeur frontend à temps plein. Je m'en suis sorti en deux week-ends grâce à deux outils : **Claude** pour le design, **Claude Code** pour l'implémentation en Astro. Voici ce que j'ai retenu, avec un focus appuyé sur la partie référencement — c'est là que la plupart des portfolios de CTO se plantent. ## Étape 1 — designer avec Claude Design, pas avec Figma Je ne suis pas designer. Ouvrir Figma pour itérer sur une page d'accueil coûte cher en friction. Claude Design, lui, sait produire une maquette, du HTML + Tailwind qui s'affiche immédiatement dans un navigateur. Le processus : 1. Je décris l'identité visuelle (éditoriale, sobre, typographie mixte serif + sans + mono) et les sections attendues. 2. Claude Deisgn me rend trois artifacts HTML autonomes : accueil, liste d'articles, article. 3. J'itère en langage naturel (« la sidebar est trop dense », « fais sauter le bandeau Tweaks », « passe l'accent de cette section en ocre »). 4. À la fin, je demande à Claude de rédiger un **handoff document** : tokens de design, palette oklch, structure Astro recommandée, comportements JS. Ce handoff, c'est le fichier `README.md` du repo — je l'utilise comme brief à l'entrée de Claude Code. Les maquettes HTML deviennent la référence de fidélité pixel-perfect ; Claude Code n'a plus qu'à les recoder en composants Astro idiomatiques. ## Étape 2 — implémenter avec Claude Code et Astro **Pourquoi Astro ?** Trois raisons concrètes pour un site de contenu : - **Zéro JS par défaut.** Ce que je n'écris pas ne peut pas casser, ne ralentit pas les Core Web Vitals, et ne m'oblige pas à hydrater des îlots pour afficher du texte. - **Content Collections typées.** Chaque article est un `.mdx` validé par un schéma Zod. Le build casse si un champ manque — pas de date manquante qui plombe le sitemap en prod. - **i18n natif.** Un simple bloc dans `astro.config.mjs` et je tiens deux arbres `/fr` et `/en` sans router maison. Claude Code, lui, est à son aise sur ce genre de codebase : lecture large de la structure, édits ciblés, respect des conventions du projet. Je travaille en plan-then-execute : je lui demande d'abord de planifier un découpage en petites tâches, je relis, puis je laisse exécuter. ## Focus SEO — six briques non négociables Pour un site de CTO freelance, le SEO n'est pas un plan marketing. C'est l'hygiène qui fait que Google, Perplexity, ChatGPT et les recruteurs trouvent les bonnes pages dans le bon ordre. Voici ce qui porte 90 % du résultat. ### 1. Un schéma de contenu strict La première brique SEO n'est pas dans le HTML, elle est dans le modèle de données. Chaque article déclare des champs obligatoires via Zod, sinon le build casse : ```ts // src/content.config.ts const blog = defineCollection({ loader: chapteredGlob({ base: './src/content/blog', extensions: ['.mdx', '.md'], }), schema: ({ image }) => z.object({ title: z.string().max(120), excerpt: z.string().min(80).max(220), publishedAt: z.coerce.date(), updatedAt: z.coerce.date().optional(), category: z.enum(['IA', 'Tech', 'Lead', 'Business']), tags: z.array(z.string()).default([]), keywords: z.array(z.string()).default([]), cover: image().optional(), resume: resumeSchema, // injecté depuis resume.mdx faq: z.array(faqItem).default([]), // injecté depuis faq.mdx sources: z.array(sourceItem).default([]), // injecté depuis sources.mdx number: z.number().int().positive(), lang: z.enum(['fr', 'en']).default('fr'), translationOf: z.string().optional(), }), }); ``` `excerpt` est borné entre 80 et 220 caractères parce que c'est la longueur utile d'une meta description. Le champ `resume` n'est pas une chaîne de frontmatter : c'est un objet `{ markdown, html, plain }` injecté par `chapteredGlob` à partir d'un fichier `resume.mdx` réservé à la racine du dossier de l'article — même mécanique pour `faq.mdx` (questions structurées exposées en JSON-LD `FAQPage`) et `sources.mdx` (citations vérifiables). On édite du contenu, pas du YAML, et le résultat reste exploitable côté LLM via `resume.plain`. `translationOf` relie deux versions linguistiques du même article : indispensable pour les balises `hreflang`. **Format dossier multi-chapitres.** Au-delà de 1500 mots, je découpe l'article en `index.mdx` (frontmatter + intro) + chapitres `NN-.mdx` que le loader agrège alphabétiquement. Cet article-ci en est un exemple. Le slug public ne change pas — c'est le nom du dossier — mais la rédaction et la review d'un long billet deviennent gérables, fichier par fichier, plutôt que de naviguer dans un seul `.mdx` de 2000 lignes. ### 2. Hreflang et canonical systématiques Un site bilingue mal balisé se tire une balle dans le pied : Google indexe les deux versions comme des doublons. Le `BaseLayout` émet une balise canonique et deux `hreflang` sur **chaque page**, en se basant sur le `translationOf` du contenu : ```astro ``` `x-default` pointe vers la version française — c'est ma version principale. Sans ce bloc, un moteur ne saurait pas laquelle des deux pages servir à un utilisateur anglophone. ### 3. JSON-LD pour chaque type d'entité Les données structurées sont encore sous-utilisées par les devs et lues par toutes les IA qui crawlent le web. J'émets trois schémas sur chaque article : `Person`, `BlogPosting`, `BreadcrumbList`. ```ts // src/utils/schema.ts (extrait) export function blogPostingSchema(p: BlogPostingInput) { const lang = p.lang ?? 'fr'; const url = `${SITE.url}${localizedPath(lang, `/blog/${p.slug}`)}`; return { '@context': 'https://schema.org', '@type': 'BlogPosting', '@id': `${url}#article`, mainEntityOfPage: { '@type': 'WebPage', '@id': url }, url, headline: p.title, description: p.description, datePublished: p.publishedAt, dateModified: p.updatedAt ?? p.publishedAt, inLanguage: LANG_META[lang].bcp47, articleSection: p.category, keywords: p.keywords.join(', '), timeRequired: p.readingTime ? `PT${p.readingTime}M` : undefined, author: { '@id': `${SITE.url}/#person` }, publisher: { '@id': `${SITE.url}/#person` }, }; } ``` Le même `Person` est référencé partout via `@id`, pas dupliqué. C'est ce que Google veut : un graphe, pas un tas de JSON dispersés. ### 4. Sitemap et robots.txt IA-friendly Deux intégrations Astro font 95 % du travail. Le sitemap est généré au build avec les locales déclarées ; `robots.txt` autorise explicitement les crawlers IA que je veux voir citer mon contenu. ```js // astro.config.mjs (extrait) integrations: [ sitemap({ i18n: { defaultLocale: 'fr', locales: { fr: 'fr-FR', en: 'en-GB' } }, changefreq: 'weekly', priority: 0.7, }), robotsTxt({ sitemap: [`${SITE.url}/sitemap-index.xml`], policy: [ { userAgent: 'GPTBot', allow: '/' }, { userAgent: 'ClaudeBot', allow: '/' }, { userAgent: 'PerplexityBot', allow: '/' }, { userAgent: 'Google-Extended', allow: '/' }, { userAgent: 'Bytespider', disallow: '/' }, { userAgent: '*', allow: '/', disallow: ['/404'] }, ], }), ], ``` Bloquer Bytespider n'est pas de l'idéologie : c'est un crawler bruyant qui consomme de la bande passante sans citer ses sources. Ouvrir GPTBot, ClaudeBot et PerplexityBot, au contraire, c'est accepter d'être cité par les assistants que mes prospects utilisent déjà. ### 5. Un `llms.txt` pour l'ère des LLM Personne ne sait encore si le standard `llms.txt` va s'imposer, mais il coûte presque rien et résout un vrai problème : un LLM qui crawle mon site a besoin d'un plan éditorial, pas d'un sitemap XML. ```ts // src/pages/llms.txt.ts (extrait — FR à la racine, defaultLocale sans préfixe) export const GET: APIRoute = async () => { const posts = (await getCollection('blog', (entry) => isPublished(entry, 'fr'))).sort((a, b) => b.data.publishedAt.getTime() - a.data.publishedAt.getTime()); const lines: string[] = [`# ${SITE.name}`, '', '## Articles', '']; for (const post of posts) { lines.push(`- [${post.data.title}](${SITE.url}/blog/${post.id}/) ` + `(${toISODate(post.data.publishedAt)}, ${post.data.category}) : ${post.data.excerpt}`); } return new Response(lines.join('\n'), { headers: { 'Content-Type': 'text/plain; charset=utf-8' }, }); }; ``` Les excerpts stricts du schéma Zod (point 1) deviennent ici des résumés utilisables par un modèle sans qu'il ait à charger chaque article. En complément, `src/pages/llms-full.txt.ts` génère le **corpus markdown complet** des articles — c'est le standard que pousse Jeremy Howard (auteur originel de `llms.txt`) pour que les modèles puissent raisonner sur l'intégralité du contenu sans crawler page par page. Coût marginal au build, gain potentiel si l'un de ces standards finit par s'imposer côté indexation IA. ### 6. Statique, léger, rapide Les cinq premiers points ne valent rien si la page met 4 secondes à s'afficher. Astro sort du HTML pur, Tailwind v4 génère le CSS utilisé, et trois options dans `astro.config.mjs` achèvent le travail : ```js build: { inlineStylesheets: 'auto', format: 'file' }, prefetch: { prefetchAll: false, defaultStrategy: 'hover' }, trailingSlash: 'never', ``` Le `inlineStylesheets: 'auto'` embarque le CSS critique dans le HTML initial — zéro aller-retour pour le premier paint. Le `prefetch` au hover précharge la page au moment où la souris entre sur un lien. `trailingSlash: 'never'` évite les redirections 301 entre `/blog/` et `/blog` qui plombent les scores PageSpeed. ## Ce que je ferais différemment - **Commencer par le schéma**, pas par le design. Un champ `keywords` oublié au départ se paie en migration plus tard. - **Écrire la page `/llms.txt` dès le jour 1.** C'est cinq lignes de code et ça force à avoir des excerpts propres partout. - **Mesurer le Lighthouse sur mobile à chaque commit.** J'ai un `pa11yci` et un `lighthouserc.json` dans le repo ; je les aurais posés plus tôt. ## Le code est ouvert Le repo complet est public : [github.com/gabrielmustiere/mustiere.fr](https://github.com/gabrielmustiere/mustiere.fr). Si vous voulez cloner la structure pour votre propre site de consultant, servez-vous — le schéma, les utils SEO et le squelette i18n sont directement réutilisables. ---