yesid.dev
Un portfolio CMS bilingue, édité dans Directus et servi aux visiteurs depuis du contenu généré prêt pour l’edge.
yesid.dev est bâti autour du même résultat que je vendrais à un client : éditer le contenu dans Directus, régénérer le cache TypeScript, puis servir des pages qui n'appellent pas le CMS à chaque visite.
Le résultat : un site bilingue facile à mettre à jour, sans taxe d'exécution sur chaque page vue. Le contenu garde une seule source de vérité, le site reste rapide à l'edge, et les prochains projets peuvent hériter du même système de marque au lieu de repartir à zéro.
La qualité fait partie de l'identité ici. C'est un produit fièrement québécois, bilingue par défaut, avec le français et l'anglais traités comme deux surfaces de première classe au lieu d'une traduction ajoutée à la fin.
La voix montréalaise compte, mais les détails aussi : une mise en page mobile-first, des contrôles accessibles, les thèmes light et dark, puis le comportement prefers-reduced-motion. Le but, ce n'est pas seulement que le site me ressemble. Il faut qu'il se comporte comme si je fais attention.
Le pipeline de contenu est volontairement simple : Directus garde le texte et les médias sur Neon, export-fallbacks.ts transforme cet état en cache TypeScript généré, puis SvelteKit sert le résultat sur Vercel. Le point important, c'est la frontière : les éditeurs ont un vrai CMS, les visiteurs reçoivent des fichiers prêts pour l'edge.
flowchart LR
directus["CMS Directus"] --> export["export-fallbacks.ts"]
export --> cache["cache TypeScript généré"]
cache --> svelte["SvelteKit"]
svelte --> vercel["edge Vercel"]- `Directus` est la source de vérité pour le texte, les médias, les sections de projet et les métriques.
- La rangée projet possède les images héro, les variantes light et le toggle `featured` du proof reel.
- Les fichiers générés dans `apps/web/src/lib/content` sont un cache de build, pas du contenu écrit à la main.
- Les visiteurs publiés passent par la sortie `SvelteKit` et la livraison edge `Vercel`, avec `0` appel CMS par visite.
- Le CMS dev et le CMS prod restent séparés pour relire le contenu avant la promotion.
- Le verrou d'intégrité attrape les dérives du cache généré avant qu'une poussée puisse les cacher.
Le modèle média héro est explicite parce que l'ordre de la galerie n'est pas un contrat. L'image 1 est l'image bureau par défaut. L'image 2 est optionnelle et veut dire mobile ou secondaire seulement quand le champ CMS est rempli. Les autres images restent dans la galerie du cas d'étude.
type ProjectHeroMedia = {
image: string;
imageLight?: string;
imageSecondary?: string;
imageSecondaryLight?: string;
featured: boolean;
};
// image 1 drives desktop/default cards.
// image 2 is optional and drives mobile/secondary split cards.Les assets et le contenu passent par le même chemin répétable. On envoie les médias dans Directus, on met à jour la rangée du CMS dev, puis on régénère le cache fallback importé par le site web.
export OP_SERVICE_ACCOUNT_TOKEN="$(grep ^OP_TOKEN= .env | cut -d= -f2-)"
op run --env-file=apps/cms/.env -- env PUBLIC_DIRECTUS_URL=https://cms.dev.yesid.dev bun apps/cms/scripts/migrate-assets.ts
op run --env-file=apps/cms/.env -- env PUBLIC_DIRECTUS_URL=https://cms.dev.yesid.dev bun apps/cms/scripts/content-projects-yesid.ts --apply
op run --env-file=apps/cms/.env -- env PUBLIC_DIRECTUS_URL=https://cms.dev.yesid.dev bun apps/cms/scripts/export-fallbacks.ts --module=projectsLa commande de régénération peut aussi rouler seule quand le CMS contient déjà le bon état et que seul le cache généré doit être rafraîchi.
export OP_SERVICE_ACCOUNT_TOKEN="$(grep ^OP_TOKEN= .env | cut -d= -f2-)"
op run --env-file=apps/cms/.env -- env PUBLIC_DIRECTUS_URL=https://cms.dev.yesid.dev bun apps/cms/scripts/export-fallbacks.ts --module=projectsLe module généré est le contrat consommé par l'app web. L'app importe du contenu typé, pas des réponses CMS en direct, donc le rendu reste prévisible et économique.
type GeneratedContentCache = {
source: 'Directus CMS';
file: 'apps/web/src/lib/content/projects.ts';
contract: 'Project[]';
runtimeCmsCallsPerVisit: 0;
};
export const projects = [...] satisfies Project[];La gate de qualité reste volontairement simple. Même un changement de contenu doit passer le type checking, les tests des scripts CMS, les tests web et le verrou d'intégrité avant la poussée opérateur.
cd apps/web && bunx svelte-check --tsconfig ./tsconfig.json
cd apps/cms && bun test
cd apps/web && bunx vitest runLe collapsible README reste dernier exprès. Les repos publics peuvent le remplir automatiquement; les repos privés de clients peuvent garder la preuve technique dans le CMS sans exposer le code source.
Étude de cas d'un portfolio SvelteKit bilingue avec édition Directus CMS, contenu TypeScript généré et livraison edge Vercel sans appel CMS par visite.