In a continuation of the first post, here I will break down my blog, its many features, its challenges, and future plans.
The Blog and its Features
In my last post I broke down the VPS and infrastructure side of the stack without going terribly in to detail with all of the features and work I put in to the app. This post aims to rectify that by filling out the rest of the full stack.
For the most complex and security-sensitive parts of the application, I decided to delegate to third party services. These are covered in the other post, but they are things like Authentication where I feel I lack the necessary knowledge to get the full picture of making a secure and robust authentication scheme. For now, these were easy ways to deal with these complex problems and I have designed my code to allow me to swap these out at will.
Features
Login and administrator panels for a single user application
A Full-featured editor with previews for both the blog card and the actual post itself
Automatic draft saving
I have used a debounce function to automatically save drafts to the database whenever I stop typing for 1 second. This allows me to not worry about losing progress and is one of my favorite features of the site. Drafts work for both new and edited posts.
Image uploading and hosting
Every post has an associated image, which is automatically uploaded to an object storage bucket (R2 in this case from cloudflare) when the post is sent.
Markdown-compliant
Every post is saved in the database with markdown features and then parsed and rendered client-side in React Markdown, a fantastic component for handling this feature.
Responsive layouts
Looks great on all screen sizes.
Home-grown Continuous Integration and Deployment This comes in two parts. the Github actions, which uploads the image to Dockerhub and runs automated tests, and then a connection webhook program I wrote to automatically trigger a pull via docker-composes pull policy, which will always pull the newest image when I refresh docker compose. It is shoddy, but it allows for the most basic of continuous deployment and integration features, which was my goal. Likely in the future I will use something such as Jenkins as the webhook receiver instead of my small Go program. All of the code can be found on my github for this.
Tech stack, libraries, etc used
NextJS
This project was primarily created for me to get a handle on the fullstack framework ecosystem of react, largely for personal projects and less for professional work. I have written a blog website before and it was a VERY basic CRUD app using Go as the backend and some very mediocre javascript as the front end. Determined to not repeat the mistakes and lessons I learned there, I turned to frameworks for their familiarity, unified codebase and language, and various security features, as well as simply requiring the power that a backend would give me. NextJS is what I landed on to try first, as it is one of the most popular modern ones. I find its features such as Server Actions and its Caching to be very useful, but the power of this framework is a bit beyond the scope I need in a smaller application like this.
React
Ah, React. Functional, powerful, declarative. React is my most familiar library, and the general programming style lends itself well to all front end projects I have thrown at it. I spent a lot of time tinkering with things like custom hooks, using some of the more famous libraries, and trying out various styles of components in this application, really taking my time to dig in to its paradigms in an attempt to write cleaner code.
Typescript
I feel lost without a strongly typed language. I feel that typescript makes me a better programmer. It does lower my speed in writing features for now, but the guard rails and safety are indispensable.
TailwindCSS
I decided to go with tailwind and not CSS modules or CSS-in-JS for this project. Tailwind increases my velocity as a developer by allowing me to VERY quickly iterate through sane, curated steps of a CSS selector (ex. Using border-2, border-4, etc to grow and shrink the border size quickly) allowing me to worry less about the details and create a more cohesive layout based on these known steps. I try and use powers-of-2 and avoid odd numbers for everything. The typography of the blog post is also a Tailwind plugin which allowed me to very easily consider all screen sizes.
Shadcn
The main component library for the buttons and form fields and so on is Shadcn. They provide a tailwindCSS theme setup which is easy to work with, using tailwind classes. They are fully accessible, a task that is well beyond the scope of this project, and full featured compound components. They are a popular choice these days for those who want to focus on the building and less on the "other stuff", and are highly customizable as the code for them is strictly within your codebase instead of locked in your node packages.
I have also explored creating my own properties, though only briefly and for very specific purposes:
/* The blue to purple borders all use this gradient property */ @utility border-gradient { border-style: solid; border-image: linear-gradient( to right bottom, oklch(0.623 0.214 259.815), oklch(0.627 0.265 303.9) ) } /* This is used to place a square between two text blocks or span elements */ @utility between-flex-spacer { > *:not(:last-child)::after { content: "\25A0"; display: inline-block; padding-inline: 0.5rem; } }
Supabase Authentication
In the past, I have used firebase, one of the most known front-end developer persistence and authentication toolkits. While I believe firebase would be a great fit, supabase offers an SQL database over a NoSQL system and a larger variety of integrations with third party services. (As I said before in the last post, I actually log in to my site with my discord login of all things) which were more modern and appealing to my size of project. If I was not already self-hosting the database, I would likely also use Supabase for its relational database, as working with them is much more familiar to me than using document databases. The schema for my blog will likely not change again, which solves the problem that NoSQL solves neatly of having "evolving" schemas as your program changes and grows. This is fantastic for development, but beyond that, I much prefer the rigidity of something like Postgres.
Drizzle is the new kid on the block in the Object relational Mapper space. I largely picked this over Prisma due to its smaller learning curve and typescript-first attitude.. As the website grows, I look forward to using its unique relations system for table mappings, as a comments system is in the works.
Problems, Issues, and bugs
Image uploading
A large amount of complexity had to go in to automatic image handling. There are several states: No image was chosen, so the default is used An image was chosen, so it is uploaded automatically when posting and that image is used In editing, no image was chosen, and the image already existing is the default image In editing, an image was chosen, and the former image must now be replaced.
As well as: Several services (Amazon S3, Cloudflare R2) are used for the uploading and storage, necessitating custom handling.
Deleting unused images that were changed over has still not been handled, so eventually I will have to either manually or automatically track and delete many "dead" image links, but for now that number is in the megabytes and will be figured out later as I fill out the image service more in general.
The image service runs off of the backbone of these large cloud services and images are cached to be quickly responsive from the Content Delivery Network itself rather than having the single endpoint on the machine. Object storage lends itself extremely well to random, miscellaneous image links as well, and was an obvious fit for this problem.
React-hook-form
has less of a place in React 19 and full stack react, but still usable for more complex validation and its rather easy to use API. I quite like it, but without some sort of actions system it may very well be left behind.
form.watch errors
This is specific to react-hook-form. Apparently, even a form state going from undefined to "" counts as a change. I have not determined the best way to fix this yet, but I believe it will involve making the default that empty string.
NextJS
NextJS as a whole is a rather non-intuitive framework. It abstracts away a great deal, which causes someone unfamiliar with it or who makes assumptions about how things work (me) can run in to strange unexplained bugs. I had a problem where the way NextJS handles POST requests was causing errors with my geoIP service as it was stripping essential authentication headers. Nowhere was this explained in the nextjs documentation, and it caused me to have to adjust my nginx config to fix it. My situation is pretty unique, since I am self-hosting with docker, and most people are unlikely to run in to this problem, but it still is worth mentioning.
Future Plans
As I make these, I will go through and strike through all of the ones I have added.
Tags searching
Since this blog site may end up being a catch-all for my thoughts, I would like to allow tag filtering so that those who may want to find specific content (Technology posts versus video game posts versus music posts) instead of just clicking through the pagination looking for something relevant.
Revamp of dashboard, adding statistics, more CSS work in the back end
Since none of this is user-facing and is largely only for me, I have not put in significant time in to making the dashboard anything more than basically functional. I would like to improve it, adding metrics like post hits/site hits and other useful tracking information to allow for better feedback of how my site is doing, as well as improving table functionality beyond its very basic styling using tanstack table and shadcn.
Markdown editing features, creating links from popups etc..
Most markdown enabled websites with input boxes have at least some basic form of markdown dashboard. There may be solutions already made out there to do this, but it does not seem beyond me to create my own.
Comments System
Having nested comments will involve Recursive SQL Queries which will give me a chance to work with them. They will also need to be secured against abuse with rate limiting functions, and allow administrative deletion for bad actors.
Make it more deploy-able and customizable for other users
Currently, there is some hacky-ness involved with deployment and setting up the initial administrator user. I would like to smooth that out as a last step so that others might be able to use this website for their own blogs if they so wish.
Final Thoughts
This blog is the most complex achievement I have created to date. While still being a Create Read Update Delete app (CRUD) and not being too complex I have given it many unique features, building on top of the basics to make it a usable, stylish product (at least, for me, since it's my personal blog). I hope you enjoyed my deep dive in to this and please look forward to my next project breakdown!