Marathon HYPERMODE : 8 plans, 1 portfolio, 1 nuit
Architecture monorepo Astro 6 + Cloudflare livrée en marathon coordonné. Comment structurer 7 apps + 7 packages avec pnpm workspaces.
TL;DR — J’ai livré un portfolio complet en 8 plans exécutés en séquence dans une seule nuit de travail intensif. Stack : Astro 6 + pnpm workspaces + Cloudflare Pages. La clé : isoler chaque plan dans un worktree git séparé pour éviter la pollution de contexte, déployer par incréments, et tagger chaque plan livré. 7 apps Astro + 7 packages partagés, 126 commits, 80+ tests, conformité Loi 25/Loi 96 intégrée par construction. Voici comment la structure tient.
Quand on bâtit un portfolio qui est censé démontrer ses compétences d’ingénierie, le portfolio lui-même doit être de l’ingénierie solide. Pas un template Notion public, pas un site WordPress thémé — une vraie architecture monorepo avec CI, semantic versioning, et déploiement multi-apps.
Voici la structure exacte que j’ai construite, les décisions d’architecture qui la font tenir, et les erreurs que j’aurais évitées si je recommençais.
Structure du monorepo
jescygratton-monorepo/
├── apps/
│ ├── portfolio/ # Site principal jescygratton.ca
│ ├── demo-loi25/ # loi25.jescygratton.ca
│ ├── demo-osbl-starter/ # osbl.jescygratton.ca
│ ├── demo-lois-qc/ # lois-qc.jescygratton.ca
│ ├── demo-tpstvq/ # tpstvq.jescygratton.ca
│ ├── demo-hreflang/ # hreflang.jescygratton.ca
│ └── demo-benevoles/ # benevoles.jescygratton.ca
└── packages/
├── design-system/ # Composants Astro + tokens Aurora
├── i18n/ # Traductions FR/EN + routage
├── ui/ # Islands React partagées
├── seo/ # BaseHead, JsonLd, sitemap
├── analytics/ # CloudflareAnalytics
├── legal/ # Textes Loi 25, mentions légales
└── tsconfig/ # Config TypeScript partagée
Le fichier pnpm-workspace.yaml est minimal :
packages:
- 'apps/*'
- 'packages/*'
Chaque app référence les packages locaux via workspace:* dans son package.json. pnpm résout les liens symboliques au node_modules/.pnpm/ central — pas de duplication des dépendances communes comme React ou Zod.
Astro 6 + adapter Cloudflare
Astro 6 a introduit le loader API pour les Content Collections — c’est le changement le plus structurant. Au lieu de placer les fichiers MDX directement dans src/content/, on déclare un loader dans la collection :
// src/content.config.ts
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'zod';
const blog = defineCollection({
loader: glob({ pattern: 'fr/*.mdx', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
description: z.string(),
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
tags: z.array(z.string()),
readingMinutes: z.number().int().min(1),
draft: z.boolean().default(false),
}),
});
export const collections = { blog };
L’avantage : validation Zod à la compilation. Si un article a un champ manquant ou mal typé, astro build échoue — avant le déploiement.
L’adapter Cloudflare (@astrojs/cloudflare) transforme les routes non-prerenderées en Cloudflare Workers. Pour le portfolio, presque tout est export const prerender = true — seule l’API de contact tourne comme Worker.
// astro.config.ts
import cloudflare from '@astrojs/cloudflare';
export default defineConfig({
output: 'static',
adapter: cloudflare(),
integrations: [mdx(), react(), sitemap()],
});
Le piège :
output: 'static'avecadapter: cloudflare()permet quand même des routes dynamiques viaexport const prerender = false. Le mixing est possible, mais chaque route non-prerenderée consomme un Workers invocation.
Worktrees git pour paralléliser
Chaque plan a été développé dans un worktree isolé :
git worktree add .worktrees/plan-1-foundation -b plan-1-foundation
git worktree add .worktrees/plan-2-design -b plan-2-design
# ...
git worktree add .worktrees/plan-9-blog -b plan-9-blog
L’avantage concret : chaque plan a son propre répertoire de travail avec sa propre branche, mais partage le même .git. On peut travailler sur le plan 3 pendant que le plan 2 est en review, sans git stash ni checkout qui casse l’état d’un autre agent.
Pour les agents IA utilisés comme multiplicateurs de productivité, l’isolation par worktree évite la contamination de contexte — chaque agent travaille dans son répertoire et ne voit que sa branche.
CI/CD avec GitHub Actions
Le pipeline est simple mais robuste :
# .github/workflows/ci.yml
jobs:
ci:
steps:
- uses: pnpm/action-setup@v4
- run: pnpm install --frozen-lockfile
- run: pnpm lint # Biome sur tout le monorepo
- run: pnpm typecheck # astro check par app
- run: pnpm build # Build avant les tests
- run: pnpm test --passWithNoTests
--passWithNoTests est critique : les packages utilitaires n’ont pas tous des tests, et on ne veut pas que CI échoue sur un package vide. Les tests métier (validation schemas, routage i18n) sont couverts dans les packages qui en ont besoin.
Semantic versioning et tags
Chaque plan livré reçoit un tag :
v0.1.0-foundation → Plan 1 : base Astro + CI
v0.2.0-design → Plan 2 : tokens Aurora + design system
...
v0.8.0-launch-ready → Plan 8 : QA + matériaux de démarchage
Convention : MAJOR.MINOR.PATCH-plan-name. Le MAJOR reste à 0 jusqu’au lancement public. Chaque plan = MINOR bump. Les hotfixes pendant le développement = PATCH.
git tag -a v0.1.0-foundation -m "Plan 1 — Fondation monorepo + CI"
git push origin v0.1.0-foundation
Design system Aurora
Les tokens Aurora sont des variables CSS définies dans le design system et importées par toutes les apps :
/* packages/design-system/src/tokens.css */
:root {
--jg-bg: #09090b; /* zinc-950 */
--jg-accent: #f59e0b; /* amber-500 */
--jg-fg: #fafafa;
--jg-fg-muted: #a1a1aa;
--jg-font-sans: 'Plus Jakarta Sans', sans-serif;
--jg-text-sm: 0.875rem;
--jg-text-base: 1rem;
/* ... */
}
L’avantage d’utiliser des variables CSS plutôt que Tailwind config : les tokens sont accessibles dans n’importe quel fichier CSS/Astro sans dépendance au moteur Tailwind. Chaque app importe global.css qui inclut les tokens, et les utilise directement dans ses <style> scoped.
Ce que je referais différemment
1. Commencer par les tests de schema. J’ai ajouté les tests de validation Zod après avoir créé les articles — je l’aurais fait avant pour définir le contrat du schema.
2. Un seul astro.config.ts de base étendu par app. Chaque app a sa propre config Astro, ce qui crée de la duplication sur les options communes. Une config base exportable aurait réduit ça.
3. Preview déploiements par PR. Cloudflare Pages supporte les preview deployments sur PR — je l’aurais configuré dès le Plan 1 pour avoir un lien de review automatique sur chaque PR.
Pour aller plus loin
- Astro Content Collections (loader API) — documentation officielle sur le nouveau loader API introduit en v5+
- pnpm Workspaces — gestion des packages locaux avec liaison symbolique
- Cloudflare Pages + Workers — hybrid static + edge functions
- Conventional Commits — convention de messages de commit structurés