Personal project
stack:Next.js, Firebase, GraphQL (Apollo server), Typescript
what i did:Everything
This project is arguably the largest project I've built as a web dev. Since I didn't plan on releasing this as a product or service for anyone but myself anytime soon I had two primary goals for this project:
I had a range of features I wanted this CMS to have, but considering the scope and size of this project and me just being one person, I wanted to first create a Minimum Viable Product (MVP) where i build all the essential features that I need for me to start using it seriously. As such, this project is not completely finished and there are things missing and incomplete, but it has reached a point where it's fully usable for me in production. I defined the following feature list for this project:
This page would be way too long if I cover every feature in detail so instead I'll detail the most important ones below and how I overcame some hurdles and the lessons I've learned building them.
I wanted to use this project to get more comfortable building a larger project with Next.js & Firebase. I wanted in particular to take advantage of Next.js's API routes and cloud functions to create a REST API for the consumers of content from the CMS. As I developed the REST API I figured it would be a great opportunity to learn GraphQL better and build a GraphQL API. I had used GraphQL a bit getting data from Shopify's APIs for Your Special Sound but this would be the first time I built and configured my own GraphQL server from scratch.
The CMS is organized around the concept of a hub. A hub has several collections and or posts. A collection has one or more fields. There are 4 types of fields: Plain text, numbers, Rich Text, or images/media. I wanted to keep this as simple as possible, keeping in mind that the CMS should be completely oblivious and not care about how or where the content is rendered, therefore there was no need for field types tied to UI or front-end behavior.
The post type is really just a rich text field that has some extra information attached to it like an excerpt, description, author, etc. It mostly exists so that I can create a blog really fast.
When fetched by a consumer all data is output as simple JSON for versatility and compatibility.
The media library is used by the user in 3 ways:
Here the user can upload, delete, edit or search for media that is attached to that current content hub. In the back end, a bucket is created in Google Cloud Storage for each content hub belonging to that user/admin. This would allow me to add folder structures as a feature should i want to, but for now, I went with the WordPress solution with one huge store. One of challenges building this page was to implement search and two different display modes as well as the pagination of media (more on that later).
Therefore the data was separated into a seperate Media context, to separate the data from any components that wanted to interact with it. This made it much easier to have both a list and gallery view as the data remained the same, React simply rendered a different UI component around the same data, which allowed for persistence of things like search state, which image is currently selected and it's metadata etc.
This component is actually a child component of the Rich Text Editor, and is how the user inserts media quickly inline into the content. Because this component can only render 8 items at a time (to not look ugly and to stay small and compact) I needed to implement pagination for it to work properly. This is another benefit of seperating into a seperate media context, as I created all the pagination logic there and exported them to users of the context
<code goes here>
This one is fairly straight forward. You can simply add an image as a field in a collection. The popup that the user uses to pick the image is much larger than the media insert popup, but also this uses pagination if the number of elements exceeds a certain amount.