Directus headless CMS + Next.js for agencies
Most Next.js templates pretend MDX is the answer to every content problem. For developer-authored content that is mostly true. For agency marketing sites, where the client edits their own case studies, services, and blog after launch, handing them a git repo and asking for a pull request is the wrong workflow. A headless CMS is the right shape, and Directus is the credible self-hosted option that does not lock you into a SaaS provider.
Why a CMS for an agency marketing site, not for a SaaS app
Agencies hand off marketing-site control to clients; clients are not developers; PRs and MDX commits are not their workflow. A CMS gives non-technical editors a familiar admin UI to update services, case studies, team bios, and blog posts without touching the codebase. Directus's admin is structured around your actual database schema, so 'case_studies' as a Postgres table becomes a 'Case Studies' collection in admin with the right fields.
The other three BoilerlyKit templates do not ship a CMS because their content shapes are different: SaaSForge Starter is developer-authored MDX, SaaSForge AI's content is user-generated (chat threads, uploads), and SaaSForge Core's product UI is dynamic per-workspace. CMS overhead would slow down those buyers; it pays for itself only on the marketing-site use case Agency is built for.
How Next.js fetches and revalidates Directus content
Server Components fetch from the Directus REST or GraphQL API with the typed SDK. Pages use Next.js's `revalidate` to cache responses for a configurable window (5-60 minutes is typical for marketing content), so a popular page does not hit Directus on every request but updates within the window after an editor publishes.
On-demand revalidation is the second mechanism: Directus webhooks fire on collection changes, hit a Next.js revalidation endpoint, and bust the cache for the affected slug. The result is that editors see their changes within seconds of publishing without a full rebuild. ISR-friendly fetching is the pattern that makes a CMS-backed Next.js site feel like a static site to visitors and like a live CMS to editors.
import { directus } from "@/lib/directus";
export const revalidate = 600;
export default async function CaseStudyPage({ params }: { params: { slug: string } }) {
const study = await directus.request(
readItems("case_studies", {
filter: { slug: { _eq: params.slug } },
fields: ["title", "summary", "body", "client", "translations.*"],
limit: 1,
})
);
if (!study?.[0]) notFound();
return <CaseStudy data={study[0]} />;
}Self-hosting Directus on Railway (or any Docker host)
SaaSForge Agency self-hosts Directus alongside Postgres 16 and Redis 7 via Docker Compose. Railway is the default deployment target in the docs because its one-click Docker deploy and managed Postgres remove most of the operational friction; AWS, Fly, Render, and any other Docker-capable host work the same way.
Self-hosting matters for agencies because client data, case studies, contact submissions, internal notes, lives on infrastructure you control rather than a CMS SaaS vendor's. The trade-off is operational responsibility: backups, updates, and uptime are yours. For most agency sites this is a fair trade; the data volume is small and Railway's managed Postgres handles the heavy lifting.
Schema-as-code and Directus migrations
Directus's schema lives in the Postgres database; changes to collections, fields, and relations can be exported as SQL migrations and committed alongside your code. This is the discipline that keeps a CMS-backed site from becoming a 'works on my laptop' problem: every collection change ships through the same review process as code.
SaaSForge Agency ships an initial schema covering services, case studies, portfolio items, blog posts, pages, and FAQs, each with translations for the three locales (EN, FR, ES). Adding a new collection is a Directus admin change plus a migration export, a half-hour exercise, not a research project.