GuideAutomatisationGratuit

Blog automatisé avec l'IA — 3/3 : Admin, Google Indexing & déploiement

Finalise le projet : interface admin pour piloter à la main, Google Indexing API pour une indexation en moins de 24h, et déploiement sur Vercel.

Publié le 28 avril 2026· 5 min de lecture
Télécharger le pack

Partie 3/3 — Admin, Google Indexing & déploiement

Pré-requis : avoir terminé les Partie 1 et Partie 2.

Ce que tu vas obtenir à la fin de cette partie

  • Une interface admin pour générer/publier/supprimer à la main
  • Google Indexing API connectée → tes articles indexés en <24h
  • Le projet déployé sur Vercel avec les crons actifs
  • Une checklist complète de mise en production

Temps estimé : 45 minutes
Pré-requis : un compte Google Cloud (gratuit) et un compte Vercel (gratuit ou Pro pour les crons)


Étape 1 — Créer l'interface admin

Une page protégée pour piloter manuellement quand tu en as besoin (ex. publier en urgence un article, en supprimer un mauvais).

Crée src/app/admin/blog/actions.ts :

"use server";
import { revalidatePath } from "next/cache";
import { cookies } from "next/headers";
import { createClient } from "@supabase/supabase-js";
import { generateSEOArticle } from "@/lib/seo/article-generator";
import { requestIndexing, pingGoogleSitemap } from "@/lib/seo/google-indexing";

async function ensureAdmin() {
  const cookieStore = await cookies();
  const token = cookieStore.get("admin-token")?.value;
  if (token !== process.env.ADMIN_SECRET) throw new Error("Unauthorized");
}

function adminClient() {
  return createClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.SUPABASE_SERVICE_ROLE_KEY!
  );
}

// Publier un article scheduled immédiatement
export async function publishNow(id: number) {
  await ensureAdmin();
  const supabase = adminClient();
  const now = new Date().toISOString();

  const { data: article } = await supabase
    .from("seo_articles").select("slug").eq("id", id).single();

  await supabase.from("seo_articles")
    .update({ status: "published", published_at: now })
    .eq("id", id);

  await requestIndexing(article!.slug);
  await pingGoogleSitemap();
  revalidatePath("/blog");
  revalidatePath(`/blog/${article!.slug}`);
}

// Supprimer un article (mauvaise génération, etc.)
export async function deleteArticle(id: number) {
  await ensureAdmin();
  await adminClient().from("seo_articles").delete().eq("id", id);
  revalidatePath("/blog");
}

// Insérer les mots-clés du fichier seed-keywords.ts
export async function seedKeywords() {
  await ensureAdmin();
  const { KEYWORDS } = await import("@/lib/seo/seed-keywords");
  const supabase = adminClient();
  let inserted = 0;
  for (const k of KEYWORDS) {
    const { error } = await supabase
      .from("seo_keywords")
      .insert(k);
    if (!error) inserted++;
  }
  return { inserted, total: KEYWORDS.length };
}

Les 3 actions exposées :

Action Quand l'utiliser
publishNow(id) Publier un scheduled sans attendre le cron de 7h
deleteArticle(id) Article généré nul → poubelle
seedKeywords() Au premier setup, pour remplir la file d'attente

💡 La page admin elle-même (UI) est libre à toi : tableau des articles + boutons. Le minimum vital ce sont les actions ci-dessus.


Étape 2 — Configurer Google Indexing API

Sans Google Indexing API, ton article peut mettre plusieurs semaines à être découvert. Avec l'API : indexé en moins de 24h.

2.1 — Créer un Service Account Google Cloud

  1. Va sur console.cloud.google.comNew Project (ex. blog-indexing)
  2. APIs & Services → Library → cherche Web Search Indexing APIEnable
  3. APIs & Services → CredentialsCreate Credentials → Service Account
  4. Donne-lui un nom (ex. blog-indexer) → Create and Continue → skip les rôles → Done
  5. Clique sur le service account créé → onglet KeysAdd Key → JSON → télécharge le fichier

