@monark_it/mch-blog
v0.10.2
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, sans écrire |
| --slot <id> | éjecte un emplacement (répétable ou séparé par des virgules ; numéro accepté) |
| --force | réécrit un composant déjà éjecté (sans confirmation ; en interactif, confirmation demandée) |
| --dry-run | affiche les écritures sans écrire |
Sans --force, un composant déjà éjecté est conservé : vos modifications ne sont jamais écrasées. La commande ne lance aucune commande git ; 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.
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, toutes serveur uniquement.
| 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 |
- 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.
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 (publication visible immédiatement). En production, les lectures passent par le cache Next tagué mch-release (TTL 60 s).
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.
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.
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 |
| Article publié pas à jour | webhook non reçu ou cache 60 s | attendre la revalidation ; vérifier le binding dans le Hub |
| 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 |
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.
