Every year around Christmas, I think about changing my site from Wordpress to something new. This time I did it.
Purpose of frommel.net
Before spending too much time on this project, I needed to make sure that I actually still needed the site. Its main purpose was to share memories with my family, but I also use it for myself to refresh my memory from time to time. I don't use social media much anymore (except for Instagram), and social media posts are too ephemeral to be an archive/photo album. Having this site makes it easy to find and (re)share posts with family and friends. And it provides more access control than social media sites.
Why change from Wordpress?
While Wordpress worked 'ok' for a long time, there were some things that kept on nagging at me:
- I wanted more control over my work, and having all my content locked into a Wordpress database made me more and more uncomfortable. I really wanted to get the posts into a set of files that I could edit with any tool.
- Wordpress has a lot of bloat that I don't need.
- Depending on plugins for things like Flickr albums or password-protecting pages made the post content tricky and was also limiting, for example I could not get EXIF data from my Flickr photos.
- Customizing the site felt like a constant hacking exercise with lots of CSS to make it look the way I wanted it.
- I wanted to write posts using Markdown my own editors (and not in a wysiwig editor in a browser).
- It cost around $175 a year to host the Wordpress site and I actually think I can do it virtually for free with a different stack.
What tools did I choose?
Since I use NextJS and Node at work, I wanted to build the site in JavaScript, and chose NextJS. I did briefly consider VitePress (a higher level site framework by Vercel, the makers of NextJS), but it includes features I would not need and I was worried that it would not allow me the flexibility I needed without fighting with VitePress.
The main components of my new site are:
- NextJS - A React framework with a built-in node server and useful features for cacheing and routing.
- Tailwind CSS - a popular CSS framework I wanted to learn
- Flickr - I still host my photos and albums there as private photos, and access them using private account tokens, allowing me to have access control on my site instead of requiring people to have Flickr accounts to view my private photos.
- Markdown to jsx - a lightweight, customizable React markdown component
- Image gallery - a very configurable component for laying out an image gallery, including the ability to use NextJS's
Image
feature for optimizing the thumbnail sizes - Yet Another React Lightbox - a customizable image lightbox component (when you click on an image)
- S3 and Cloudfront for serving images fast (the ones not on Flickr)
Converting my Wordpress posts to Markdown
I did a Wordpress database export and then used an Observable notebook to wrangle the posts into markdown with proper graymatter headings for the posts. The most complicated parts of the conversion were:
- Lots of regex replace logic to convert the Wordpress shortcodes for videos, galleries and iframes to React component calls.
- Identifying images that are linked to posts so that they could be converted into markdown galleries.
I then simply wrote a tiny node script to write the JSON out as separate
.md
files.The features I loved making
- Dark mode - my site now has a dark and a light mode (it switches automatically based on your OS preferences)
- Once I got the Flickr API working with OAuth I could pull in the private image data from albums and individual images, including EXIF data, which I can now show in a popup on the Lightbox view.
- Next's image sizing and cacheing - this is actually quite amazing. The next server processes and caches the images at various sizes and serves smaller sizes to the browser when the viewport is smaller. Super critical for bandwidth-constrained users.
- Lazy loading... The Next
Image
component supports lazy loading, so I didn't have to build paging (yet)... instead you can just scroll and scroll (the page size without the images is very small) - Private with password - was pretty straightforward to add password protection to posts with a tag of 'private'. I can share the password with family and friends and it would add a cookie to their browsers so that they could view other private posts for a year without entering the password again.
- Next has a pretty easy way to add placeholder UI while waiting for the server to render a page.
- Search - I just do a regex search through the posts (and could easily make 'custom' search patterns, like entering just a year like '2020' would then show all posts from that year (as opposed to doing a text search). And multiple terms narrows the search instead of 'OR'-ing the search terms, so when you search for "camping 2005" it would narrow the camping results to just the eyar 2005, as opposed to returning all matches for 2005 as well as camping (this irritated me before).
Some technical Q&A
Do you load all you markdown files into memory to search through them or do you maintain your own search index?
I do the search server-side and just read the contents of all the markdown files into an array on every search. I didn't want to cache it yet (to prevent stale search results), and with around 500 posts, it seems pretty snappy. If this ever becomes too slow, I will introduce a cacheing layer, which should be very easy. I like the flexibility and control of the regex search and I like that I can add my own search rules (e.g. searching for a year when you have a search term that matches
/^20\d{2}$/
).How do the tailwind classes make it from Markdown to the html?
This is a pretty cool feature of
markdown-to-jsx
. You can override any html tag with your own version of it, including props like className
. So, for example, I configured p
tags to have some vertical margin (through a tailwind class) like so:p: {
component: "p",
props: {
className: "my-2",
},
},
This then adds the className to all my
<p>
tags. More importantly, this also enables me to use any React component in my markdown, for example adding a Flickr photo album to a post, I can simply write the following and it will render the component:# Test post
Here is a nice album.
<Photos flickr album="72177720311817608" rowHeight="400"/>
What does an article's front-matter look like?
It is pretty light at the moment:
---
title: Rebuilding my site in NextJS
subtitle: Another year, another rebuild
date: 2024-01-10
highlight: https://images.frommel.net/2024/01/next.png
tags:
---
What is the overall hosting setup, and how does deployment work?
So Vercel's limits on the image cache is too, well, limiting. I kept on going over the limit, which I find a bit suspicious. Anway, I decided to host on Netlify and adding in GA4 tracking (instead of the built-in Vercel analytics tracking). I think I can stay on the free plan with Netlify.
Most of my photos are on Flickr so I continue to upload albums and my own photographs there (private) and access them through OAuth credentials from the Next server. Those images are also sized and cached by Next's Image component. The Flickr hosting is $5.50 a month (I am on a 2-year plan).
For other static assets (images that were uploaded to wordpress before, and any other non-photographic assest), I am hosting them on a personal S3 bucket with CloudFront edge storage. I created a subdomain (images.frommel.net) that points to the CloudFront url. The cost of the S3 bucket will most likely be a few cents a month. Will see.
The code and blog posts are all in a private GitHub repo. The cool thing
about hosting on Vercel Netlify is that whenever I create a new commit on the main
branch on GitHub, it rebuilds and auto-deploys the site (takes about 1 minute).
This was all automatically
linked up when creating the site. They really do a great job making
this easy.