Automatisation multi-sites
Créez et gérez des dizaines de sites de manière programmatique avec le SDK OptimoCMS. Idéal pour les franchises, agences et plateformes white-label.
Automatiser la gestion multi-sites
Vous avez une franchise avec 50 emplacements qui ont chacun besoin de leur propre site web ? Ou vous gérez des dizaines de sites clients en tant qu'agence ? Le SDK OptimoCMS vous permet d'automatiser l'ensemble du processus.
Dans ce tutoriel, vous apprendrez à :
- Écrire un script qui crée plusieurs sites dans une boucle
- Respecter les limites de requêtes avec la logique de retry intégrée
- Gérer les erreurs pour des opérations batch robustes
- Personnaliser les design tokens et le contenu par emplacement
Prérequis
- Node.js 18+
npm install @optimocms/sdk- Clé API avec scope
sites:write
Étape 1 — Préparer les données des emplacements
Créez un fichier JSON avec tous les emplacements :
[
{
"name": "Pizza Roma - Amsterdam",
"subdomain": "pizzaroma-amsterdam",
"address": "Keizersgracht 123, Amsterdam",
"phone": "+31 20 123 4567",
"color": "#D4380D"
},
{
"name": "Pizza Roma - Rotterdam",
"subdomain": "pizzaroma-rotterdam",
"address": "Coolsingel 45, Rotterdam",
"phone": "+31 10 987 6543",
"color": "#D4380D"
},
{
"name": "Pizza Roma - Utrecht",
"subdomain": "pizzaroma-utrecht",
"address": "Oudegracht 78, Utrecht",
"phone": "+31 30 456 7890",
"color": "#D4380D"
}
]Étape 2 — Script batch avec respect des limites
Le SDK intègre une logique de retry pour les 429 Too Many Requests. Vous pouvez aussi ajouter un délai entre les requêtes.
import { OptimoCMS, RateLimitError } from '@optimocms/sdk';
import { readFileSync } from 'node:fs';
interface Branch {
name: string;
subdomain: string;
address: string;
phone: string;
color: string;
}
const client = new OptimoCMS({
apiKey: process.env.OPTIMOCMS_API_KEY!,
maxRetries: 3,
});
const branches: Branch[] = JSON.parse(
readFileSync('./branches.json', 'utf-8')
);
async function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function createBranchSite(branch: Branch): Promise<string> {
const site = await client.sites.create({
name: branch.name,
subdomain: branch.subdomain,
language: 'nl',
});
const templates = await client.templates.list({ category: 'restaurant' });
await client.templates.install(site.id, {
templateId: templates.data[0].id,
designTokens: {
colorPrimary: branch.color,
fontHeading: 'Poppins',
fontBody: 'Inter',
radiusCard: '16px',
radiusButton: '8px',
},
});
await client.pages.create(site.id, {
title: 'Contact',
slug: 'contact',
status: 'published',
blocks: [
{
type: 'Hero',
props: { title: `Visitez ${branch.name}`, subtitle: branch.address },
},
{
type: 'ContactInfo',
props: {
address: branch.address,
phone: branch.phone,
email: `info@${branch.subdomain}.nl`,
},
},
{
type: 'BookingWidget',
props: { serviceId: 'dinner' },
},
],
});
await client.sites.publish(site.id);
return site.id;
}
async function main() {
const results: Array<{ branch: string; siteId?: string; error?: string }> = [];
for (const branch of branches) {
try {
const siteId = await createBranchSite(branch);
results.push({ branch: branch.name, siteId });
console.log(`✓ ${branch.name} → ${branch.subdomain}.optimocms.com`);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
results.push({ branch: branch.name, error: message });
console.error(`✗ ${branch.name}: ${message}`);
}
await sleep(500);
}
const success = results.filter(r => r.siteId).length;
const failed = results.filter(r => r.error).length;
console.log(`\nTerminé : ${success} réussis, ${failed} échoués sur ${branches.length} au total`);
}
main().catch(console.error);Étape 3 — Gestion des erreurs et retry
Le SDK gère les limites de requêtes automatiquement (backoff exponentiel, max 3 retries). Pour d'autres erreurs, ajoutez un wrapper de retry :
async function withRetry<T>(
fn: () => Promise<T>,
maxAttempts = 3,
delayMs = 1000
): Promise<T> {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
if (error instanceof RateLimitError) {
const waitMs = error.retryAfter ? error.retryAfter * 1000 : delayMs * attempt;
console.log(`Limite atteinte, attente ${waitMs}ms...`);
await sleep(waitMs);
continue;
}
if (attempt === maxAttempts) throw error;
console.log(`Tentative ${attempt} échouée, retry dans ${delayMs * attempt}ms...`);
await sleep(delayMs * attempt);
}
}
throw new Error('Unreachable');
}
const siteId = await withRetry(() => createBranchSite(branch));Étape 4 — Mise à jour en masse des design tokens
Mettre à jour les sites existants en masse avec un nouveau schéma de couleurs :
async function updateAllBrandColors(newColor: string) {
for await (const site of client.sites.list()) {
if (!site.subdomain.startsWith('pizzaroma-')) continue;
await client.designTokens.update(site.id, {
colorPrimary: newColor,
});
console.log(`✓ ${site.name} mis à jour`);
await sleep(200);
}
}
await updateAllBrandColors('#1D4ED8');Étape 5 — Variante MCP
Avec MCP, vous pouvez obtenir le même résultat en langage naturel dans Cursor ou Claude :
Créez 3 sites pour la franchise Pizza Roma :
- Pizza Roma Amsterdam (sous-domaine : pizzaroma-amsterdam)
- Pizza Roma Rotterdam (sous-domaine : pizzaroma-rotterdam)
- Pizza Roma Utrecht (sous-domaine : pizzaroma-utrecht)
Utilisez le template restaurant pour chaque site avec couleur primaire #D4380D et police Poppins.
Créez une page contact avec l'adresse et le numéro de téléphone par emplacement.
Publiez tous les sites.Limites par forfait
| Forfait | Requêtes/min | Sites/heure |
|---|---|---|
| Free | 60 | 5 |
| Pro | 300 | 50 |
| Business | 1000 | 200 |
| Enterprise | Sur mesure | Illimité |
Le SDK respecte automatiquement le header Retry-After lors d'une réponse 429.
Conseils pour les gros batches
- Séquentiel, pas parallèle — Évitez les race conditions en créant les sites un par un
- Sauvegardez votre progression — Enregistrez les éléments traités pour pouvoir reprendre après un crash
- Validez les entrées — Vérifiez les sous-domaines (minuscules, pas d'espaces, max 63 caractères) avant d'appeler l'API
- Mode dry-run — Ajoutez un flag
--dry-runqui ne fait que logger ce qui se passerait
const DRY_RUN = process.argv.includes('--dry-run');
if (DRY_RUN) {
console.log(`[DRY RUN] Créerait : ${branch.name} → ${branch.subdomain}`);
} else {
await createBranchSite(branch);
}Prochaines étapes
- Pipeline CI/CD — Automatiser ce script via GitHub Actions
- Sync webhook — Recevoir des notifications lors de la publication
- Pagination SDK — Itérer efficacement à travers de grandes listes
Créer un site de restaurant
Créez un site web de restaurant complet avec menu, réservations, avis et design tokens via le SDK OptimoCMS, MCP et l'API REST.
Sync webhook avec CRM
Gardez votre CRM automatiquement synchronisé avec OptimoCMS via les webhooks. Apprenez à configurer les webhooks, traiter les payloads et vérifier les signatures.