Content · We run it ourselves
MDX is a format that compiles Markdown into JSX, so a React component can be placed in the middle of a paragraph and rendered as part of the prose.
There are three places content can live in a React application, and they are not interchangeable: a database behind a CMS, a typed module in the repository, or a file that is prose with components in it. MDX is the third. We use it in seahero, where documentation pages need a working component beside the sentence explaining it, and we deliberately do not use it on this site, where every page is a typed object that a build script can refuse. The choice is not a matter of taste — it is a question of who writes the content, how often, and what the build is allowed to check.
01
MDX exists to solve one problem: prose that needs a live component inside it, not beside it. A Markdown file can hold a code block; it cannot hold a running interactive example that the paragraph before it is describing. In seahero we compile MDX through @next/mdx and next-mdx-remote-client precisely for that shape of page — documentation where the explanation and the working thing must be adjacent, and where splitting them into a component tree with the text passed as props makes both halves unreadable.
02
The two loaders in seahero are not redundancy, they are two different content lifetimes. @next/mdx compiles files that ship with the repository — they are part of the build, they fail the build when broken, and they can import components directly. next-mdx-remote-client compiles a string at runtime — content that arrives from somewhere else and is not present when the build runs. Choosing the wrong one is the common MDX mistake: build-time compilation gives you type safety and no runtime cost, runtime compilation gives you content the deploy does not gate, and a project that reaches for the second when it needed the first has bought an evaluation step for nothing.
03
MDX is a compiler, not a store. It has no editorial workflow, no draft state, no scheduled publish, no field validation, no user who is not a developer. Every one of those is a CMS feature and MDX supplies none of them. This is the honest reason MDX is popular in developer-facing documentation and rare in marketing sites with a content team: the format assumes the person writing the content also holds a git checkout and understands what a component import means.
04
Typed content modules — the approach on this site — are what MDX cannot give: a shape the compiler and the build script both understand. Because every page here is an object with named fields rather than a document, scripts/check-schema.ts can fail the build on a dangling @id in structured data, scripts/check-markdown.ts can fail it when content negotiation and the routes disagree, and scripts/check-knowledge.ts can enforce invariants across the whole corpus. None of those checks are possible against a prose file. A build gate can read a field; it cannot read an argument.
05
The cost of typed modules is exactly what MDX removes: prose becomes a string in a TypeScript file, and long-form writing inside string literals is unpleasant work that nobody does well. That trade is worth taking on a site of a few dozen structured pages where every field must be validated, and it is a bad trade for fifty documentation pages of flowing text. Seahero pays the MDX cost, this site pays the typed-module cost, and both are running.
06
The MDX toolchain does not stand alone — it is a Markdown pipeline with a JSX target, which means the remark and rehype ecosystem is available underneath it. In practice that is where the useful work happens: heading anchors, syntax highlighting, link rewriting, frontmatter extraction. A team that adopts MDX and never touches the plugin layer has taken on a compiler to get a slightly better Markdown, and would have been better served by plain Markdown and a renderer — which is what this site does for its /services/<slug>/md twins, plain Markdown served by content negotiation, no compilation at all.
07
Content that must be readable by machines argues against MDX and for typed data. A page assembled from typed fields can be emitted twice — once as HTML for people, once as Markdown or JSON for automated clients — because the source is structure, not presentation. An MDX file is already committed to one rendering; extracting clean data back out of it means parsing prose. On this site, /llms.txt and the Markdown twins exist because the content was never prose to begin with.
An entry that cannot point at something you can open, run or read does not compile. That is a property of the type, not a promise in a paragraph.
Invariant
MDX stops working the moment the person writing the content is not the person who can run the build. There is no draft state, no preview for a non-developer, no field the format itself validates, and a mistyped component name is a failed deploy rather than a visible error on one page — so a marketing team that publishes weekly will end up routing every change through an engineer, which is slower than the CMS the project was trying to avoid. It also stops paying on very small content sets: below roughly ten pages, the compiler configuration, the plugin layer and the two-loader decision cost more time than writing the pages as typed modules or plain Markdown ever would.