@monark_it/mch-blog
v0.10.9
Published
Runtime de blog pour les sites Monark : liste, article, RSS, sitemap, SEO, thème et revalidation. Tout le contenu vient de la **release publiée par le Monark Content Hub** — aucun fichier Markdown local, aucune base de données côté site.
Readme
@monark_it/mch-blog
Runtime de blog pour les sites Monark : liste, article, RSS, sitemap, SEO, thème et revalidation. Tout le contenu vient de la release publiée par le Monark Content Hub — aucun fichier Markdown local, aucune base de données côté site.
1. Le blog en 30 secondes
- Hub → release → site. Vous publiez dans le Hub ; le site lit la release et la sert sur
/blog. - Le package fournit le rendu (liste paginée par 10, article, RSS, sitemap), le SEO (canonical, Open Graph, JSON-LD, redirections 301), le thème et la revalidation (webhook signé).
- Le site ne garde que des wrappers fins :
npm install @monark_it/mch-blog@latestmet à jour tout ce comportement sans toucher aux fichiers du site. - Peer dependencies :
react18 ou 19,next≥ 15.
Ce que contient un site équipé :
| Fichier | Rôle |
| -------------------------------- | -------------------------------------------------------------------------- |
| app/blog/layout.tsx | importe styles.css |
| app/blog/page.tsx | liste (BlogListPage) + metadata |
| app/blog/[slug]/page.tsx | article (ArticlePage), redirections 301, JSON-LD, generateStaticParams |
| app/blog/rss.xml/route.ts | flux RSS (getBlogRssResponse, si RSS activé) |
| app/sitemap.ts | sitemap du site + articles (getBlogSitemap, créé ou patché) |
| app/api/mch/[...path]/route.ts | route Hub : hello, revalidate, well-known |
| app/blog/_overrides/index.ts | registry des composants personnalisés |
2. Démarrage
# 1. wrappers, registry et variables du site, puis installer la dépendance
npx @monark_it/mch-create-blog
npm install
# 2. associer le site au Hub (code à approuver dans le Hub, page /connect)
npx @monark_it/mch-create-blog connect --url https://exemple.com
# 3. copier les 6 variables MCH_* listées par connect dans la plateforme d'hébergement
# 4. déployer
# 5. publier un article depuis le Hub, ouvrir /blogLe parcours détaillé (questions, options, dépannage) est dans le README de @monark_it/mch-create-blog.
3. Installation et commandes
npm install @monark_it/mch-blog installe le runtime. Peer dependencies : react 18 ou 19 et next ≥ 15 (entrée /next).
Les trois commandes du scaffolder :
| Commande | Rôle |
| ------------------------------------------------ | --------------------------------------- |
| npx @monark_it/mch-create-blog | wrappers, registry et variables du site |
| npx @monark_it/mch-create-blog connect --url … | association au Hub et route API |
| npx @monark_it/mch-create-blog customize | éjection d'un composant personnalisable |
Options et dépannage : README de @monark_it/mch-create-blog.
4. Personnalisation
Trois niveaux, du plus simple au plus lourd : le Design dans le Hub (sans code), l'éjection de composants avec customize, puis la page headless.
4.1 Design (sans code)
Dans le Hub, onglet Design du site, l'aperçu est rendu par les composants réels du blog.
| Réglage | Ce qui change | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | | Couleurs | palette claire et palette sombre : accent, surfaces, textes, liens, code, citations, tableaux, focus | | Typographie | taille du texte, interligne, tailles et couleur des titres | | Liste | grille ou liste, nombre de colonnes | | Carte | couverture (haut, côté, aucune), métas, extrait, auteur, catégorie, ordre (titre d'abord ou métas d'abord), survol, fond et bordure | | Article | couverture contenue, pleine largeur ou aucune | | Articles liés | cartes, liste ou compact, avec ou sans couverture | | Page | titre, sous-titre, mode sombre | | Largeur du contenu | largeur maximale de la page (défaut 1200, plage 480–1440, réglable dans Design) | | Markdown | soulignement des liens, alignement et largeur des images, zébrures des tableaux, thème du code, séparateurs, boutons | | Par emplacement | surcharge des réglages liste, carte, article ou liés selon l'endroit |
Deux points à connaître :
- Contraste : le Hub vérifie les paires texte/fond (AA) et signale celles à corriger ; le bouton « Corriger tout » ajuste les couleurs automatiquement.
- Mode sombre : une palette sombre définie n'est servie qu'avec
darkMode: system(préférence du visiteur). Par défaut, la palette claire s'applique.
Les réglages enregistrés partent avec la prochaine publication du site : publiez depuis le Hub pour les voir en ligne.
4.2 Composants personnalisés (customize)
npx @monark_it/mch-create-blog customize --list
npx @monark_it/mch-create-blog customize --slot article-cardSept emplacements sont éjectables :
| Slot (--slot) | Fichier éjecté | Ce qui change |
| ----------------- | ------------------------------------------------------- | --------------------------------------------------------- |
| article-card | app/blog/_overrides/ArticleCard/ArticleCard.tsx | carte d'un article dans la liste |
| related-card | app/blog/_overrides/RelatedCard/RelatedCard.tsx | section « Articles liés » |
| article-header | app/blog/_overrides/ArticleHeader/ArticleHeader.tsx | titre, métas, tags et couverture de l'article |
| article-content | app/blog/_overrides/ArticleContent/ArticleContent.tsx | enveloppe du corps Markdown |
| article-layout | app/blog/_overrides/ArticleLayout/ArticleLayout.tsx | ordre des blocs de l'article |
| empty-state | app/blog/_overrides/EmptyState/EmptyState.tsx | message d'une liste sans article |
| markdown | app/blog/_overrides/Markdown/MarkdownComponents.tsx | rendu des balises du corps (a, img, table, code…) |
Chaque éjection écrit le composant et son CSS dans un sous-dossier _overrides/<Slot>/, puis branche le registry app/blog/_overrides/index.ts :
app/blog/_overrides/
index.ts ← registry (imports + branchements)
ArticleCard/
ArticleCard.tsx
ArticleCard.css
ArticleLayout/
ArticleLayout.tsx
ArticleLayout.css
...Le principe : les pages importent listComponents, articleComponents et markdownComponents depuis le registry. Éditez le composant, le rendu suit ; les pages ne bougent pas.
| Option | Effet |
| --------------- | ---------------------------------------------------------------------------------------------------- |
| --list | liste les 7 emplacements et leur état (éjecté/défaut), sans écrire |
| --slot <id> | éjecte un emplacement (répétable ou séparé par des virgules ; numéro accepté) |
| --remove <id> | retire un emplacement : dossier supprimé, défauts du package rebranchés (répétable ; numéro accepté) |
| --remove-all | retire tous les emplacements et revient au registry par défaut (alias --reset) |
| --force | réécrit/supprime sans confirmation, y compris un composant modifié à la main |
| --dry-run | affiche les écritures/suppressions sans écrire |
--slot et --remove/--remove-all sont exclusifs. Sans --force, un composant déjà éjecté est conservé et un composant modifié n'est jamais supprimé sans confirmation : vos modifications ne sont jamais écrasées ni effacées à votre insu. La commande ne lance aucune commande de gestion de versions ; committez les fichiers comme le reste du site.
Après une éjection, le Hub est informé automatiquement. Le registry exporte la liste overrides, la route API la transmet à chaque revalidation et sur /.well-known/mch-release, et le Hub affiche un bandeau « Composants personnalisés » dans Design. Rien à déclarer à la main ; la liste se met à jour au prochain échange entre le Hub et le site.
Dé-éjection
Pour revenir aux composants par défaut du package :
npx @monark_it/mch-create-blog customize --remove article-card
npx @monark_it/mch-create-blog customize --remove article-card,empty-state
npx @monark_it/mch-create-blog customize --remove-all # alias : --resetLe retrait supprime le dossier _overrides/<Slot>/ et débranche proprement le registry (import, entrée, ligne overrides) en rebranchant le défaut du package :
| Retiré | Rebranché sur |
| ----------------- | ------------------------------ |
| article-card | BlogCard |
| article-layout | ordre par défaut du package |
| article-header | ArticleHeader |
| related-card | Related |
| empty-state | état vide du package |
| article-content | ArticleContent |
| markdown | composants Markdown par défaut |
Quand le dernier override part, le registry redevient identique au template (overrides = []) : le Hub ne voit plus aucun composant personnalisé au prochain échange. --remove sur un emplacement déjà au défaut est un no-op, et un composant modifié à la main n'est supprimé qu'après confirmation ou avec --force. En interactif (sans --slot/--remove), le menu propose : éjecter / retirer / lister.
Recettes
Carte custom — éditez ArticleCard.tsx ; le composant reçoit BlogCardProps (article, config, design, placement) :
import { formatDate, getArticlePath, type BlogCardProps } from "@monark_it/mch-blog/ui";
export function ArticleCard({ article, config }: BlogCardProps) {
return (
<a className="ma-carte" href={getArticlePath(config, article.slug)}>
<h2>{article.title}</h2>
<time dateTime={article.published_at}>{formatDate(article.published_at, config.locale)}</time>
</a>
);
}Métas en bas — article-layout fournit les blocs Cover, Title, Meta, Tags, Content, Related, déjà liés à l'article :
import type { ArticleLayoutProps } from "@monark_it/mch-blog/ui";
export function ArticleLayout({ blocks }: ArticleLayoutProps) {
const { Cover, Title, Meta, Tags, Content, Related } = blocks;
return (
<>
<Title />
<Tags />
<Cover />
<Content />
<Meta />
<Related />
</>
);
}Bouton personnalisé — le slot markdown surcharge les balises du corps ; un lien rendu en bouton porte la classe mch-button :
import {
MARKDOWN_BUTTON_CLASS,
defaultMarkdownComponents,
type MarkdownComponents,
} from "@monark_it/mch-blog/ui";
export const markdownComponents: MarkdownComponents = {
...defaultMarkdownComponents,
a: ({ className, ...props }) => (
<a
{...props}
className={className?.includes(MARKDOWN_BUTTON_CLASS) ? `${className} ma-marque` : className}
/>
),
};4.3 Page headless
Pour une page 100 % custom, resolveBlogContent donne les articles et le design ; designStyle et Markdown conservent le thème et le rendu du corps.
import {
Markdown,
designStyle,
resolveBlogContent,
resolveEnvBlogConfig,
} from "@monark_it/mch-blog";
export default async function Page() {
const config = resolveEnvBlogConfig();
const { articles, design } = await resolveBlogContent(config);
return (
<main style={designStyle(design)}>
{articles.map((article) => (
<article key={article.id}>
<h2>{article.title}</h2>
<Markdown design={design}>{article.body}</Markdown>
</article>
))}
</main>
);
}Compromis : vous renoncez à la pagination, aux metadata, au JSON-LD, aux redirections et à la revalidation clés en main. À réserver aux pages très spécifiques ; pour le reste, partez des wrappers et éjectez un composant.
4.4 Ajouter votre header/footer autour du blog
Le blog vit hors de vos éventuels segments de locale (/blog), donc votre chrome de site ne s'y applique pas automatiquement. C'est voulu : app/blog/layout.tsx vous appartient et le composant BlogLayout du package est un simple passe-plat ({children}), précisément pour que chaque site mette son header et son footer.
// app/blog/layout.tsx
import "@monark_it/mch-blog/styles.css"; // requis : styles du blog
import { BlogLayout } from "@monark_it/mch-blog/next";
import { SiteHeader } from "@/components/site-header";
import { SiteFooter } from "@/components/site-footer";
export default function BlogShell({ children }: { children: React.ReactNode }) {
return (
<>
<SiteHeader />
<BlogLayout>{children}</BlogLayout>
<SiteFooter />
</>
);
}Notes :
- Gardez l'import
@monark_it/mch-blog/styles.css: c'est lui qui porte les styles et les variables--mch-*. - Si votre header/footer dépend d'un contexte (traductions, providers), gardez ces providers dans ce layout. Le blog n'étant pas préfixé par la locale, il utilise votre locale par défaut (
MCH_LOCALE). - Le package ne fournit aucun chrome : header, footer, bannière cookies, etc. restent ceux de votre site.
- Évitez les doublons globaux (ex. données structurées déjà rendues par votre layout racine) : un seul endroit suffit.
5. Variables d'environnement
Six variables requises, toutes serveur uniquement, plus deux variables optionnelles pour la résilience réseau.
| Variable | Défaut | Rôle |
| ------------------------ | ------- | ------------------------------------------ |
| MCH_SITE_KEY | — | identifiant du site, titre par défaut |
| MCH_BASE_URL | — | origine publique (canonical, RSS, JSON-LD) |
| MCH_BLOG_PATH | /blog | chemin public du blog |
| MCH_LOCALE | fr | locale des dates |
| MCH_HUB_URL | — | Hub qui publie la release |
| MCH_HUB_SITE_ID | — | identifiant du site dans le Hub |
| MCH_HUB_RETRY | 3 | réessais des appels Hub (optionnel) |
| MCH_HUB_RETRY_DELAY_MS | 250 | délai de base du backoff en ms (optionnel) |
- Jamais de préfixe
NEXT_PUBLIC_: ces valeurs sont lues au build et au rendu serveur, pas dans le navigateur. - Une valeur modifiée exige un rebuild.
- En local, elles vivent dans
.env.local(écrit par le CLI) ; en production, copiez-les dans les variables de votre plateforme d'hébergement. - Pour une préversion : ajoutez
MCH_HUB_ENV=stagingau déploiement de staging. - Sans les variables du Hub, le rendu échoue avec
CONFIG_INVALID: il n'existe aucun repli vers des fichiers locaux. MCH_HUB_RETRYetMCH_HUB_RETRY_DELAY_MSsont optionnelles : les défauts (3 réessais, 250 ms) absorbent déjà les incidents transitoires du Hub. Ne les touchez que pour durcir un build capricieux.
6. Comprendre le rendu
6.1 Entrées du package
| Entrée | Pour | Contenu |
| -------------------------------- | ------------- | --------------------------------------------------------------------------------------------- |
| @monark_it/mch-blog | serveur/build | config, release, SEO, revalidation |
| @monark_it/mch-blog/next | App Router | BlogLayout, BlogListPage, ArticlePage, metadata, sitemap, RSS, mchApiGet/mchApiPost |
| @monark_it/mch-blog/ui | navigateur | composants, Markdown, designStyle, types |
| @monark_it/mch-blog/styles.css | site | styles mch-* |
L'entrée /ui ne contient aucun import node:* ni next/* : elle peut tourner dans un navigateur (aperçu du Hub). La racine reste l'entrée serveur.
6.2 Thème et styles
- Le thème est publié avec la release.
designStyle(design)pose les tokens--mch-*(--mch-accent,--mch-ink,--mch-surface,--mch-content-width…) sur les conteneurs.mch-blog-page/.mch-article. Aucune variable globale n'est définie sur le site hôte. - Dans l'article, l'en-tête et le corps Markdown (texte, images, tableaux) occupent toute la largeur du contenu (
--mch-content-width) ; seuls les tableaux et les blocs de code passent en scroll horizontal quand leur contenu dépasse. - Toutes les classes du package sont préfixées
mch-*et stylées parstyles.css, importé parapp/blog/layout.tsx. Aucun scan Tailwind n'est nécessaire. - Les images (couverture, corps Markdown, articles liés) sont sans bordure par défaut ; un hôte qui en veut une pose
--mch-image-border. - Le mode sombre est piloté par
data-has-dark-palette(palette définie) etdata-apply-dark-palette(palette appliquée au visiteur).
6.3 Lecture d'une release
resolveBlogContent(config) lit le pointeur live puis le snapshot de la génération publiée et renvoie :
articles: articles triés du plus récent au plus ancien, médias en URLs absolues ;design: thème publié, sinon les défauts ;generation: numéro de génération publiée (0si rien n'est publié) ;redirects: redirections des anciens slugs (previous_slugs), servies en 301.
En développement, la release est relue à chaque requête. En production, les lectures passent par le cache de données Next tagué mch-release, avec une revalidation au plus tard toutes les 60 s. Après validation du webhook signé, le tag expire immédiatement et le layout du blog est revalidé. Un webhook manqué ou en échec n'empêche donc pas la mise à jour : la page se rafraîchit d'elle-même au plus tard au bout de 60 s. Le TTL de 60 s concerne aussi le cache en mémoire du pointeur live dans la source de release.
Site connecté sans aucune release publiée : le blog est vide sans erreur (articles: [], design par défaut). Toutes les autres erreurs remontent en BlogError typée : CONFIG_INVALID, RELEASE_FETCH_FAILED, RELEASE_NOT_FOUND, RELEASE_CONFLICT, RELEASE_HASH_MISMATCH, RELEASE_INCOMPATIBLE, RELEASE_STALE_GENERATION, RELEASE_INVALID.
Résilience : les appels au Hub (live et snapshot) sont réessayés jusqu'à 4 tentatives (3 réessais) avec backoff exponentiel + jitter en cas d'erreur réseau, de timeout ou de statut transitoire (408, 425, 429, 500, 502, 503, 504). L'en-tête Retry-After est honoré et plafonné à 10 s. Les 404 et 409 ne sont jamais réessayés (erreurs définitives). Un 502 ponctuel du Hub pendant next build n'échoue donc plus la collecte des pages ; réglable via MCH_HUB_RETRY / MCH_HUB_RETRY_DELAY_MS ou les options retries / retryDelayMs de resolveBlogContent.
6.4 Revalidation
Après chaque publication, le Hub envoie un webhook signé (JWS) à /api/mch/revalidate. Le site vérifie la signature, relit la génération, puis invalide le tag mch-release et la route du blog : liste, articles, RSS et sitemap repartent de la release à jour.
Deux réponses déclarent l'état du site :
- l'accusé de revalidation (
202) ; /.well-known/mch-release, qui expose la génération servie et la listeoverrides.
Le Hub s'en sert pour vérifier le binding automatiquement après déploiement et pour afficher les composants personnalisés dans Design. La génération de /.well-known/mch-release confirme le pointeur de release ; elle ne prouve pas à elle seule le HTML rendu pour chaque article.
6.5 Médias
Les images de la release (couverture et images du corps) sont servies en URLs absolues depuis le Hub :
<MCH_HUB_URL>/api/v1/public/sites/<MCH_HUB_SITE_ID>/media/<hash>.webpAucune route locale d'images n'est nécessaire. Quand featured_image est vide, carte, en-tête et articles liés affichent une illustration SVG inline (DefaultCover) qui suit la palette claire ou sombre : jamais d'image cassée.
7. Dépannage
| Symptôme | Cause probable | Action |
| --------------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| Site non configuré / Site non connecté | variables MCH_* manquantes | définir les variables puis rebuild |
| /blog sans article | aucune release publiée | publier depuis le Hub |
| Build échoue sur Release indisponible (502) | Hub momentanément indisponible | déjà réessayé (3 réessais) ; vérifier la santé du Hub, ou augmenter MCH_HUB_RETRY |
| Article publié pas à jour | webhook non reçu/validé ou rendu de page non régénéré | vérifier l'accusé 202 et le binding, puis contrôler le HTML effectivement rendu de l'article |
| Image manquante | média non publié ou variables Hub absentes | republier le média ; définir les variables |
| Page sans style | styles.css non importé | import "@monark_it/mch-blog/styles.css"; dans app/blog/layout.tsx |
| Contraste AA insuffisant dans Design | paires texte/fond sous le seuil | bouton « Corriger tout » ou ajuster les couleurs |
| Mode sombre sans effet | darkMode vaut off, ou aucune palette sombre | activer darkMode: system avec une palette sombre définie |
| Design ne liste pas un composant éjecté | déclaration au prochain échange | attendre le prochain échange Hub ↔ site ; route ancienne : relancer init/connect |
| Retirer un composant casse le build | dossier supprimé à la main, import orphelin | customize --remove <id> (débranche le registry proprement) |
8. Migration (anciens sites)
Pour un site d'une génération précédente (config fichier, scope @barry_monarkit, dossier images/blog), relancez :
npx @monark_it/mch-create-blog
npx @monark_it/mch-create-blog connect --url https://exemple.comLes wrappers historiques sont migrés vers le registry _overrides, les artefacts des générations précédentes sont nettoyés, et vos composants modifiés sont conservés.
