Directus vs Hygraph: Who Owns the Database Decides It
Directus layers over your own SQL; Hygraph holds content in a federated graph. The axis that decides it, and why this blog's 326 posts run on neither.
Directus and Hygraph are both headless CMSs with an auto-generated API, and they disagree about one thing that decides everything downstream: who owns the database. Directus is a layer you point at your own SQL database — the tables stay yours. Hygraph is a GraphQL-native SaaS where content lives in their cloud and you consume it over their API. Pick by whether your content is already in a database you run.
Every other difference follows from that. The feature grids are close enough to be useless: both generate APIs from a schema, both do localisation, webhooks, roles, preview and asset handling. The architectural split is not close at all.
The comparison that matters
| Directus | Hygraph | |
|---|---|---|
| Model | A layer over your SQL database | GraphQL-native SaaS content platform |
| Content lives in | Your Postgres / MySQL / SQLite / MSSQL | Hygraph's cloud |
| Can it adopt an existing database? | Yes — that is its core trick | No — you model inside it |
| API shape | REST and GraphQL, auto-generated | GraphQL-first |
| Hosting | Self-host (open source) or their cloud | SaaS only |
| Schema source of truth | The database itself | The Hygraph project |
| Distinctive strength | SQL access, self-hosting, data you already have | Content federation, remote sources in one graph |
| Distinctive cost | You operate a database and an app | Vendor-held content; egress is an API migration |
| Query mental model | Tables, relations, filters | One typed graph, stitched across sources |
| Licence | Open source, source-available terms by version | Proprietary |
The row that decides most projects is "can it adopt an existing database". If you already have a Postgres instance with real tables that other systems write to, Directus can sit on top of it and give editors a UI over data that was never designed for a CMS. Hygraph cannot do that, and does not try to — it wants to be the content store.
The row that decides the rest is federation. Hygraph's content federation lets you expose remote REST or GraphQL sources as fields in the same typed graph, so a product record from your commerce backend and marketing copy from Hygraph arrive in one query. If your pain is "the front end has to stitch four APIs together", that is the feature you are buying, and Directus has no direct equivalent.
What the two look like in a front end
Directus gives you a filterable REST or GraphQL layer over tables. Reads look like querying a database, because you are:
// Directus — tables, relations, filters
const posts = await directus.request(
readItems("posts", {
filter: { status: { _eq: "published" } },
fields: ["slug", "title", "author.name"],
sort: ["-published_at"],
limit: 20,
}),
);
author.name is a join across a relation Directus read out of your schema. Nobody declared that relationship to the CMS — it was a foreign key first.
Hygraph gives you one typed graph and you ask it for a shape:
// Hygraph — one typed graph, one query, stitched sources
const { posts } = await hygraph.request(gql`
query Posts {
posts(where: { stage: PUBLISHED }, orderBy: publishedAt_DESC, first: 20) {
slug
title
author { name }
# a field resolved from a REMOTE source, not stored in Hygraph
inventory { inStock }
}
}
`);
The inventory field is the part Directus cannot copy: it is served by another system and appears in this response because the graph federates it.
Read the two together and the trade is plain. Directus's version is as powerful as your schema and stops at your database's edge. Hygraph's version is as powerful as the graph you compose and stops at what the vendor will hold.
A disclosure, because it affects how much weight to give this: this site runs neither. The code above is the documented shape of each product's client, not a benchmark from production here. Public pricing and plan details for both move often enough that any figure quoted in a blog post is stale by the time you read it — check the vendors' own pricing pages before you commit, and treat the aggregator comparison sites that dominate this search as lead generation.
Choosing between them
Pick Directus when: the data already exists in SQL; something other than the CMS writes to those tables; you need to self-host for cost, residency or compliance reasons; or you want SQL-level access for reporting and migrations. The cost is that you are now operating a database and an application — backups, upgrades, connection limits, all of it.
Pick Hygraph when: content is authored from scratch by a content team; you want a managed service with no infrastructure; or your actual problem is that content is scattered across several systems and the front end is paying for it. The cost is that content lives somewhere you do not control, and "we can export over the API" is true but is an API migration, not a database dump.
A tiebreaker that is not on any feature grid: who writes the schema. In Directus the database is the source of truth, so a developer changing a column changes the CMS. In Hygraph the project is the source of truth, so a content modeller changing a field changes the API your build consumes. Whichever of those two people you have more of is the system that will be cheaper to live with.
The third option, and why this storefront took it
The 326 posts on this blog run on neither CMS. They are MDX compiled at build time, with a typed registry beside them. That is not an argument that headless CMSs are wrong — it is the same "who edits" question answered differently. Here the people writing posts are the people with repo access, so a CMS would add a network dependency and a second place for content to live, in exchange for an editor nobody needed.
The content model is three files that must agree, and the interesting part is the enforcement. src/data/blog.ts holds typed metadata — slug, title, description, date, cluster, target keyword, reading minutes, related templates. src/content/blog/<slug>.mdx holds the prose. And src/lib/blog.ts pairs them through an explicit map:
const postLoaders: Record<string, () => Promise<{ default: ComponentType }>> = {
"directus-vs-hygraph": () => import("@/content/blog/directus-vs-hygraph.mdx"),
"contentful-vs-storyblok": () =>
import("@/content/blog/contentful-vs-storyblok.mdx"),
// … one line per post
};
That map is written out slug by slug on purpose, and the comment above it explains the cost of the obvious alternative: a dynamic import built from a slug variable works, but it hands the bundler a wildcard — every .mdx in the directory joins the graph, a typo becomes a runtime error instead of a build error, and nothing type-checks. Explicit entries make a missing file fail next build, and src/data/__tests__/blog.test.ts turns a forgotten entry into a failing npm test. The suite is 391 tests across 31 files, and the drift check between those three files is one of them.
Metadata is deliberately separate from prose so the index grid, the home teaser, sitemap.ts and the RSS feed can list posts without compiling a single article. That is the thing a hosted CMS gives you for free and a file-based model has to design for: a cheap way to enumerate content.
What this approach costs, stated plainly, is the whole reason the two products above exist. There is no editor. There is no preview for a non-developer. Scheduling a post means merging at the right time. If any of those were requirements, the MDX model would be the wrong answer and one of these two would be right. MDX vs a headless CMS for a blog works through that threshold in detail.
The defect this model shipped, since first-party means admitting them
The MDX pipeline is wired in next.config.ts, and it is four lines:
const withMDX = createMDX({
options: {
remarkPlugins: ["remark-gfm"],
rehypePlugins: ["rehype-slug"],
},
});
Those plugin names are strings, not imported functions, and that detail cost real debugging time. Turbopack runs the MDX pipeline in Rust and cannot receive a JavaScript function reference. Pass the imported plugin and the build succeeds while silently dropping it — which surfaced as every comparison table in every post rendering as paragraphs of literal pipe characters, because MDX defaults to CommonMark and CommonMark has no table syntax. A silent no-op is the worst failure mode a build plugin has, and it is now a comment in that file so nobody re-learns it.
Worth noting what that class of bug looks like on the other two: with Directus or Hygraph, a rendering pipeline failure is yours too, because both hand you content and leave rendering to the front end. What you do not own with a hosted CMS is the content's availability — which is the trade in one sentence.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Directus shows a table but no relations | Foreign keys are missing in the database | Add real FK constraints; Directus reads relations from the schema |
| A Directus collection is invisible to the API | Role has no permission on it | Grant the role read access per collection, not globally |
Hygraph query returns null for content you can see | You are querying the PUBLISHED stage for a draft entry | Query DRAFT with a preview token, or publish it |
Federated Hygraph field is null in production only | The remote source is unreachable from their infrastructure | Check the remote's auth headers and allowlists, not the query |
| Content models drift between environments | The schema lives in a UI, not in version control | Export/commit the schema; promote via their migration tooling |
| Self-hosted Directus is slow under load | No connection pooling, or an unindexed filter column | Pool, then index the columns your filters actually use |
| Switching CMS later means rewriting the front end | Vendor response shapes leaked into components | Map responses to your own types at one boundary |
That last row is the practical hedge for either choice. Both products' responses have a distinctive shape, and if that shape reaches your components, the CMS has become a dependency of your UI rather than a source of data.
Frequently asked questions
Is Directus free? The open-source version is self-hostable at no licence cost, with their cloud offering as a paid alternative, and the licence terms have changed across major versions. Read the licence for the version you intend to run — this is the one question where a two-year-old blog answer is actively dangerous.
Does Hygraph support REST? It is GraphQL-first; treat REST as not part of the deal. If a REST consumer is non-negotiable, that is a point for Directus, which auto-generates both.
Can I use either with Next.js static generation?
Yes — both are plain fetches at build time, so generateStaticParams plus a fetch per route works with either. Content changes then need a rebuild or on-demand revalidation, which is the same constraint the MDX model has.
Which is better for a marketing site a content team edits daily? Hygraph, most likely — it is designed for exactly that, and self-hosting a CMS to serve a marketing team is infrastructure you did not need. Directus wins when the content is also operational data.
Templates in this post
ASoc Folio (a portfolio site with filtered work), ASoc Forge (an AI resume-builder landing page) and ASoc Frame (an AI image-generator landing page) each ship a Next.js edition, so either CMS above can be wired into a finished front end rather than an empty project.
Browse the full sets: Next.js landing page templates, Tailwind landing page templates.
