The Jamstack architecture (JavaScript, APIs, Markup) combines a pre-built static frontend with a decoupled content API — giving you a fast, secure static site while still allowing non-technical content editors to manage content through a CMS interface.
The Jamstack Architecture
Headless CMS (content storage/editing) → Build process (fetches content, generates static HTML) → Static hosting (Nginx)
Choosing a Headless CMS
| Option | Notes |
|---|---|
| Strapi (self-hosted) | Full control, runs on your own VPS |
| WordPress (headless mode) | Familiar editing UI, exposed via its REST/GraphQL API |
| Directus (self-hosted) | Wraps an existing database with a CMS-style admin UI and API |
Step 1 — Deploy a Self-Hosted Headless CMS (Strapi Example)
npx create-strapi-app@latest my-cms --quickstart
Deploy following the same Node.js deployment pattern as How to Deploy a Node.js Application with PM2 and Nginx.
Step 2 — Configure the CMS API
In Strapi's admin panel, define content types (e.g. "Blog Post" with title, body, image fields) and set API permissions for public read access to published content.
Step 3 — Fetch Content at Build Time
In your static site generator (Astro, Next.js static export, Hugo with a data pipeline):
// Astro example
const response = await fetch('https://cms.yourdomain.com/api/blog-posts');
const posts = await response.json();
Step 4 — Build the Static Site
npm run build
This generates static HTML pages incorporating the fetched content at build time — no runtime API calls needed when visitors actually load the site.
Step 5 — Deploy the Static Output
See How to Host a Static Website on a VPS with Nginx for the deployment itself.
Triggering a Rebuild When Content Changes
Since the site is static, new content published in the CMS doesn't appear until the next build. Set up a webhook from your CMS to trigger a rebuild-and-deploy automatically:
// Strapi webhook configuration points to your CI/CD trigger endpoint
// which runs: npm run build && rsync deployment
See How to Deploy Automatically on Git Push for the underlying webhook-triggered deployment pattern, adapted here to trigger on CMS content publish events rather than Git pushes.
Handling Preview Content Before Publishing
For editors to preview unpublished changes, either run a separate "preview" build pulling draft content, or use your framework's built-in preview mode feature if available — consult your specific static site generator's documentation.
Securing the CMS Admin
The CMS backend itself should follow standard application security practices — HTTPS, strong authentication, and ideally IP restriction on the admin panel:
location /admin {
allow YOUR_TRUSTED_IP;
deny all;
}
Why This Architecture Is Attractive
- The public-facing site is static — extremely fast, minimal attack surface
- Content editors get a familiar CMS editing experience
- The CMS backend, if compromised, doesn't directly expose the live site (since it's pre-built, not rendering content dynamically per visitor)
Common Errors
Content changes don't appear on the live site — expected behavior until a rebuild runs; verify the webhook-triggered rebuild is correctly configured.
Build fails due to CMS API being unreachable — verify network connectivity between the build environment and the CMS API, and that API permissions allow the necessary read access.
Continue Reading
- How to Deploy Automatically on Git Push (Webhook-Based Deployment)
- How to Host a Static Website on a VPS with Nginx
- How to Set Up a Headless WordPress Backend for a Decoupled Frontend
Browse more articles in Static Site Hosting & Frontend Deployment.
