📧 Reste informé(e) !

Reçois les derniers articles et conseils EasyAngularKit directement dans ta boîte mail.

S'inscrire gratuitement

~3 min de lecture

Ajouter un flux RSS à ton blog Angular en mode SSG

Les agrégateurs, les extensions de navigateur, les clients RSS — tout ça dépend d'un seul fichier : rss.xml. Si tu blogues et que tu n'en as pas, tu perds une partie de ton audience sans même le savoir.

Voici comment mettre en place un flux RSS 2.0 statique sur un blog Angular SSG — généré à partir des articles markdown, intégré au pipeline de build, disponible immédiatement en dev.

Prérequis

Ce guide suppose que tu as :

  • Un blog Angular en mode SSG avec des articles en markdown
  • Les articles dans un dossier accessible au build (ex: public/blog/)
  • gray-matter pour parser le frontmatter (npm install -D gray-matter tsx)

Le frontmatter minimal attendu dans chaque article :

---
title: Mon article
date: 2025-12-06
description: Une courte description.
---

Build ou SSR ?

Première question à se poser : est-ce que le RSS doit être généré côté serveur (SSR) ou au build ?

Pour un blog statique, le build est la bonne réponse :

  • Pas de requête serveur pour chaque visiteur
  • Le fichier est servi directement par le CDN
  • Accessible immédiatement en mode dev, sans build au préalable

Où écrire le fichier ?

Ne génère pas rss.xml directement dans dist/. Écris-le dans public/ — le dossier d'assets statiques copié automatiquement vers la sortie de build par Angular CLI ou Nx.

Avantages :

  • Zéro couplage au chemin de sortie spécifique de ton projet
  • rss.xml servi en dev sans build
  • Commité dans le dépôt → visible dans les PRs, versionné avec le contenu

Le script de génération

// tools/generate-rss.ts
import {readdirSync, readFileSync, writeFileSync} from 'fs';
import {join} from 'path';
import matter from 'gray-matter';

const BLOG_DIR = join(process.cwd(), 'public/blog'); // adapte ce chemin
const OUTPUT = join(process.cwd(), 'public/rss.xml');
const SITE_URL = 'https://ton-site.com';             // adapte

type Article = {
    title: string;
    date: Date;
    description: string;
    slug: string;
};

// gray-matter parse les dates YAML natives en Date,
// mais les strings restent des strings — les deux cas peuvent coexister
function toDate(value: unknown): Date | null {
    if (value instanceof Date) return isNaN(value.getTime()) ? null : value;
    if (typeof value === 'string') {
        const d = new Date(value);
        return isNaN(d.getTime()) ? null : d;
    }
    return null;
}

function escapeXml(str: string): string {
    return str
        .replace(/&/g, '&')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&apos;');
}

const files = readdirSync(BLOG_DIR).filter((f) => f.endsWith('.md'));
const articles: Article[] = [];

for (const file of files) {
    const content = readFileSync(join(BLOG_DIR, file), 'utf-8');
    const {data} = matter(content);

    const title = data['title'];
    const date = toDate(data['date']);

    if (!title || !date) {
        console.warn(`⚠️  Skipping ${file}: missing title or invalid date`);
        continue;
    }

    articles.push({
        title,
        date,
        description: data['description'] ?? '',
        slug: file.replace(/\.md$/, ''),
    });
}

articles.sort((a, b) => b.date.getTime() - a.date.getTime());

const lastBuildDate = articles[0]?.date.toUTCString() ?? new Date().toUTCString();

const items = articles
    .map((a) => {
        const url = `${SITE_URL}/blog/${a.slug}`;
        return `  <item>
    <title>${escapeXml(a.title)}</title>
    <link>${url}</link>
    <description>${escapeXml(a.description)}</description>
    <pubDate>${a.date.toUTCString()}</pubDate>
    <guid>${url}</guid>
  </item>`;
    })
    .join('\n');

const xml = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Ton blog</title>
    <link>${SITE_URL}</link>
    <description>Description de ton blog</description>
    <language>fr</language>
    <lastBuildDate>${lastBuildDate}</lastBuildDate>
${items}
  </channel>
</rss>`;

writeFileSync(OUTPUT, xml, 'utf-8');
console.log(`✅ ${articles.length} articles exportés vers public/rss.xml`);

Le slug est dérivé du nom de fichier (mon-article.md/blog/mon-article). Si tes articles ont un champ slug dans le frontmatter, remplace la ligne correspondante par slug: data['slug'].

Deux pièges à éviter

1. Les dates YAML natives vs strings

gray-matter parse les dates YAML sans guillemets comme des objets Date JavaScript :

# Parsé en Date object

date: 2025-12-06

# Parsé en string

date: "2025-12-06"

Si tes articles mélangent les deux conventions (courant sur un blog avec du contenu ancien), tu dois gérer les deux cas. C'est exactement ce que fait le helper toDate().

2. L'échappement XML

Le moindre & non échappé dans un titre ou une description brise le parsing XML. Les cinq caractères à échapper impérativement :

Caractère Entité XML
& &amp;
< &lt;
> &gt;
" &quot;
' &apos;

L'ordre compte : remplace & en premier, sinon tu vas échapper tes propres entités.

Intégration dans le pipeline de build

Ajoute un script generate:rss et branche-le au prebuild pour qu'il s'exécute automatiquement avant chaque build :

// package.json
{
  "scripts": {
    "generate:rss": "tsx tools/generate-rss.ts",
    "prebuild": "pnpm run generate:rss"
  }
}

Si tu as déjà un prebuild existant, enchaîne simplement :

"prebuild": "pnpm run generate:blog && pnpm run generate:rss"

Pour aller plus loin

Ce qui n'est pas couvert ici, mais peut valoir le coup selon ton contexte :

  • Content-Type: application/rss+xml — pour servir le feed avec le bon MIME type (configuration serveur/CDN)
  • Feuille XSL — pour un rendu lisible directement dans le navigateur
  • <link rel="alternate"> dans le <head> — pour l'auto-découverte par les extensions RSS
  • Feed Atom — une alternative plus moderne à RSS 2.0

En résumé

Décision Choix Raison
Moment de génération Build (pas SSR) Blog statique, performance CDN
Emplacement de sortie public/rss.xml Copie auto, fonctionne en dev
Slug Nom de fichier Zéro modification des articles existants
Dates toDate() helper gray-matter renvoie Date ou string selon les guillemets YAML
Intégration prebuild Génération automatique, rien à lancer manuellement

Un flux RSS, c'est ~100 lignes de TypeScript et un ajout dans package.json. Aucune raison de s'en priver.

📚 Envie de creuser Angular ?

👉🏼 ➡️ Découvre EasyAngularKit

📧 Reste informé(e) !

Reçois les derniers articles et conseils EasyAngularKit directement dans ta boîte mail.

S'inscrire gratuitement

AngularKit

Suite d'outils pour développeurs Angular francophones. Apprends, modernise tes réflexes, audite ta codebase.

Produits

Contact

Légal

© 2026 AngularKit. Tous droits réservés.