Like many tech enthusiasts, I like maintaining a blog as both a portfolio and a sort of basic documentation for personal projects that I’ve worked on in the past. I was previously using the Ghost blogging platform - but it was a bit like using a sledgehammer to crack a nut. It’s an amazing fully featured platform for a broad audience, but I wasn’t using its analytics dashboard, comments, accounts and subscriptions features. And most importantly, its WYSIWYG editor, while powerful, wasn’t really meeting my expectations.
What’s the problem?
Like most technical writers and devs, I gravitate towards writing Markdown - and while Ghost does support it via MD editor blocks - I would at least expect it to support all the features GitHub’s Markdown flavour does, like Mermaid Diagrams.
Finally, Ghost wants you to author posts directly within their interface. That wasn’t working for me, as the editor I have most familiarity with is Outline, a team knowledgebase app literally built around markdown documents, where I found myself drafting articles to then copy manually over to Ghost block by block. Changes were then going out of sync, and the whole thing was a mess.
What do I want?
What I wanted was a platform where I could author markdown articles in Outline, publish them to a collection, and have them automatically sync to my platform of choice. Hosted Outline instances do allow you to publish a vault or collection as a browsable wiki, but with limited customisation. In any case - my instance is self-hosted.
So what’s out there?
AFAIK no turnkey Outline-to-blog solutions exist yet, though browsing the Markdown Guide tools directory and neighbouring ecosystems shows plenty of close cousins:
- Zero-Friction Markdown Publishers: Hosted platforms like Blot (which turns a synced Dropbox or Git folder of
.mdfiles directly into a blog), JotBird (one-click publishing for Markdown and Obsidian notes with callout/Mermaid support), and Markdown Space nail the "write Markdown, get a webpage" experience — but they are hosted services rather than self-hosted pipelines wired into Outline. - Vault & Workspace Bridges: Open-source tools like Quartz publish local Obsidian vaults as interconnected digital gardens. However, it doesn’t speak Outline's RPC API, use webhooks, or private attachment redirects.
- Static Frameworks & Styling: In the Astro ecosystem, I love Sat Naing’s AstroPaper for it’s clean, single-column technical blog. Meanwhile, VitePress is what I’m most familiar with and natively supports Outline's
:::callout blocks and Shiki syntax highlighting out of the box.
What’s the solution?
My solution, VitePaper, brings those ideas together into a self-updating, single-column technical blog purpose-built for Outline. It ports AstroPaper’s visual design and typography onto VitePress, pairing a minimal theme with an authenticated Outline attachment sync engine and webhook rebuild server in one published container. You’re looking at it right now!
How It Works: Architecture Overview
At a high level, VitePaper treats a single Outline collection as a headless CMS, split into Drafts and Published parent documents. Everything runs inside a single container: a lightweight HTTP server serves the compiled static site while listening for HMAC-signed webhooks from Outline whenever a document is created, updated, or moved into Published.
When a webhook fires, the sync engine pulls the published document tree via the Outline API, caches any private attachments locally, transforms Outline-specific markdown quirks and frontmatter into clean .md files, and triggers a background VitePress build. Within seconds of hitting save in Outline, the updated static bundle is live with zero downtime.
Syncing Content & Authenticated Attachments from Outline
Outline's RPC API makes fetching the document hierarchy straightforward, automatically turning nested subfolders under Published into post #tags and syncing Drafts as unlisted noindex preview pages at /drafts. The real catch is media: pasted images in Outline resolve to private /api/attachments.redirect?id=<uuid> URLs behind authentication. During sync, VitePaper intercepts these links, downloads the binaries using an OUTLINE_API_KEY, caches them in public/attachments/, and rewrites the Markdown paths so public visitors never hit a login wall.
Internal links and @mentions between Outline documents get the same treatment. Links to other published posts are rewritten to /posts/<slug> and automatically populate a bidirectional Referenced In backlinks section at the foot of the target article, while links to private notes are gracefully unwrapped into plain text so they never 404.
Bridging Outline Markdown to VitePress
Because Outline doesn't have native frontmatter fields, VitePaper looks for an optional ```yaml code block at the very top of a document and strips it into VitePress frontmatter. That lets me override dates, slugs, and tags, or even pin standalone pages (like About Me) directly to the top navigation bar with nav: About Me and excludeFromPosts: true.
To keep authoring natural, a few custom markdown-it rules smooth over the gaps between Outline's ProseMirror exporter and VitePress: fixing mangled bold-inline-code tokens, grouping consecutive [tab: Title] code blocks into interactive ::: code-group tabs, and expanding inline tokens like `icon:github` or `integration: socials` into inline SVG brand icons and social link bars.
The Minimal Theme
Rather than fighting VitePress by building a heavy custom theme from scratch, VitePaper extends DefaultTheme with Tailwind CSS v4 and five lightweight Vue components (PostHeader, PostFooter, PostList, Mermaid, and SocialLinks) injected via layout slots. This ports over AstroPaper’s warm parchment light mode, dark slate & orange palette, IBM Plex Mono typography, View Transitions morphing post headers, a floating/docking back-to-top button, and an undulating navbar wave—while keeping VitePress's best built-ins like Ctrl+K local search, slide-out/sidebar table of contents, and build-time RSS and sitemap generation completely intact.
Self-Updating Container & Webhook Rebuilds
In production, a tiny Node server (scripts/server.ts) serves the static bundle via sirv with immutable asset caching alongside a POST /api/webhook/outline listener. When Outline fires a webhook, the server verifies the HMAC-SHA256 Outline-Signature header, debounces rapid save bursts over a 3-second window, and builds the updated site into a staging directory (dist-next/) before atomically swapping dist/ in place—delivering instant updates without an external CI/CD pipeline or a single dropped request.
Wrapping Up & What’s Next
Moving from Ghost to VitePaper has turned publishing into a zero-friction workflow inside the editor I already use every day: draft in Drafts, preview live at /drafts, and drag into Published when ready.
If you also self-host Outline and want a similar setup, the full source code, pre-built Docker image (ghcr.io/jcktwd/vitepaper), and configuration guide are available on GitHub (jcktwd/vitepaper).
\