The Right to Exit
I’ve had a personal blog on WordPress in one form or another since about 2008. However after watching much of the community getting disrupted by power struggles last year, I decided it was time to try something new for my personal blog.
Rather than simply jumping to another platform with the same structural risks, I decided to see just how far I could go in building my own decentralised publishing stack.
The Goal

Traditional blog publishing features three key layers:
- CMS (Content Management System) – The software layer for creating and managing content.
- Host – Where your website files live on the internet.
- Domain – A memorable name that helps people access your site.
My goal was to decentralise every layer.
The Decentralised Architecture

I’d already been playing with some of these ideas in my head, so these were ultimately the tools I chose:
| Layer | Tool | Why I Chose It |
|---|---|---|
| CMS | Nostr | Signed portable content served via relays |
| Host | IPFS + Fleek | Censorship-resistant, globally mirrored |
| Domain | ENS (.eth + eth.link) | Human-readable decentralised domains |
Nostr as a CMS
Nostr (Notes and Other Stuff Transmitted by Relays) is a decentralised messaging protocol that uses public key cryptography, similar to Bitcoin and Ethereum. At its core, you sign messages, and broadcast them to a network of relays, which mirror the content. While the primary way most people use Nostr is through apps such as Primal that offer a Twitter-like experience, the protocol has been expanded through a series of NIPs (Nostr Implementation Possibilities) that specify several other content “kinds” which can represent other data.
To use Nostr as a CMS, I make use of several message kinds:
- Kind 0: Profile metadata
- Kind 1: Short-form posts e.g. Twitter-like microblogging
- Kind 30003: Link lists (used to build a link tree here)
- Kind 30023: Long-form blog posts (Like this one)
Instead of using an existing Nostr client, I built a static frontend that fetches and renders the content directly in the browser with help from the Cursor IDE. The entire site was written in vanilla TypeScript with zero frameworks to avoid as many dependencies as possible.

IPFS via Fleek as Host
IPFS (InterPlanetary File System) is a peer-to-peer protocol for storing and sharing data. Instead of using location-based naming, e.g., danielwonder.com, it uses content addressing via file and folder hashes. Due to the nature of hash functions, the same hash will always resolve to the same content as long as someone on the network is hosting a copy of the content. It first gained wider recognition as a protocol for hosting NFT media. However it can be used to host entire websites.
To host a file or collection of files, users must pin content on their node, which keeps serving it through the network. However, I don’t want to rely on others altruistically pinning and serving my site, so I host my own node that serves the site from my personal server.
Additionally, I pay for hosting from a company called Fleek, which pins my site across their network, including globally distributed IPFS nodes and gateways served through an edge network. Fleek connects directly to my GitHub, so that every push to `main` deploys an updated version of the site, which generates and pins a new IPFS hash.
As every update generates a new hash, it typically wouldn’t be ideal for a blog. You would need to re-pin and update your domain records every time you publish a new update. To address this, I designed my website as a static template that dynamically pulls the content from the Nostr network. By separating dynamic content (via Nostr) from the static website (served by IPFS), I can make frequent content updates without changing the website hash.
ENS as the domain
If you’ve ever seen an IPFS hash, you know it’s not something that most people will remember, especially if it changes often. On the traditional web, we use domains like .com to solve that problem for IP addresses. And in fact, I do still serve my blog through a .com domain as well for convenience. But ENS (Ethereum Name Service) provides the final layer of my decentralising stack.
ENS allows you to store an IPFS content hash in the `contenthash` field. This lets anyone with an ENS-compatible browser access your site through a human-readable address such as `danielwonder.eth`.

For broader compatibility, there are also HTTP gateway services that let `.eth` domains resolve using the traditional DNS system. You can use these by simply appending `.link` or `.limo` to the end of the domain. The end result is a single domain, e.g. `danielwonder.eth.link`, that works in traditional browsers and can also be accessed through the `.eth` name without relying on centralised DNS infrastructure.
One final note is that I could have used ENS as the profile layer instead of Nostr. I chose not to, as it would have introduced additional dependencies and I was already pulling content from Nostr, which also includes profile fields such as name, avatar, and social links. That said, I’ve added my Nostr pubkey to my ENS profile as an additional layer of verification.
Key Tradeoffs & Considerations
While the structure I’ve chosen optimises for decentralisation and true ownership, there are some downsides.
- Slower page loads – The content is pulled after the initial page load which removes the “instant” feeling you get on some websites.
- Relay Dependence – Currently my Nostr relay set are defined by a set of ~8 traditional domain names. I am actively exploring ways to host a decentralised relay.
- Requires JavaScript – This means some older crawlers may not explore all content and users must trust your scripts.
Future Development Plans
Right now, the static site template has a few creative shortcuts that allowed me to ship fast, e.g. fallback assets. However, it already features a simple config file to allow for alternative identities and custom Nostr relays. I plan to open source it and allow anyone to use the template with their own Nostr key and host it however they want.
