Markdown blogging is doable (but not the way I initially thought)
I’ve always liked writing in plain Markdown. It’s fast, distraction-free, and doesn’t force me into any framework-specific workflow... Khm-khm .mdx... For a while, I used Docsify.js to turn Markdown into a clean, interactive blog with almost zero setup and honestly, it worked great.
It really worked great. Until the audience wasn't agents.
The problem with client-side rendering
Docsify is purely client-side. It relies on the global window object and renders everything in the browser. That used to be fine.
But things changed.
- A large portion of traffic is now agent-driven (LLMs, crawlers, automation tools).
- These agents most of the time don’t execute client-side JavaScript.
- Even when they do, they’re not patient enough to wait for rendering and the
ajaxrequests Docsify does to reach out for the actual markdown content...
So effectively, the audience might never see your content. Not to mention how bad this is for those lovely SEO bots.
At the time I worked around this by pre-rendering Markdown into HTML using Cloudflare Browser Workers. On every update, I’d spin up a headless browser, render the page, and cache the result. (Obviously, this was a DB trigger on Xano..)
Rethinking the approach
I wanted something simpler:
- Write plain
.mdfiles - No
.mdx, no JSX, no TSX - No build step
- Still support components, embeds, and extensions
- Fully server-renderable HTML
My initial idea was:
- Parse Markdown using
marked - Walk the AST
- Inject custom components (Docsify-style syntax)
- Render everything server-side
Before building it, I sanity-checked the idea with Perplexity and got mixed feelings. I was directed towards Markdoc which already exists and is exactly what I was thinking...
Enter Markdoc
Stripe’s Markdoc does exactly this.
- Markdown + structured extensions
- AST-based processing
- Server-side rendering by design
- Custom renderers (built-in html or react, I should build one for preact)
- Custom tags and components
It basically solved every requirement I had.
Implementation setup
I wired Markdoc into my stack:
- Backend: Xano
- Markdoc processing runtime: Deno (lambda-style execution)
- Rendering: Markdoc → HTML
Basic flow:
import Markdoc from '@markdoc/markdoc';
const ast = Markdoc.parse(markdownSource);
const content = Markdoc.transform(ast, config);
const html = Markdoc.renderers.html(content);
Custom tags
You can define your own components:
const config = {
tags: {
alert: {
attributes: {
type: { type: String, default: 'note' }
}
}
}
};
Then use it in Markdown:
{% alert type="tip" %}
This is a tip.
{% /alert %}
Dealing with edge cases
A few things needed extra care, because I work with Xano and a bit of a niche tech stack:
- Twig conflicts: Markdoc syntax overlaps, but manageable with config tweaks.
- Code highlighting:
prism.jsinitially broke rendering.
Root cause: CSS/styling issues, not Markdoc itself. - Rich content: Added support for (which were external plugins in Docsify.js, but needed a rewrite for Markdoc):
- Mermaid diagrams
- Animated terminal previews
- Enhanced code blocks
The result
I ended up with:
- Fully server-rendered HTML
- Clean Markdown authoring experience
- Extensible components (without JSX)
- No build pipeline
- Fast delivery for both humans and agents
Most importantly, writing feels predictable again. I don’t have to wonder whether the final output will match what I had in mind or whether it’ll even render for all the audience (even if there's actually no traffic here LOL).
@calycode, we build open, reliable, and automated workflows for Xano that empower teams with clarity, transparency, and control. Our mission: make backend development safe, collaborative, and future-ready, so every team can ship confidently and trust the process.
We have a Chrome Browser Extension for Xano to get you rolling much faster and more reliable Xano systems. If you're curious how @calycode/cli works with Xano features, check out our repo, docs or drop us a message on Discord!
You can also get started with Xano right now, here.