Le JSON ressemble à ça :

{
  "type": "service_account",
  "client_email": "blog-indexer@xxx.iam.gserviceaccount.com",
  "private_key": "-----BEGIN PRIVATE KEY-----\nMII...\n-----END PRIVATE KEY-----"
}

2.2 — Autoriser le Service Account dans Search Console

  1. Va sur search.google.com/search-console
  2. Vérifie ton domaine (méthode DNS recommandée)
  3. Settings → Users and permissions → Add user
  4. Email = le client_email du JSON
  5. Permission = Owner (obligatoire pour l'Indexing API)

2.3 — Ajouter les credentials dans .env.local

# Google Indexing API
GOOGLE_INDEXING_CLIENT_EMAIL=blog-indexer@xxx.iam.gserviceaccount.com
GOOGLE_INDEXING_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMII...\n-----END PRIVATE KEY-----"
NEXT_PUBLIC_SITE_URL=https://ton-domaine.com

# Admin (pour la page admin)
ADMIN_SECRET=remplace-par-un-autre-secret-aleatoire

⚠️ La private_key doit garder ses \n littéraux. C'est replace(/\\n/g, "\n") dans le code qui les retransforme en vrais sauts de ligne.


Étape 3 — Le module Google Indexing

Crée src/lib/seo/google-indexing.ts. Pas de dépendance externe : on signe le JWT à la main avec le module crypto natif de Node.

async function getAccessToken(): Promise<string> {
  const now = Math.floor(Date.now() / 1000);

  // 1. Construire le JWT (header + payload)
  const header = Buffer
    .from(JSON.stringify({ alg: "RS256", typ: "JWT" }))
    .toString("base64url");

  const payload = Buffer
    .from(JSON.stringify({
      iss:   process.env.GOOGLE_INDEXING_CLIENT_EMAIL,
      scope: "https://www.googleapis.com/auth/indexing",
      aud:   "https://oauth2.googleapis.com/token",
      exp:   now + 3600,
      iat:   now,
    }))
    .toString("base64url");

  // 2. Signer avec la clé privée
  const privateKey = process.env.GOOGLE_INDEXING_PRIVATE_KEY!
    .replace(/\\n/g, "\n");
  const { createSign } = await import("crypto");
  const sign = createSign("RSA-SHA256");
  sign.update(`${header}.${payload}`);
  const signature = sign.sign(privateKey, "base64url");
  const jwt = `${header}.${payload}.${signature}`;

  // 3. Échanger le JWT contre un access_token
  const res = await fetch("https://oauth2.googleapis.com/token", {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
      assertion:  jwt,
    }),
  });
  const { access_token } = await res.json();
  return access_token;
}

// Notifier Google qu'un article a été publié/mis à jour
export async function requestIndexing(slug: string) {
  const token = await getAccessToken();
  await fetch("https://indexing.googleapis.com/v3/urlNotifications:publish", {
    method: "POST",
    headers: {
      Authorization:  `Bearer ${token}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      url:  `${process.env.NEXT_PUBLIC_SITE_URL}/blog/${slug}`,
      type: "URL_UPDATED",
    }),
  });
}

// Pinger le sitemap après une publication batch
export async function pingGoogleSitemap() {
  await fetch(
    `https://www.google.com/ping?sitemap=${process.env.NEXT_PUBLIC_SITE_URL}/sitemap.xml`
  );
}

Maintenant, branche-le dans le cron de publication (Partie 2, Étape 5) :

import { requestIndexing, pingGoogleSitemap } from "@/lib/seo/google-indexing";

// ... dans la boucle for (const article of articles)
await requestIndexing(article.slug);

// Après la boucle
if (articles?.length) await pingGoogleSitemap();

Étape 4 — Sitemap dynamique

Google a besoin d'un sitemap pour découvrir les nouveaux articles. Next.js le génère pour toi avec un fichier app/sitemap.ts :

import { MetadataRoute } from "next";
import { createClient } from "@supabase/supabase-js";

export const revalidate = 3600;

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const supabase = createClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  );

  const { data: articles } = await supabase
    .from("seo_articles")
    .select("slug, updated_at")
    .eq("status", "published");

  const base = process.env.NEXT_PUBLIC_SITE_URL!;

  return [
    { url: base,           changeFrequency: "weekly", priority: 1   },
    { url: `${base}/blog`, changeFrequency: "daily",  priority: 0.9 },
    ...(articles || []).map(a => ({
      url: `${base}/blog/${a.slug}`,
      lastModified: new Date(a.updated_at),
      changeFrequency: "weekly" as const,
      priority: 0.7,
    })),
  ];
}

