CI/CD pipeline
Automatiseer content updates en publicatie met GitHub Actions en de OptimoCMS SDK. Van staging naar productie in één push.
CI/CD pipeline met GitHub Actions
Automatiseer je OptimoCMS workflow met GitHub Actions. Push content-wijzigingen naar een repository en laat de pipeline automatisch je site bijwerken en publiceren.
In deze tutorial:
- GitHub Actions workflow instellen
- Content updaten via de SDK in CI
- Staging (sandbox) → productie flow
- Automatisch publiceren bij merge naar
main
Vereisten
- GitHub repository met je content/configuratie
- OptimoCMS API key (opgeslagen als GitHub Secret)
- Optioneel: een sandbox-site voor staging
Stap 1 — GitHub Secrets instellen
Ga naar je repository → Settings → Secrets and variables → Actions en voeg toe:
| Secret | Waarde |
|---|---|
OPTIMOCMS_API_KEY | Je live API key |
OPTIMOCMS_SITE_ID | Je productie site ID |
OPTIMOCMS_SANDBOX_KEY | (optioneel) Sandbox API key voor staging |
OPTIMOCMS_SANDBOX_SITE_ID | (optioneel) Sandbox site ID |
Stap 2 — Content als code
Structureer je content in de repository:
content/
├── pages/
│ ├── home.json
│ ├── about.json
│ └── menu.json
├── design-tokens.json
└── navigation.jsonVoorbeeld content/pages/home.json:
{
"title": "Home",
"slug": "home",
"blocks": [
{
"type": "Hero",
"props": {
"title": "Welkom bij ons restaurant",
"subtitle": "De beste Italiaanse keuken sinds 1985",
"ctaText": "Reserveer nu",
"ctaLink": "/reserveren"
}
},
{
"type": "FeaturedMenu",
"props": {
"title": "Chef's aanbeveling",
"items": ["bruschetta", "risotto", "tiramisu"]
}
}
]
}Stap 3 — Deploy script
Maak een scripts/deploy.ts die content naar OptimoCMS pushed:
import { OptimoCMS } from '@optimocms/sdk';
import { readFileSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
const client = new OptimoCMS({
apiKey: process.env.OPTIMOCMS_API_KEY!,
});
const siteId = process.env.OPTIMOCMS_SITE_ID!;
interface PageContent {
title: string;
slug: string;
blocks: Array<{ type: string; props: Record<string, unknown> }>;
seo?: { title?: string; description?: string };
}
async function deployPages(): Promise<void> {
const pagesDir = join(process.cwd(), 'content', 'pages');
const files = readdirSync(pagesDir).filter(f => f.endsWith('.json'));
console.log(`Deploying ${files.length} pagina's...`);
for (const file of files) {
const content: PageContent = JSON.parse(
readFileSync(join(pagesDir, file), 'utf-8')
);
// Check of pagina al bestaat
const existing = await client.pages.list(siteId, { status: 'published' });
const existingPage = existing.data.find(p => p.slug === content.slug);
if (existingPage) {
await client.pages.update(siteId, existingPage.id, {
title: content.title,
blocks: content.blocks,
seo: content.seo,
});
console.log(` ✓ Updated: ${content.title} (${content.slug})`);
} else {
await client.pages.create(siteId, {
title: content.title,
slug: content.slug,
status: 'published',
blocks: content.blocks,
seo: content.seo,
});
console.log(` ✓ Created: ${content.title} (${content.slug})`);
}
}
}
async function deployDesignTokens(): Promise<void> {
const tokensFile = join(process.cwd(), 'content', 'design-tokens.json');
try {
const tokens = JSON.parse(readFileSync(tokensFile, 'utf-8'));
await client.designTokens.update(siteId, tokens);
console.log(' ✓ Design tokens bijgewerkt');
} catch {
console.log(' ⏭ Geen design-tokens.json gevonden, overgeslagen');
}
}
async function deployNavigation(): Promise<void> {
const navFile = join(process.cwd(), 'content', 'navigation.json');
try {
const nav = JSON.parse(readFileSync(navFile, 'utf-8'));
await client.navigation.update(siteId, nav);
console.log(' ✓ Navigatie bijgewerkt');
} catch {
console.log(' ⏭ Geen navigation.json gevonden, overgeslagen');
}
}
async function main(): Promise<void> {
console.log('🚀 OptimoCMS deploy gestart\n');
await deployPages();
await deployDesignTokens();
await deployNavigation();
// Publiceer de site
const result = await client.sites.publish(siteId);
console.log(`\n✓ Site gepubliceerd (${result.status})`);
}
main().catch(error => {
console.error('Deploy mislukt:', error.message);
process.exit(1);
});Stap 4 — GitHub Actions workflow
Maak .github/workflows/deploy-content.yml:
name: Deploy Content to OptimoCMS
on:
push:
branches: [main]
paths:
- 'content/**'
workflow_dispatch:
jobs:
deploy-staging:
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' || github.ref != 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Deploy to Sandbox
env:
OPTIMOCMS_API_KEY: ${{ secrets.OPTIMOCMS_SANDBOX_KEY }}
OPTIMOCMS_SITE_ID: ${{ secrets.OPTIMOCMS_SANDBOX_SITE_ID }}
run: npx tsx scripts/deploy.ts
deploy-production:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Deploy to Production
env:
OPTIMOCMS_API_KEY: ${{ secrets.OPTIMOCMS_API_KEY }}
OPTIMOCMS_SITE_ID: ${{ secrets.OPTIMOCMS_SITE_ID }}
run: npx tsx scripts/deploy.ts
- name: Notify success
if: success()
run: echo "✓ Content deployed to production"
- name: Notify failure
if: failure()
run: echo "✗ Deploy failed — check logs"Stap 5 — Staging → Productie flow
Gebruik een PR-gebaseerde workflow met sandbox preview:
name: Preview on PR
on:
pull_request:
paths:
- 'content/**'
jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Deploy to Sandbox
env:
OPTIMOCMS_API_KEY: ${{ secrets.OPTIMOCMS_SANDBOX_KEY }}
OPTIMOCMS_SITE_ID: ${{ secrets.OPTIMOCMS_SANDBOX_SITE_ID }}
run: npx tsx scripts/deploy.ts
- name: Comment PR with preview URL
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `## 🔍 Content Preview\n\nDe wijzigingen zijn gedeployd naar de sandbox:\n\n**Preview:** https://${process.env.SANDBOX_SUBDOMAIN}.sandbox.optimocms.com\n\nMerge deze PR om naar productie te deployen.`
});Workflow overzicht
Feature branch PR Main branch
│ │ │
│ push content/ │ │
├─────────────────►│ deploy to sandbox │
│ ├───────────────────► │
│ │ preview link in PR │
│ │ │
│ │ merge PR │
│ ├───────────────────────►│
│ │ │ deploy to production
│ │ ├───────────────────►Optioneel: Content validatie
Voeg een validatie-stap toe die controleert of JSON-bestanden geldig zijn:
// scripts/validate.ts
import { readFileSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
const pagesDir = join(process.cwd(), 'content', 'pages');
const files = readdirSync(pagesDir).filter(f => f.endsWith('.json'));
let errors = 0;
for (const file of files) {
try {
const content = JSON.parse(readFileSync(join(pagesDir, file), 'utf-8'));
if (!content.title) throw new Error('Missing "title"');
if (!content.slug) throw new Error('Missing "slug"');
if (!Array.isArray(content.blocks)) throw new Error('"blocks" must be an array');
console.log(`✓ ${file}`);
} catch (err) {
console.error(`✗ ${file}: ${err instanceof Error ? err.message : err}`);
errors++;
}
}
if (errors > 0) {
console.error(`\n${errors} bestand(en) met fouten`);
process.exit(1);
}
console.log(`\n✓ Alle ${files.length} bestanden valide`);Voeg toe aan je workflow vóór de deploy-stap:
- name: Validate content
run: npx tsx scripts/validate.tsVolgende stappen
- Multi-site automatisering — Dezelfde pipeline voor meerdere sites
- Webhook sync — Ontvang notificaties bij publicatie
- SDK Error Handling — Robuuste foutafhandeling in CI
Webhook sync met CRM
Houd je CRM automatisch gesynchroniseerd met OptimoCMS via webhooks. Leer hoe je webhooks instelt, payloads verwerkt en signatures verifieert.
Importeren vanuit Lovable
Migreer je Lovable project naar OptimoCMS. Importeer content, afbeeldingen en design tokens met behoud van je design.