(I have since switched back to WordPress, since IPFS is not very stable and I needed some WordPress features.)
I’ve just switched my blog to using Hexo instead of WordPress, and it’s been an interesting experience… Hexo is a static site generator, which means all the code is executed directly in your browser, unlike WordPress which runs from a PHP server. Since I’m already familiar with npm
and Node.js which Hexo uses, it was actually quite a smooth process for me…
Now don’t get me wrong, WordPress is probably still the best blogging software out there. It’s simple to set up, easy to use, and has tons of plugins and themes. The reason I did this, is because I want to host my website on IPFS. IPFS is a pure peer-to-peer file hosting system, where there are no servers involved at all, and so WordPress would not be able to run on it.
I’m doing this as an experiment to see just how serverless this website can get. The end goal is to have the entire website running entirely peer-to-peer, with no servers, and no server costs! If this experiment works, the entire jjv360.me site could cost me nothing at all, and be entirely protected from network outages since the content is forever within IPFS’s network.
The biggest challenge I foresee throughout this move, is moving all the original blog posts from my WordPress site onto this blog… But if this works, I’ll do it. There weren’t that many posts there, anyway.
The site generator
So the first step for me was setting up Hexo. I looked at a few other static site generators, and Hugo also seems
like a good choice, but I went with Hexo since it uses Node.js which I am more familiar with.
I followed the guide on their site and set up my site. I then selected a theme and installed it. Next, I made sure the npm scripts were set up nicely. All this means is that I now have these commands I can run:
npm start
– Displays my site locally for testingnpm run build
– Builds my site into the/public
foldernpm run deploy
– Publishes my site to IPFS.
Posts and pages
The next part was figuring out how to add content in Hexo. It seems that Hexo automatically reads all markdown files in the /source
folder, and any other file types are just copied to the final website. Nice and simple, actually. So I made my first post (this one) at _posts/stories/hello-world-from-ipfs.md
. A post is really simple, it’s just a Markdown file with some fields at the top, like this:
--- title: Hello World from IPFS date: 29 December 2020 --- Post content here.
For pages, the process was a bit different. Hexo converts all Markdown files inside the source/_posts
folder into posts, and all Markdown files outside of that folder into pages. Pages don’t show up in the post list. So I created a file at source/pages/about-me.md
, and it gets converted to /pages/about-me.html
. I then added that link to the sidebar menu.
Categories
On my WordPress site I had buttons along the side to view posts by categories, such as Coding, Scripts, Stories, etc. I’d like to recreate that here too. This one took me a long time to figure out, but it turns out that Hexo generates a page for each of your categories, which lists the posts in that category. So all I had to do was add links to these to my sidebar. For example, I have a category called "Stories"
, so I added a link to my sidebar that pointed to /categories/Stories
.
Importing from WordPress
My next step was to import all existing posts from WordPress. I used the hexo-migrator-wordpress
plugin, and it was a simple matter of running hexo migrate wordpress backup.xml
and it was done. There’s now a Markdown file in the _posts
folder for each post that was on WordPress. However the import did not keep the formatting for a lot of posts, so I moved them all into _drafts
and plan to publish them individually after checking the content for each one…
Uploading to IPFS
Now for the most interesting step, at least for me… Uploading the site to IPFS. I modified the deploy
script in package.json so that it would upload the generated output to IPFS. I created a deploy.js script, which first runs hexo generate
and then ipfs add -r -Q /public
, to get the hash of this new content.
At this point, the website is readable at https://ipfs.io/ipfs/HASH. Or it would have been… Turns out Hexo generates absolute URLs for all it’s content. This would probably still have worked once the jjv360.me address had been set up, but I wanted to test each and every step, so I had to add relative_link: true
to the Hexo config file. Without this step, my site had no styles at all, and was just plain text.
Pinning the content
With IPFS, content stays on the machines of whoever views it for a while, until other content pushes it out. This means the more people looking at that content, the more available it is, and the faster the site will load. Currently, the script will store the content on my own computer, but if no-one looks at this site often enough, my computer will be the only place the content is served from. This means the site will be slow, and if my computer goes down it will not load at all.
To work around this, I could have written a script to continually access the site through public CDNs, but this isn’t an ideal way to do it… Instead, I’m going to use the Pinata pinning service. Websites like this have their own IPFS nodes, and allow you to “pin” your content to them, meaning the content will always be in their cache. This gives the IPFS network another possible node to get your content from, so the site should be available even if no-one has visited it in a while and my own computer is offline.
I’ve also updated the deploy.js
script to pin the hash on Pinata after adding to my local IPFS node automatically.
Setting up a name on IPNS
With the way things are now, I’m going to have a new address (https://ipfs.io/ipfs/HASH) every time I run npm run deploy
. But that’s not what I want, I want an address that will always point to the latest content. This is where IPNS comes in. IPNS is a part of the IPFS system, and it allows you to create a hash which is actually a public key, and it resolves to another hash address. This means you can only change the pointer if you have the private key which matches the public key.
There is also DNSLink, which is a faster and easier alternative to IPNS, but it requires updating the DNS records every time the content changes… I would have preferred this method, but my DNS provider is currently on Amazon AWS, and I don’t want to include access credentials for that here… So I’m going to use IPNS instead, which only requires changing DNS records once when you set it up.
After updating the deploy.js
script, I now have a permanent address for my site which looks like https://ipfs.io/ipns/HASH.
Setting up the DNS
The final step is to ensure that the IPNS address is used when visiting the main website address, jjv360.me. Ironically, I’ll be using DNSLink for this. DNSLink allows pointing to IPNS hashes as well as IPFS hashes, so it just needs to be set up once and point to the IPNS hash.
After adding the appropriate TXT records, my site is now fully available at https://ipfs.io/ipns/jjv360.me. The next step is to make it so when you go directly to jjv360.me, you get to this address instead.
This was a simple matter of changing my domain’s A and AAAA records to point to a public IPFS gateway. I chose to use Cloudflare’s gateway instead of the one at gateway.ipfs.io, since it allows for generating SSL certificates. This means the site will continue to work, even if you visit via HTTPS.
A more decentralized site name
There is also a system called Ethereum Name Service which allows you to register a .eth address on the Ethereum blockchain. Using this, the entire thing would be completely decentralized. So I went and set up jjv360.eth to point to this site as well. Now the entire website exists in the cloud, and there are no servers at all in any step of the process!
I would switch to using jjv360.eth only, but there is a problem. Since ENS is such a new system, currently most mainstream browsers don’t support it without an extension like MetaMask…
Complete
And it’s done! Now both jjv360.me and jjv360.eth point to this website. Here’s a summary of the parts involved:
- Hexo generates the website content as static files.
- IPFS allows those files to be accessed through a peer-to-peer network, as a website.
- IPNS allows a single hash to be used to always access the most up-to-date version of the website.
- DNSLink allows the entire process to be handled seamlessly from a normal browser’s point of view, so normal users who don’t have IPFS installed can still view the website via a public gateway.
- ENS provides an alternative website name (jjv360.eth) which is not dependent on DNS servers, and makes the entire thing pure P2P.