Tu trouves ton sitemap à https://ton-domaine.com/sitemap.xml.


Étape 5 — Déployer sur Vercel

  1. Push ton code sur GitHub
  2. Sur vercel.com/new → import du repo → Deploy
  3. Une fois déployé, va dans Settings → Environment Variables et ajoute toutes les variables de ton .env.local :
Variable Source
NEXT_PUBLIC_SUPABASE_URL Partie 1
NEXT_PUBLIC_SUPABASE_ANON_KEY Partie 1
SUPABASE_SERVICE_ROLE_KEY Partie 1
ANTHROPIC_API_KEY Partie 2
CRON_SECRET Partie 2
GOOGLE_INDEXING_CLIENT_EMAIL Partie 3
GOOGLE_INDEXING_PRIVATE_KEY Partie 3
NEXT_PUBLIC_SITE_URL Partie 3
ADMIN_SECRET Partie 3
  1. Redéploie (Deployments → ⋯ → Redeploy) pour que les variables soient prises en compte
  2. Vérifie que les crons sont bien actifs : Settings → Crons → tu dois voir tes 2 entrées de vercel.json

Étape 6 — Premier test en production

  1. Va dans Search Console → Sitemaps → soumets https://ton-domaine.com/sitemap.xml
  2. Dans ton admin (ou directement via Supabase) → bouton "Seeder mots-clés" → tu dois voir tes mots-clés dans seo_keywords
  3. Déclenche le cron de génération à la main avec curl :
curl -H "Authorization: Bearer ton-cron-secret" \
  https://ton-domaine.com/api/cron/seo-blog/generate
  1. Vérifie dans Supabase qu'un article apparaît avec status = scheduled
  2. Le lendemain, le cron de publication le passera en published et le notifiera à Google

✅ Checklist finale de mise en production

Base de données (Partie 1)

  • Projet Supabase créé
  • Tables seo_articles + seo_keywords en place
  • RLS activée

Génération (Partie 2)

  • Clé API Anthropic configurée
  • article-generator.ts testé en local
  • seed-keywords.ts rempli avec ≥ 20 mots-clés
  • 2 crons écrits et vercel.json configuré

Indexation & déploiement (Partie 3)

  • Service Account Google Cloud créé
  • Service Account ajouté en Owner sur Search Console
  • google-indexing.ts branché dans le cron de publication
  • Sitemap soumis à Google Search Console
  • Variables d'env sur Vercel (les 9)
  • Crons Vercel actifs
  • Premier article généré en production

📊 Résultat attendu

Métrique Valeur
Articles publiés 4 par semaine
Délai indexation Google < 24h
Temps de maintenance 0 (sauf relecture optionnelle)
Coût mensuel ~1 € (Claude) + 0-20 € (Vercel)

Après 3 mois : 50+ articles indexés, premiers trafics organiques sur la longue traîne.


📦 Ce que contient le pack à télécharger

  • README.md — ce guide
  • admin-actions.ts — server actions admin (Étape 1)
  • .env.example — variables Google + admin (Étape 2)
  • google-indexing.ts — module d'indexation (Étape 3)
  • sitemap.ts — sitemap dynamique (Étape 4)
  • trigger-prod.sh — script de test en production (Étape 6)

🎉 Bravo, ton blog tourne tout seul.

← Partie précédenteBlog automatisé avec l'IA — 2/3 : Génération automatique avec Claude
← Retour aux ressources