Skip to content

Instantly share code, notes, and snippets.

@MarcL
Last active July 14, 2026 08:48
Show Gist options
  • Select an option

  • Save MarcL/c36ca085ca37acab095d7110ec668d98 to your computer and use it in GitHub Desktop.

Select an option

Save MarcL/c36ca085ca37acab095d7110ec668d98 to your computer and use it in GitHub Desktop.
Deckchair Asia CMS Proposal
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Headless CMS Platform — Deckchair Asia</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; max-width: 860px; margin: 0 auto; padding: 2rem; color: #1a1a1a; line-height: 1.7; }
h1 { font-size: 2rem; border-bottom: 3px solid #0070f3; padding-bottom: 0.5rem; margin-bottom: 0.25rem; }
h2 { font-size: 1.4rem; margin-top: 2.5rem; color: #0070f3; border-bottom: 1px solid #e0e0e0; padding-bottom: 0.25rem; }
h3 { font-size: 1.1rem; margin-top: 1.5rem; color: #333; }
code { background: #f4f4f4; padding: 0.15em 0.4em; border-radius: 4px; font-size: 0.9em; }
pre { background: #1e1e1e; color: #d4d4d4; padding: 1.25rem; border-radius: 8px; overflow-x: auto; }
pre code { background: none; padding: 0; color: inherit; font-size: 0.9rem; }
ul, ol { padding-left: 1.5rem; }
li { margin: 0.35rem 0; }
ul.tasks { list-style: none; padding-left: 0; }
li.task { padding: 0.2rem 0; color: #555; }
a { color: #0070f3; text-decoration: none; }
a:hover { text-decoration: underline; }
strong { color: #111; }
hr { border: none; border-top: 1px solid #e0e0e0; margin: 2rem 0; }
p { margin: 0.75rem 0; }
.header-meta { color: #666; font-size: 0.9rem; margin-bottom: 2rem; }
</style>
</head>
<body>
<div class="header-meta">Prepared by Marc Littlemore &middot; deckchairasia.com</div>
<h1>Modernising the Stack for Mark's Web Agency</h1>
<p>A proposal for Mark (Deckchair Asia) to think through whether — and how — to modernise his agency's CMS and development workflow.</p>
<p>This isn't a straight recommendation to switch stacks. It's a structured look at the real problem, two viable paths, and an honest comparison of the trade-offs.</p>
<hr>
<h2>The Real Problem</h2>
<p>Before choosing a solution, it's worth being precise about what's actually painful.</p>
<p>The most common problem for agencies at this scale is <strong>no shared codebase</strong>. Every client site is a bespoke build from scratch. That means:</p>
<ul>
<li>Engineers reinvent the same things repeatedly — auth, content types, admin UIs, deployment</li>
<li>Bugs fixed on one site don't carry over to others</li>
<li>Quality and consistency are hard to maintain across a growing client list</li>
<li>Onboarding a new client takes too long</li>
</ul>
<p>The question is: <strong>is the problem WordPress, or is it how WordPress is being used?</strong></p>
<p>If the team has been building fully bespoke themes with no shared foundation, the same problem will follow them to a new stack. The fix is templating and a shared codebase — and that's achievable in WordPress too.</p>
<hr>
<h2>Path A — WordPress Done Better</h2>
<p>WordPress gets a bad reputation, often fairly, but not always. For a small agency with non-technical clients in Asia, it has real advantages worth taking seriously.</p>
<h3>What WordPress does well</h3>
<ul>
<li><strong>Clients already know it.</strong> Many hotel managers and property owners in Asia have used WordPress before. Zero training overhead</li>
<li><strong>ACF Pro (Advanced Custom Fields)</strong> — define structured content types (rooms, amenities, pricing), clients get a clean custom admin UI. Not far off what Directus offers</li>
<li><strong>Page builders</strong> — Elementor or Divi let designers build and iterate without engineering involvement. Real cost saving</li>
<li><strong>WordPress Multisite</strong> — one WordPress installation, multiple client sites. Shared theme, shared plugins. Updates roll out across all clients at once. Directly addresses the reinventing-the-wheel problem</li>
<li><strong>WPGraphQL + Astro frontend</strong> — WordPress as a headless CMS is a real and popular pattern. Modern Astro frontend, WordPress as the content backend. Best of both worlds if the team wants to move away from PHP templating without abandoning WordPress entirely</li>
<li><strong>Managed hosting with Asian data centres</strong> — Kinsta has Singapore and Tokyo nodes. Handles security patches, updates, performance, backups. Ops burden largely disappears</li>
<li><strong>Talent pool</strong> — finding a WordPress developer in Bangkok is easy. Finding a Directus + Astro specialist is harder</li>
</ul>
<h3>Where WordPress falls short</h3>
<ul>
<li>Plugin conflicts and version drift are real, even on managed hosting</li>
<li>Developer experience is poor — engineers won't enjoy it, and that has a real cost</li>
<li>ACF schema portability between sites is doable but fiddly — not as clean as a JSON snapshot</li>
<li>Multisite has quirks, especially with custom domain mapping per client</li>
<li>Performance ceiling is lower — PHP, needs caching layers, more server resources per site</li>
<li>Less control over data structure than a purpose-built CMS</li>
</ul>
<h3>What Path A looks like in practice</h3>
<ul>
<li>WordPress Multisite on Kinsta Singapore (~$30–50/month to start, scales per client)</li>
<li>ACF Pro for structured content types</li>
<li>Shared parent theme with child themes per client for branding</li>
<li>WPGraphQL + Astro if the team wants a modern frontend (optional, addable later)</li>
<li>Existing clients migrate naturally — no big-bang replatform</li>
</ul>
<p><strong>Upfront cost:</strong> Low. Engineers already know WordPress. A few days to set up Multisite and the shared theme structure.</p>
<hr>
<h2>Path B — New Stack (Directus + Astro)</h2>
<p>A clean break. Purpose-built headless CMS, modern frontend framework, self-hosted on DigitalOcean Singapore.</p>
<h3>What this does well</h3>
<ul>
<li><strong>Developer experience</strong> — Astro + Directus is a much better stack to build on. Engineers will move faster and enjoy the work more</li>
<li><strong>Schema as code</strong> — Directus schema exported as JSON. Template a new client site in minutes, not days</li>
<li><strong>No plugin roulette</strong> — a controlled stack the team owns is more stable and predictable</li>
<li><strong>Cost at scale</strong> — self-hosted on DigitalOcean Singapore, ~$12/month shared infrastructure for multiple clients</li>
<li><strong>Clean content model</strong> — Directus generates a structured admin UI directly from the schema. Clients get only what they need, nothing more</li>
</ul>
<h3>The honest trade-offs</h3>
<ul>
<li><strong>Upfront investment</strong> — building the templates, deployment scripts, and schema snapshots is probably a few weeks of engineering work before the first client goes live</li>
<li><strong>New territory</strong> — Docker, Coolify, Directus, Astro. The engineers are capable, but there will be a learning curve and unexpected gaps</li>
<li><strong>Clients won't know it</strong> — Directus is unfamiliar. That's fine once they're in it, but expect some hand-holding at launch</li>
<li><strong>Smaller talent pool</strong> — harder to hire for if an engineer leaves</li>
</ul>
<h3>The key question</h3>
<p>Is the problem interesting enough to justify the investment? If the engineers are genuinely frustrated with WordPress and Mark wants to build a productised, scalable agency model — yes. If the primary goal is just "build client sites faster" — WordPress Multisite gets there with less risk.</p>
<hr>
<h2>Comparison</h2>
<p><strong>WordPress Multisite + ACF Pro</strong></p>
<ul>
<li>Upfront cost: Low</li>
<li>Time to first client: Days</li>
<li>Client familiarity: High</li>
<li>Developer experience: Poor</li>
<li>Ops burden: Low (managed hosting)</li>
<li>Latency (Asia): Good (Kinsta Singapore)</li>
<li>Flexibility: Medium</li>
<li>Scalability: Medium</li>
</ul>
<p><strong>Directus + Astro</strong></p>
<ul>
<li>Upfront cost: High (weeks of engineering)</li>
<li>Time to first client: Weeks</li>
<li>Client familiarity: Low</li>
<li>Developer experience: Excellent</li>
<li>Ops burden: Medium (self-managed infra)</li>
<li>Latency (Asia): Good (DigitalOcean Singapore)</li>
<li>Flexibility: High</li>
<li>Scalability: High</li>
</ul>
<hr>
<h2>Recommendation</h2>
<p><strong>If the goal is to reduce time-per-client and improve consistency now:</strong> Path A. WordPress Multisite + ACF Pro. Lower risk, faster payoff, clients already know it.</p>
<p><strong>If the goal is to build a better agency platform for the long term, and the engineers are bought in:</strong> Path B. Directus + Astro. More investment upfront, significantly better foundation.</p>
<p><strong>Either way:</strong> don't start by migrating existing clients. Pilot with one new client first.</p>
<hr>
<h2>AI & MCP Integration — Investigation Notes</h2>
<p><em>This is exploratory research, not settled architecture. Mark's requirements doc is pending.</em></p>
<h3>How Directus MCP actually works</h3>
<p>Built into Directus v11.12+. Disabled by default. Enabled via Settings → AI → Model Context Protocol.</p>
<p><strong>The critical detail:</strong> the MCP server runs under the permissions of the authenticated user. Exactly like the REST API. AI tools can only do what that user's role allows. No special bypass.</p>
<p>Authentication via OAuth (recommended) or static access token. OAuth lets clients sign in via browser and approve MCP access — the issued token is scoped to the MCP endpoint only.</p>
<h3>The two roles Directus explicitly documents</h3>
<p><strong>Content Editor</strong> (recommended for clients/AI):</p>
<ul>
<li>Read/Create/Update on content collections</li>
<li>Files and folders CRUD</li>
<li>System collections: read only</li>
<li><strong>No schema access whatsoever</strong></li>
</ul>
<p><strong>Developer</strong> (required for schema changes):</p>
<ul>
<li>Everything above, plus:</li>
<li>Collections CRUD</li>
<li>Fields CRUD</li>
<li>Relations CRUD</li>
<li>Flows CRUD</li>
</ul>
<p>This directly validates the concern about schema safety. If clients connect via a Content Editor role, they literally cannot change the schema — neither via the UI, the API, nor MCP. The capability doesn't exist for that role.</p>
<h3>Additional safeguards in MCP</h3>
<ul>
<li><strong>Allow Deletes</strong> toggle — disabled by default globally. Even delete of content items is off until explicitly enabled</li>
<li><strong>DISABLE_TOOLS</strong> env var — can block specific MCP tools regardless of role (e.g. <code>["delete-item", "update-field"]</code>)</li>
<li><strong>Custom system prompt</strong> — can constrain what the AI attempts to do</li>
<li><strong>AI Prompts Collection</strong> — reusable prompt templates stored in Directus, scoped per use case</li>
<li><strong>Tool approval</strong> — in the AI Assistant chat, all tool calls require user approval by default. Can be configured per tool</li>
</ul>
<h3>The AI Assistant (chat interface)</h3>
<p>Released as GA in v11.15. Built into the Directus Data Studio sidebar. Requires an API key for OpenAI, Anthropic, Google Gemini, or any OpenAI-compatible provider (including Ollama for self-hosted).</p>
<ul>
<li>Context-aware — clients can attach content items to a conversation</li>
<li>Streams responses in real-time with Markdown</li>
<li>Runs under the authenticated user's permissions (same model as MCP)</li>
<li>Tool calls shown as cards, approval required by default</li>
</ul>
<p>This is the client-facing AI interface. A hotel manager could open Directus, open the AI sidebar, and say "update the description of The Garden Suite and translate it into Thai" — and it would work safely within their Content Editor permissions.</p>
<h3>Schema change workflow</h3>
<p>If a client legitimately needs a new field (e.g. <code>breakfast_included</code> toggle):</p>
<ol>
<li>Client requests it via support or chat</li>
<li>Engineer adds field to schema (Developer role)</li>
<li>Engineer updates Astro frontend template</li>
<li>Deploy</li>
<li>Field available for client/AI to populate</li>
</ol>
<p>This is a developer touchpoint per schema change. Correct behaviour — keeps the frontend stable. Schema changes should never be self-service for clients.</p>
<h3>What needs further investigation</h3>
<ul>
<li><strong>Multi-tenant MCP</strong> — each client instance has its own Directus with its own MCP endpoint. Straightforward. But if we explore shared multi-tenant (one Directus, multiple client namespaces), how does MCP auth scope to a specific tenant? Not documented — needs testing</li>
<li><strong>Vercel deployment module</strong> — v11.15 added one-click Vercel deploys from the Data Studio. Worth exploring for static rebuild triggers if we go that route</li>
<li><strong>Ollama for self-hosted AI</strong> — if Mark wants to avoid OpenAI/Anthropic API costs at scale, running a local LLM on the VPS is possible. Trade-off: quality vs cost. Worth a spike once client numbers grow</li>
<li><strong>Tool approval UX</strong> — approval required by default is safe but potentially annoying for high-frequency content updates. Need to test what the client experience actually feels like</li>
</ul>
<h3>Summary</h3>
<p>The permission model is solid and well-documented. The concern about clients arbitrarily changing schema is a real risk but one Directus has already solved — Content Editor role simply cannot touch schema. The MCP surface respects the same access control as everything else.</p>
<p>This is a green light on the approach, subject to the multi-tenant investigation above.</p>
<hr>
<p>After an initial conversation about stack modernisation, the focus shifted to something more interesting: <strong>a productised, subscription-based CMS with AI integration</strong> that Mark can sell to his clients.</p>
<p>This is a product business, not just an agency workflow improvement.</p>
<h3>The shift in framing</h3>
<p>The earlier discussion was about making Mark's team more efficient. This is different — it's about building something clients pay for monthly. The value proposition isn't "better website" it's "ongoing tool that makes running their business easier."</p>
<h3>What AI integration might actually mean for hotel and property clients in Asia</h3>
<p>Practically useful, not just buzzword-worthy:</p>
<ul>
<li><strong>Multilingual content generation</strong> — describe a room, AI writes copy in English, Thai, Japanese, Chinese. Huge for Asian hospitality clients</li>
<li><strong>Image alt text and SEO metadata</strong> — auto-generated from uploaded photos</li>
<li><strong>Translation management</strong> — maintain one source of truth, AI handles language variants</li>
<li><strong>Review summarisation</strong> — pull Google/TripAdvisor reviews, surface sentiment in the dashboard</li>
<li><strong>Enquiry handling</strong> — AI-assisted first response to contact form submissions</li>
</ul>
<p>That last one starts to blur the line between CMS and client operations tool. Worth exploring.</p>
<h3>How this links to the Directus + Astro stack</h3>
<p>Directus has a built-in Flows engine that can call external APIs — including OpenAI. AI-assisted content creation could live directly in the content editor, not bolted on. This is cleaner than WordPress where you're dependent on third-party plugins for AI features.</p>
<p>The subscription model also changes hosting economics. Centralised control and low per-client cost matters more than per-client isolation — which points back to a shared VPS approach (DigitalOcean Singapore) rather than separate instances.</p>
<h3>What the requirements doc should answer</h3>
<p>Mark is writing a business requirements document. Key things to look for:</p>
<ul>
<li>Who is the actual user — Mark's clients (hotel managers), or their end customers?</li>
<li>What does "subscription" mean — per site, per seat, per usage?</li>
<li>Is AI a core feature or a differentiator on top of a CMS?</li>
<li>What do clients currently pay for that this replaces or augments?</li>
<li>What do hotel managers in Thailand actually struggle with day-to-day? That's where the real product is</li>
<li>Is Mark trying to productise for scale, or deepen relationships with existing clients?</li>
</ul>
<h3>What stays, what changes</h3>
<p><strong>Stays relevant:</strong></p>
<ul>
<li>Directus as the CMS backbone — Flows for AI integration</li>
<li>Astro or similar for the frontend</li>
<li>DigitalOcean Singapore for hosting</li>
<li>Template approach for onboarding new clients</li>
</ul>
<p><strong>Likely needs rethinking:</strong></p>
<ul>
<li>One instance per client vs shared multi-tenant — subscription model may favour multi-tenant</li>
<li>Pricing and packaging</li>
<li>Whether the "product" is the CMS admin, the client-facing site, or both</li>
</ul>
<p><strong>Hold until requirements doc arrives before going deeper on architecture.</strong></p>
<hr>
<p>The rest of this document covers the Directus + Astro stack in full. Skip to here if Path B is the direction.</p>
<hr>
<h3>CMS — <a href="https://directus.io">Directus</a></h3>
<ul>
<li>Open source, self-hosted</li>
<li>Define your data schema (rooms, properties, images, amenities) and Directus generates the admin UI automatically</li>
<li>Excellent built-in auth and role-based permissions</li>
<li>Non-technical clients (hotel managers, property owners) can update content without any coding knowledge</li>
<li>Handles media well — built-in image transformations</li>
<li>Exports schema as JSON — perfect for templating</li>
</ul>
<h3>Frontend — <a href="https://astro.build">Astro</a></h3>
<ul>
<li>Outputs static HTML by default — fast, cheap to host</li>
<li>Templates look like enhanced HTML, so WordPress designers can adapt without learning React or Vue</li>
<li>First-class Directus integration</li>
<li>Supports component islands for any interactive bits</li>
<li>Easy to maintain and hand off</li>
</ul>
<hr>
<h2>The Template Approach</h2>
<p>Build two reusable templates in git:</p>
<p><strong>Hotel template</strong> — content types:</p>
<ul>
<li>Rooms (name, description, images, price, amenities)</li>
<li>Hotel info (name, location, contact, about)</li>
<li>Gallery</li>
<li>Facilities</li>
</ul>
<p><strong>Property template</strong> — content types:</p>
<ul>
<li>Listings (address, price, bedrooms, features, images)</li>
<li>Agency info</li>
<li>Enquiry form</li>
</ul>
<p>Each template repo contains:</p>
<ul>
<li><code>docker-compose.yml</code> — Directus + PostgreSQL</li>
<li>Directus schema snapshot (JSON) — imported on first deploy</li>
<li>Deployment script — spins up the instance, imports schema, creates admin user, configures domain</li>
<li>Astro frontend — connected to the Directus API</li>
</ul>
<p><strong>New client = create repo from template + run script.</strong> Done in minutes.</p>
<p>See Template Repos — Structure & Per-Client Customisation below for the full detail.</p>
<hr>
<h2>Multi-Tenancy — One Instance Per Client</h2>
<p>The safest and simplest approach is <strong>one Directus instance per client</strong>. Each client gets their own database and their own Directus deployment. Complete data isolation — no risk of one client seeing another's data.</p>
<p>This is easier to manage than multi-tenant (shared instance with permission scoping) until the client count gets very large.</p>
<p>Tools like <strong>Coolify</strong> or <strong>Dokploy</strong> (both open source) make deploying and managing multiple Docker instances straightforward from a single dashboard.</p>
<hr>
<h2>Hosting — Choosing the Right Region</h2>
<p>Mark's agency is based in Koh Samui. Engineers are in Bangkok. Clients are primarily in Asia. <strong>Hetzner is the wrong choice here</strong> — their data centres are in Germany, Finland, and the US. Bangkok to Frankfurt is ~200ms minimum latency. For Asian clients that's noticeable.</p>
<h3>The trade-off: EU pricing vs Asia latency</h3>
<p>Hetzner is the cheapest quality VPS in Europe, but cheapest doesn't mean best value when your clients are 9,000km away. A slightly more expensive Singapore server with fast response times is a better product.</p>
<h3>Recommended: DigitalOcean Singapore</h3>
<p>Closest equivalent to Hetzner for simplicity, pricing, and Coolify compatibility — just in the right region.</p>
<ul>
<li>Basic Droplet: 2 vCPU, 2GB RAM — <strong>~$12/month</strong></li>
<li>Singapore data centre — low latency to Bangkok, Koh Samui, and most of Southeast Asia</li>
<li>Engineers in Bangkok get fast access to the server too</li>
<li>Coolify works identically — no other changes to the stack</li>
</ul>
<p><strong>Vultr Singapore</strong> is a viable alternative at ~$10/month for the same spec. Less polished but solid.</p>
<h3>Frontend: consider Cloudflare Workers for global edge</h3>
<p>If Mark's clients span beyond Southeast Asia — Japan, Hong Kong, India, Australia — the VPS location still creates latency for the Astro frontend. The solution is to decouple frontend and backend hosting:</p>
<ul>
<li><strong>Directus + Postgres</strong> → Singapore VPS (DigitalOcean)</li>
<li><strong>Astro frontend</strong> → Cloudflare Workers (global edge)</li>
</ul>
<p>Cloudflare runs at the edge worldwide. Bangkok, Singapore, Hong Kong, Tokyo all get sub-20ms response times regardless of where the origin server is. One deployment, good latency everywhere.</p>
<p><strong>The trade-off:</strong> Cloudflare Workers adds a small amount of complexity — the Astro frontend deploys differently (via <code>wrangler</code> CLI rather than Coolify). Engineers will be comfortable with it, but it's one more moving part. Worth it if the client base is geographically spread; less important if most clients are in Thailand and Southeast Asia.</p>
<h3>Option comparison</h3>
<p><strong>Option 1 — DigitalOcean Singapore + Coolify (recommended to start)</strong></p>
<ul>
<li>Everything on one VPS, Coolify manages frontend and backend</li>
<li>Simple, cheap, good latency for Southeast Asia</li>
<li>~$12/month for the server, scales to 5–10 client instances</li>
</ul>
<p><strong>Option 2 — DigitalOcean Singapore + Cloudflare Workers frontend</strong></p>
<ul>
<li>Directus on VPS, Astro deployed to Cloudflare edge</li>
<li>Global latency for the frontend, regional latency for content API calls</li>
<li>Best for a geographically spread client base</li>
<li>Small additional complexity for engineers</li>
</ul>
<p><strong>Option 3 — Separate VPS per client (Singapore)</strong></p>
<ul>
<li>More isolation, easier to bill clients directly</li>
<li>~$12/month per client on DigitalOcean basic droplet</li>
<li>Clean but higher cost at scale</li>
</ul>
<p><strong>Recommendation:</strong> Start with Option 1. If the client base grows beyond Southeast Asia, move the frontend to Cloudflare Workers.</p>
<hr>
<h2>Domain & DNS Automation</h2>
<p>The goal: Mark enters a client name and the script handles everything — no manual DNS work.</p>
<h3>Recommended approach — two tiers</h3>
<p><strong>Tier 1: Subdomains of Mark's own domain (zero DNS work per client)</strong></p>
<ul>
<li>Set one wildcard DNS record on Mark's domain once: <code>*.deckchairasia.com → VPS IP</code></li>
<li>Each new client gets <code>clientname.deckchairasia.com</code> automatically</li>
<li>Coolify routes traffic by hostname, Traefik handles SSL via Let's Encrypt</li>
<li>No per-client DNS setup at all</li>
</ul>
<p><strong>Tier 2: Client's own custom domain (Cloudflare API)</strong></p>
<p>When a client wants their own domain:</p>
<ol>
<li>Ask client to move domain to Cloudflare nameservers (free, one-off ~5 mins)</li>
<li>Mark runs the script with the custom domain</li>
<li>Script calls Cloudflare API → creates A record pointing to VPS IP automatically</li>
<li>Coolify picks up the domain, SSL cert issued automatically</li>
</ol>
<p>Mark just needs one Cloudflare API token stored on the server. The script handles the rest.</p>
<h3>What the deployment script looks like for Mark</h3>
<pre><code class="language-">
Client name: Hilton Koh Samui
Custom domain? (leave blank for subdomain): hilton-koh-samui
→ Site will be available at: hilton-koh-samui.deckchairasia.com
</code></pre>
<p>That's it. Mark doesn't need to understand DNS, SSL, or server config.</p>
<h3>What happens under the hood</h3>
<ol>
<li>Docker Compose spins up Directus + PostgreSQL</li>
<li>Directus schema template imported automatically</li>
<li>Admin user created with a generated password</li>
<li>Domain registered in Coolify — Traefik picks it up</li>
<li>Cloudflare API called (if custom domain) → A record created</li>
<li>Let's Encrypt SSL cert issued automatically</li>
<li>Script outputs: admin URL, login credentials, client-facing URL</li>
</ol>
<h3>Tools needed</h3>
<ul>
<li><a href="https://cloudflare.com">Cloudflare</a> — free DNS management, API access</li>
<li><a href="https://coolify.io">Coolify</a> — handles domain routing and SSL</li>
<li><a href="https://traefik.io">Traefik</a> — reverse proxy (built into Coolify)</li>
<li>Let's Encrypt — free SSL (built into Traefik)</li>
</ul>
<hr>
<h2>Content Updates & Rebuilds</h2>
<p>When a client edits content in Directus, the Astro frontend needs to reflect those changes. The approach depends on whether Astro is running in static or SSR mode.</p>
<h3>Option 1 — SSR (recommended for Mark's use case)</h3>
<p>Run Astro in SSR mode with the Node adapter on Coolify. Pages render on request — always fresh, no rebuild needed. Any content change in Directus is live immediately.</p>
<ul>
<li>Slightly more server resource usage than static</li>
<li>Still fast — especially for low-traffic client sites</li>
<li>Zero ops overhead: no build triggers, no webhooks, no delays</li>
<li>Simplest setup for a small agency</li>
</ul>
<p><strong>Recommendation:</strong> Start here. It's less moving parts and instant updates are a better client experience.</p>
<h3>Option 2 — Static with Directus Webhooks</h3>
<p>If staying with static Astro, use Directus Flows to trigger a rebuild on content change:</p>
<ol>
<li>Editor updates content in Directus</li>
<li>Directus Flow fires a webhook on <code>items.create</code>, <code>items.update</code>, or <code>items.delete</code></li>
<li>Webhook hits the Coolify deploy hook URL for that site</li>
<li>Astro rebuilds and redeploys</li>
</ol>
<p>Directus Flows is built-in — no extra tooling. Each Coolify deployment exposes a webhook URL you paste into the Flow.</p>
<p><strong>Caveat:</strong> Full Astro rebuild on every save. Fine for occasional updates, annoying for frequent ones. Can add a debounce delay in the Flow to batch changes (e.g. only trigger after 5 minutes of inactivity).</p>
<hr>
<h2>Template Repos — Structure & Per-Client Customisation</h2>
<h3>Repo structure</h3>
<p>Two template repos, owned by Mark:</p>
<ul>
<li><code>agency-hotel-template</code></li>
<li><code>agency-property-template</code></li>
</ul>
<p>Each contains:</p>
<ul>
<li><code>docker-compose.yml</code> — Directus + PostgreSQL</li>
<li><code>directus/schema-base.json</code> — core content types (template, never edited per client)</li>
<li><code>directus/schema-custom.json</code> — client-specific content additions</li>
<li><code>client.config.js</code> — all client-specific config (see below)</li>
<li><code>assets/</code> — logo, fonts, images</li>
<li>Astro frontend</li>
<li>Deployment script</li>
</ul>
<h3>Frontend and backend: same repo, separate deployments</h3>
<p>They live together in source but deploy independently via Coolify. Redeploy the frontend without touching the database. The <code>docker-compose.yml</code> handles Directus + Postgres; Coolify deploys the Astro frontend as a separate service pointing at the same repo.</p>
<h3>Per-client repos</h3>
<p>Each new client gets their own repo created from the template:</p>
<pre><code class="language-bash">
gh repo create agency/hilton-koh-samui --template agency/agency-hotel-template
</code></pre>
<p>Client customisations never touch the template repos. Template improvements don't accidentally break live sites.</p>
<h3>Config-driven customisation</h3>
<p>All client-specific settings live in one file:</p>
<pre><code class="language-js">
// client.config.js
export default {
name: "Hilton Koh Samui",
domain: "hilton-koh-samui.deckchairasia.com",
theme: {
primaryColour: "#c8a96e",
font: "Playfair Display",
logo: "/assets/logo.png"
},
features: {
enquiryForm: true,
gallery: true,
bookingWidget: false
}
}
</code></pre>
<p>The Astro template reads from this — colours, fonts, feature flags. Client customisation is isolated to <code>client.config.js</code> and <code>assets/</code>. Template code stays untouched.</p>
<h3>Pulling template updates into client repos</h3>
<p>Each customer repo keeps the template as an upstream remote:</p>
<pre><code class="language-bash">
git remote add upstream git@github.com:agency/hotel-template.git
git pull upstream main
</code></pre>
<p>Because client changes are isolated to <code>client.config.js</code> and <code>assets/</code>, merge conflicts are rare. Template improvements flow through cleanly.</p>
<h3>Bespoke client work</h3>
<p>If a client needs a genuinely unique component — a custom booking widget, say — it goes in a <code>custom/</code> directory by convention, never touching core template files. That way <code>git pull upstream</code> never conflicts with bespoke work.</p>
<p><strong>For 90% of clients it's just config + branding. The other 10% get a <code>custom/</code> directory.</strong></p>
<hr>
<h2>Next Steps</h2>
<ul class="tasks">
<li class="task">&#9744; Build a proof of concept — one hotel site using Directus + Astro</li>
<li class="task">&#9744; Export Directus schema as a reusable template</li>
<li class="task">&#9744; Write a deployment script (Docker Compose + schema import)</li>
<li class="task">&#9744; Set up Coolify on a Hetzner VPS to test the workflow</li>
<li class="task">&#9744; Present to Mark with a working demo</li>
</ul>
<hr>
<h2>Links</h2>
<ul>
<li><a href="https://directus.io">Directus</a><a href="https://docs.directus.io">Docs</a></li>
<li><a href="https://astro.build">Astro</a><a href="https://docs.astro.build">Docs</a></li>
<li><a href="https://coolify.io">Coolify</a> — open source self-hosting platform</li>
<li><a href="https://dokploy.com">Dokploy</a> — alternative to Coolify</li>
<li><a href="https://www.hetzner.com/cloud">Hetzner Cloud</a> — cheapest quality VPS in Europe</li>
<li><a href="https://railway.app">Railway</a> — managed hosting alternative</li>
</ul>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment