Reflecting on my Architecture Decisions: One Year Later
It's been just over a year since I completely rebuilt this blog from the ground up. I had to make a lot of decisions a year ago about what the architecture would look like, and now that I've lived with those choices for a year, I want to take a moment to evaluate where things stand today. What decisions do I regret? Which ones worked well? Let's dig into it! I'll go over some major decisions I made, and how they've worked out.
When I launched this new blog, I wrote a post about the architecture. If you want more details about how certain things work, you can check that out for further reading.
1 - No CMS
My blog doesn't use a CMS. I prefer to own and control access to my content as much as I can. For this reason, I ruled out using a cloud CMS like Contentful. I also didn't want to deal with hosting my own headless CMS, like Strapi or Wordpress. Instead, all my posts are written in MDX and directly committed to the repository.
- I have complete ownership over my content, and don't rely on a proprietary storage format.
- MDX is highly flexible. For example, I can use React components to embed custom layouts.
- Content is open-source along with the application code for my website.
- Because content is essentially code, it's nicely version controlled and I can create branches while I try out different wording.
- More difficult to publish & update - requires a PR to the repo.
- No web GUI to quickly make changes when away from my computer.
- Less scalable than a CMS - as my volume of content grows, markdown files will become difficult to manage.
- Trickier to set up with Next.js and stay efficient. I had to build some custom APIs to serve the content for single blog posts in some situations.
Overall I've been very happy with this decision. With a CMS I wouldn't be able to do one-off unique layouts, like the grid layout I'm using for this postMessage. Using MDX, I can just write a little CSS and keep most of the content in markdown. I ended up building a simple API that wraps the static files, so I can still query for a single post's metadata, like the number of words, reading time, description, and cover image.
I will have to think about file and content organization more as my content library grows. I don't have any worries about being able to solve that issue, however, when the time comes.
2 - Next.js App Router & RSC
This site is built using the Next.js app router. I started building it right as the beta came out, so it was a bit rough around the edges but really stabilized substantially as I got the site built out. I'm rendering everything serverside making heavy use of React server components, rather that doing ISR or partial prerendering. This is primarily for handling user settings, like the current theme. On every request, these settings are read from a cookie to make sure there's not a flash of the wrong theme on the initial render.
- RSC enables my markdown content solution to work. Even without static generation, I can still efficiently serve the pages, along with complex image metadata for the loading blur state.
- Server rendering allows me to simplify logic around data fetching. For example, the cheers and view count components at the top of each post handle their own data and rely on suspense and RSC.
- I rely on many of the app directory features, like intercepted routes for the modal that opens when clicking a post link.
- I like building with server components.
- Good learning oportunity for me as the technology evolves.
- The app directory and server components were a new paradigm that took time to learn.
- The development process is slower - the builds take longer to hot reload and everything is generally slower in dev mode. I expect this will improve in time.
- Choosing to forego static generation or partial prerendering can hurt performance, especially since my old blog was entirely statically generated.
Despite being server rendered dynamically, I'm still able to get great performance from the site thanks to server components. However, I do miss the simpicity of a statically generated site that doesn't require so many moving parts for every request. I would probably still build things the same way given the opportuity, however I have started to build some pages using the pages router that I want to be able to statically generate. These are pages that don't need to be aware of the global user settings, like my landing pages for conferences. I am going to move more toward this hybrid approach going forward.
I use this blog as a testing ground for new features and technologies, so some things are overengineered on purpose if I am working on learning something new. Could I have built a simple static-generated site? Definitely. However, it was more fun to build it this way and learn some new React. My old statically-generated blog is still available and frozen in time, for comparison's sake.
3 - CSS Modules Instead of Tailwind
I'm a big proponent of Tailwind and I've written a bit about it, as well as use it heavily at work for a brand-new design system. However, when I started the project on this new site a year ago, I wanted to write it in vanilla CSS using just CSS modules. This was partially to keep my CSS skills sharp, but I also wanted the flexibility to write complex CSS that might be a bit beyond what Tailwind supports.
- I get to continue using and expanding my CSS skills!
- I can write complex selectors that just can't be done in Tailwind.
- Performance is great because I can ship a minimal amount of CSS (only what's needed for the page).
- Rapid prototyping is slower than with Tailwind, because I need to set up CSS files and imports.
- I had to write a lot of custom code for handling the colors and my theme.
If I could go back, I'd prefer to just use Tailwind from the beginning, and only drop into writing plain CSS when I needed to do something more complicated than what Tailwind supports. There's no reason it needs to be one or the other, as I can do some Tailwind and some CSS. I've actually started using Tailwind in the pages directory when I work on pages that are static generated. I may end up doing more of the blog itself in Tailwind too as time progresses.
I chose not to use Tailwind in this project for the reasons I outlined above, but I still am a big proponent of it! Most new projects I start these days are 100% Tailwind for styling.
4 - Vercel For (Almost) Everything
My Next.js site is hosted on Vercel. My speed insights and web analytics are handled by Vercel. My edge config for feature flagging uses Vercel. My KV database for rate-limiting "cheers" actions is Vercel KV. Image optimization? Vercel. I think you see the pattern here: I'm pretty locked into Vercel. Here's how that's been going:
- Tight integration with Next.js is a huge benefit. No need to be concerned with how it runs.
- Performance is great. My site is running in edge functions and is fast for the majority of my readers.
- Speed insights are incredibly valuable for diagnosing potential issues.
- Build-in analytics are convenient, and protect my readers' privacy by not collecting sensitive information.
- Image optimization gives me a huge performance boost.
- Cron jobs are easy to set up and run for background cleanup tasks.
- Vercel is expensive. For now I'm getting by with the base Pro plan (no overages yet), but as I need to scale price will become a serious factor.
- Support is suboptimal. For non-enterprise customers (like me) I've found that getting help from support (even for major issues) can be slow.
- There's a bit of a vendor lock-in feeling that I don't love.
I generally like owning my content as much as I can, which usually would mean I don't like locking in to one vendor. However, in this case, Vercel makes my life so easy that I am happy to use them for the majority of my infrastructure. I don't have to pay much now, and have some room to grow. Worst case scenario, if I scale to the point it gets too expensive, there are other options for hosting a Next.js site, just none that are quite as straightforward as Vercel.
I don't worry too much about scaling with Vercel. My traffic doesn't cause overages yet, and to be honest if I get to the point where it becomes a problem, I'll be happy to have and solve the problems that come with large-scale growth.
5 - Database: Planetscale
The largest piece of my infrastructure not running on Vercel is the database. Vercel's offering is far too expensive for what you get, and with my usage it wouldn't be a reasonable choice. Instead, I'm able to host everything on the free tier with Planetscale. I use the database for view count tracking, cheers count tracking, management of some settings, and for my QR code scan link mappings.
- My usage stays within the free tier, and probably will for a while!
- Excellent support for running from edge functions, so my server components are fast and responsive even when they need to make database queries.
- Great security features - recently I accidentally leaked my database keys and they immediately detected the leak and invalidated them to protect me.
None come to mind
My use-case is simple, but Planetscale has satisfied it perfectly, and I use them for the majority of my projects. I don't see that changing in the future.
6 - Object Storage: Cloudflare R2
Object storage is relatively new offering from Vercel, but it's too expensive for my use-case, so I use R2 from Cloudflare. All the images in my posts, as well as photo galleries are stored in R2, then processed at build time to generate some metadata, like the dimensions for each image, and a blur data URL for the loading state. After that, optimized images are served from Vercel, so the R2 storage is really just for holding the source material and I don't do a lot of traffic to Cloudflare itself.
- My usage stays within the free tier, and probably will forever!
- API is compatible with AWS S3, which I'm already familiar with.
- Performance is excellent.
- A nicer web UI than AWS S3.
- Documentation isn't great, and I had to rely a lot on the S3 docs and forums when integrating R2.
Since I don't do much direct traffic to the assets, the performance of my object storage is pretty inconsequential, but I like knowing that if anything changes with my approach, Cloudflare's object storage is fast and cheap, and can scale with my project.
Conclusion
After 1 year, while there are a few things I've learned and might do differently in the future, overall the majority of my decisions held up well, and I haven't had any major issues. It's a fairly hotly debated topic whether the Next.js app directory is stable enough for production use, and I can comfortably say that it is, based on working my way through building this website in parallel with the development and growth of the framework. The various cloud providers I've chosen for my services have been reliable and easy to work with, and I still stand by the tools I chose when I embarked on this project.
Have you made some architecture decisions lately you either love or hate? Join the discussion on X!