Back to journal
Directus 9 min read

Directus plus Next.js in production — what I actually wire up

The exact Directus + Next.js stack I ship on client projects: schema patterns, ISR strategy, image handling, and the guardrails that keep the token off the browser.

D
Deepak Mishra
June 24, 2026
Directus plus Next.js in production — what I actually wire up

Directus has quietly become my default headless CMS on client projects. Not because it is the flashiest option — Sanity and Payload are excellent — but because it is self-hostable, SQL-first, and gives editors an admin that they can navigate without a two-hour onboarding call.

Below is the exact production stack I wire up on a fresh Directus plus Next.js engagement, and the guardrails that stop the classic mistakes.

Schema patterns that survive year two

Every collection has an explicit status enum (draft, published, archived), a sort integer for editorial ordering, and a slug unique index. That trio covers ninety percent of real editorial requirements — no bespoke workflow plugins, no custom hooks.

Reference relationships are always modelled explicitly, never as JSON blobs. If a service belongs to a category, that is a real M2O — because six months in, the editors want a list of services filtered by category, and JSON blobs cannot be indexed.

The token never touches the browser

The single most common Directus mistake I audit out of client codebases: putting the admin token in a NEXT_PUBLIC_ env var. That ships it to every browser hitting your site. Instead, I put the token in DIRECTUS_ADMIN_TOKEN (no NEXT_PUBLIC_) and import a directus.ts module that starts with import "server-only". Node throws at build time if any client component accidentally imports it.

ISR — not client-side fetching

Every Directus read is a server-side fetch with next: { revalidate: 60 } — never a client component calling the API on mount. This does three things at once: the content is in the initial HTML (so search engines and LLMs see it on first byte), the token stays server-side, and CDN caching does most of the traffic work.

Fallback to static content when Directus is unreachable

Directus is self-hosted. Self-hosted services have bad days. On every project I ship a static fallback array in the codebase that the server-side fetcher returns when Directus is unreachable. The site never white-screens because the CMS is rebooting.

Image handling — Directus assets plus next/image

Directus files get served from DIRECTUS_URL/assets/{file_id}. That hostname goes into next.config.ts under images.remotePatterns, and every asset renders through next/image with explicit width and height — same as any CDN image. Do not fetch Directus files client-side to render them; Next.js will do the AVIF/WebP pipeline for you.

The permission model most people get wrong

In Directus 11, permissions live under policies, not roles directly. The public role gets a policy that grants read on published items only — that is one filter row per collection. The admin token stays admin. Anything else is a footgun.

What Directus is bad at

Not everything. Content localisation on nested relations is still fiddly; the visual builder is not Sanity Studio; and heavy media libraries are better served by a dedicated DAM. For most B2B and small-to-mid B2C storefronts, none of that matters. For a five-language global brand with a video-heavy library, it might.

Something you need shipped
this quarter?

Send a two-line brief with the outcome you're after — a build, an audit, a rescue, a second opinion. I read it personally and reply within one business day. No agency middle-layer. No sales funnel.