Blog automatisé avec l'IA — 2/3 : Génération automatique avec Claude
Le cœur du système : configure Claude pour générer un article SEO complet à partir d'un mot-clé, et planifie 2 crons Vercel pour orchestrer génération + publication.
Partie 2/3 — Génération automatique avec Claude
Pré-requis : avoir terminé la Partie 1 (tables Supabase +
.env.local).
Ce que tu vas obtenir à la fin de cette partie
- Une fonction qui transforme un mot-clé en article SEO complet (titre, HTML, meta, JSON-LD)
- Un cron Vercel qui génère 4 articles/semaine
- Un 2e cron qui les publie automatiquement après 24h
- Une liste de mots-clés stratégique en 3 phases
Temps estimé : 30 minutes
Pré-requis : une clé API Anthropic (console.anthropic.com)
Étape 1 — Récupérer ta clé Claude
- Va sur console.anthropic.com → Settings → API Keys
- Create Key → copie la clé
sk-ant-... - Ajoute-la dans
.env.local:
ANTHROPIC_API_KEY=sk-ant-api03-xxxxxxxx
- Installe le SDK Anthropic :
npm install @anthropic-ai/sdk
💰 Coût : Claude Opus à ~0,02 € par article. Pour 16 articles/mois ≈ 0,30 €/mois.
Étape 2 — Le générateur d'articles
Crée le fichier src/lib/seo/article-generator.ts. C'est lui qui appelle Claude avec le bon prompt et parse le JSON renvoyé.
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
export async function generateSEOArticle(
keyword: string,
existingArticles: { title: string; slug: string }[]
) {
// Liste des articles déjà publiés → Claude peut faire des liens internes
const internalLinksContext = existingArticles
.map(a => `- ${a.title} : /blog/${a.slug}`)
.join("\n");
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 4096,
messages: [{
role: "user",
content: [
{
type: "text",
text: `Tu es un expert SEO et copywriter en français.
Génère un article de blog SEO optimisé pour le mot-clé : "${keyword}"
Articles déjà publiés (utilise-les pour des liens internes pertinents) :
${internalLinksContext}
Contraintes :
- 800-1100 mots
- Structure H2/H3 claire avec une intro et une conclusion
- Inclure 2-3 liens internes vers les articles existants
- Ton professionnel mais accessible
Réponds UNIQUEMENT en JSON valide, sans texte avant/après, avec EXACTEMENT cette structure :`,
cache_control: { type: "ephemeral" }
},
{
type: "text",
text: `{
"title": "Titre SEO 50-65 chars contenant le mot-clé",
"slug": "slug-url-friendly-en-tirets",
"meta_description": "150-160 chars, contient le mot-clé, donne envie de cliquer",
"excerpt": "2-3 phrases qui résument l'article",
"content_html": "<article>...HTML sémantique avec h2, h3, p, ul, blockquote...</article>",
"target_keywords": ["mot-clé principal", "variante 1", "variante 2"],
"internal_links": [{ "text": "ancre", "href": "/blog/slug-existant" }],
"schema_json": {
"@context": "https://schema.org",
"@type": "Article",
"headline": "Titre",
"description": "Meta description",
"inLanguage": "fr-FR",
"wordCount": 950
}
}`
}
]
}]
});
const text = response.content[0].type === "text" ? response.content[0].text : "";
const match = text.match(/\{[\s\S]*\}/);
if (!match) throw new Error("Claude n'a pas renvoyé de JSON valide");
return JSON.parse(match[0]);
}
Pourquoi cache_control: ephemeral ? Anthropic met en cache la première partie du prompt. Si tu génères plusieurs articles dans la même session, les générations 2+ coûtent 90 % moins cher.
Étape 3 — La liste de mots-clés stratégique
La stratégie qui marche : commence par la longue traîne (faible concurrence → indexation rapide), puis monte vers les mots-clés piliers quand ton domaine a de l'autorité.
Crée src/lib/seo/seed-keywords.ts :
export const KEYWORDS = [
// ─── Phase 1 : longue traîne (priority 90-99) ──────────────
// Indexation rapide, faible concurrence, premiers trafics
{ keyword: "comment automatiser ses tâches avec l'ia",
cluster: "tutoriels", volume_est: 200, difficulty: 20, priority: 95 },
{ keyword: "configurer claude api en 5 minutes",
cluster: "tutoriels", volume_est: 150, difficulty: 18, priority: 93 },
// ─── Phase 2 : intention d'achat (priority 80-89) ─────────
// Conversion : la personne cherche déjà une solution
{ keyword: "meilleurs outils ia productivité 2026",
cluster: "comparaison", volume_est: 500, difficulty: 35, priority: 85 },
{ keyword: "alternative chatgpt pour entreprise",
cluster: "comparaison", volume_est: 400, difficulty: 32, priority: 82 },
// ─── Phase 3 : piliers (priority 70-79) ───────────────────
// Fort volume, forte concurrence — à attaquer en dernier
{ keyword: "intelligence artificielle entreprise",
cluster: "pilier", volume_est: 8000, difficulty: 70, priority: 72 },
];
Comment trouver tes propres mots-clés ?
- Outils gratuits : Google Keyword Planner, Ubersuggest (3 recherches/jour)
- Cible des
difficulty≤ 30 pour la phase 1 - Vise 20-30 mots-clés au total pour avoir 5-6 mois de contenu
Étape 4 — Cron 1 : générer un article
Crée src/app/api/cron/seo-blog/generate/route.ts. Ce cron tourne lun/mer/ven/sam à 6h UTC : il prend le mot-clé prioritaire, génère l'article, le sauvegarde en scheduled avec 24h de délai.
import { createClient } from "@supabase/supabase-js";
import { generateSEOArticle } from "@/lib/seo/article-generator";
export const maxDuration = 300; // Vercel Pro : 5 min
export async function GET(request: Request) {
// 1. Sécurité : seul Vercel Cron peut appeler cette route
const auth = request.headers.get("authorization");
if (auth !== `Bearer ${process.env.CRON_SECRET}`) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
// 2. Pioche le mot-clé pending avec la priority la plus haute
const { data: keyword } = await supabase
.from("seo_keywords")
.select("*")
.eq("status", "pending")
.order("priority", { ascending: false })
.limit(1)
.single();
if (!keyword) {
return Response.json({ message: "Plus aucun mot-clé en attente" });
}
// 3. Récupère les 20 derniers articles publiés (pour les liens internes)
const { data: existing } = await supabase
.from("seo_articles")
.select("title, slug")
.eq("status", "published")
.order("published_at", { ascending: false })
.limit(20);
// 4. Appel à Claude
const article = await generateSEOArticle(keyword.keyword, existing || []);
// 5. Sauvegarde en "scheduled" avec 24h de délai
const scheduledAt = new Date(Date.now() + 24 * 60 * 60 * 1000);
await supabase.from("seo_articles").insert({
...article,
status: "scheduled",
scheduled_at: scheduledAt,
keyword_id: keyword.id,
lang: "fr",
});
// 6. Marque le mot-clé comme utilisé
await supabase.from("seo_keywords")
.update({ status: "used", used_at: new Date() })
.eq("id", keyword.id);
return Response.json({ success: true, title: article.title });
}
💡 Pourquoi 24h de délai ? Pour te laisser le temps de relire avant publication automatique. Si l'article est nul, tu le supprimes (on verra l'interface admin en partie 3).
Étape 5 — Cron 2 : publier les articles prêts
Crée src/app/api/cron/seo-blog/publish/route.ts. Ce cron tourne tous les jours à 7h UTC : il publie tous les articles scheduled dont la date est passée.
import { createClient } from "@supabase/supabase-js";
export const maxDuration = 60;
export async function GET(request: Request) {
const auth = request.headers.get("authorization");
if (auth !== `Bearer ${process.env.CRON_SECRET}`) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
const now = new Date().toISOString();
// Tous les articles dont la date de publication est passée
const { data: articles } = await supabase
.from("seo_articles")
.select("id, slug, schema_json")
.eq("status", "scheduled")
.lte("scheduled_at", now);
for (const article of articles || []) {
// Met à jour les dates dans le JSON-LD
const schema = {
...article.schema_json,
datePublished: now,
dateModified: now
};
await supabase.from("seo_articles").update({
status: "published",
published_at: now,
schema_json: schema,
}).eq("id", article.id);
// En partie 3 : ping Google Indexing API ici
}
return Response.json({ published: articles?.length || 0 });
}
Étape 6 — Planifier les crons sur Vercel
Crée un fichier vercel.json à la racine de ton projet :
{
"crons": [
{
"path": "/api/cron/seo-blog/generate",
"schedule": "0 6 * * 1,3,5,6"
},
{
"path": "/api/cron/seo-blog/publish",
"schedule": "0 7 * * *"
}
]
}
Lecture des cron expressions :
| Schedule | Signification |
|---|---|
0 6 * * 1,3,5,6 |
Tous les lundis, mercredis, vendredis, samedis à 6h00 UTC |
0 7 * * * |
Tous les jours à 7h00 UTC |
⚠️ Vercel : les crons nécessitent un plan Pro ($20/mois). Alternative gratuite : GitHub Actions avec un
scheduleworkflow qui fait uncurlvers tes routes.
Étape 7 — Tester en local
Avant de déployer, teste tes routes en local. Lance Next.js :
npm run dev
Dans un autre terminal, simule l'appel de Vercel Cron :
# Test génération
curl -H "Authorization: Bearer ton-cron-secret" \
http://localhost:3000/api/cron/seo-blog/generate
# Test publication
curl -H "Authorization: Bearer ton-cron-secret" \
http://localhost:3000/api/cron/seo-blog/publish
Tu devrais voir un JSON { "success": true, "title": "..." } et un nouvel article apparaître dans Supabase (Table Editor → seo_articles).
✅ Récap : ce que tu as maintenant
- Générateur d'articles connecté à Claude
- Liste de mots-clés stratégique (3 phases)
- Cron de génération (4×/semaine)
- Cron de publication (1×/jour)
-
vercel.jsonprêt pour le déploiement
📦 Ce que contient le pack à télécharger
README.md— ce guideinstall.sh— commande d'installation du SDKarticle-generator.ts— appel à Claude (Étape 2)seed-keywords.ts— exemple de mots-clés (Étape 3)cron-generate.ts— cron de génération (Étape 4)cron-publish.ts— cron de publication (Étape 5)vercel.json— config des crons (Étape 6)test-local.sh— script de test (Étape 7)
Suite → Partie 3/3 : interface admin, Google Indexing API, déploiement final