
Design to Deployment, Building a CMS from scratch: A Reflection.
When I set out to improve my portfolio website, I realized that adding new content like experiences or projects was unnecessarily tedious. Each update required me to modify the codebase, commit change
January 9, 2025 · 7 min read
When I set out to improve my portfolio website, I realized that adding new content like experiences or projects was unnecessarily tedious. Each update required me to modify the codebase, commit changes, and redeploy the entire website. It wasn’t just time-consuming—it also lacked flexibility and scalability.
That’s when I decided to tackle the challenge head-on: building my own content management system (CMS) as part of my portfolio. My goal was to create a dynamic platform that would allow me to seamlessly add, edit, and manage content without ever touching the codebase again. In this blog, I’ll walk you through my journey of designing and implementing a custom CMS using Next.js, and Firebase.
The Motivation:
Static portfolio websites are great for showcasing your skills, but they can be limiting when it comes to maintaining and updating content. I wanted more flexibility without compromising performance or design.
Here’s what I wanted my CMS to accomplish:
1. Dynamic Content Management: Add and update blog posts, projects, and experiences without editing code.
2. Streamlined Workflow: Avoid manual redeployments for every small change.
3. User-Friendly Interface: Create a dashboard that feels modern and intuitive.
4. Scalability: Use tools that could support additional features in the future.
Development Process:
Choosing the Right Database for My CMS
When building a CMS, one of the first decisions I needed to make was where to store all the data for the website. After evaluating a few options, I narrowed it down to two possibilities:
1. MongoDB with Prisma
MongoDB is a powerful NoSQL database, and Prisma offers a fantastic ORM (Object Relational Mapping) for managing schemas and queries.2. Firebase Firestore
Having worked with Firebase on a previous project, I was familiar with its SDK and how the database worked fundamentally.
Ultimately, I chose Firebase Firestore because:
- I was already comfortable with the Firebase SDK.
- Firestore's schema-less structure meant I didn’t need to define complex database schemas upfront, which would have been required with MongoDB and Prisma.
- The setup and integration were straightforward, saving me time during the development process.
Integrating Firebase Firestore
Setting up and integrating Firestore went smoothly, allowing me to store and manage data for the Experience and Projects sections efficiently. Adding, editing, and deleting records through Firestore felt intuitive, and its real-time updates ensured that changes reflected on the website immediately.
Challenges with Storing Blog Content
While Firestore was a great choice for structured data like project details or experience entries, I quickly encountered challenges when working with blog content:
String Size Limitations
Firestore imposes a limit on the size of strings stored in a document, which became problematic for storing large blocks of blog content.Lack of Formatting
When I fetched blog content stored as a single string, it appeared as a blob of unformatted text. There was no easy way to include HTML elements (e.g., headings, links, images) or apply styling to the content.
Solution: Storing Blog Content in Firebase Storage
To overcome these challenges, I decided to:
Use Firebase Storage for saving blog content as HTML files. This allowed me to include fully formatted content with all the necessary styling and structure.
Store the URL of each HTML file in the Firestore database. When fetching blog data, the content was retrieved from Firebase Storage, maintaining its formatting.
This approach not only solved the string size limitation but also allowed me to add rich, styled content to my blog posts.
While storing blog content as HTML files in Firebase Storage solved the limitations of Firestore, it came with an entirely new set of challenges.
The Challenge:
Cross-Origin Resource Sharing (CORS), When I tried fetching the content using the contentURL, I quickly ran into errors. Each fetch request routed to a Google Storage bucket, but because the origin domain of my website was different, the requests failed due to CORS (Cross-Origin Resource Sharing) restrictions. At first, I was completely unaware of this protocol. Having never worked with Firebase Storage before, I didn’t realize that I would need to define specific CORS rules to allow content to be fetched securely.
Troubleshooting and Finding the Solution
After several failed attempts to fetch the content, I decided to dive deeper
Tutorials and Research: I looked up tutorials and guides on Firebase Storage and how to configure CORS rules and Asking ChatGPT for Help: I also turned to ChatGPT, which provided clear guidance on how to resolve the issue. Eventually, I learned how to configure the cors.json file in the Google Cloud Console. The process involved: Writing a JSON file that allowed requests from my domain. Uploading and applying the CORS configuration to the storage bucket.
Adding Authentication and State-Based Content with Clerk
After successfully setting up Firebase Storage and fetching content dynamically, I wanted to add an additional layer of functionality to my CMS: state-based content management. My goal was to ensure that only authenticated users (like myself) could make changes to the data, adding a layer of security and control to the platform.
Why I Chose Clerk :
I explored a few options for implementing authentication, including Firebase Auth and custom solutions. However, I decided to use Clerk for the following reasons:
1. Ease of Use: Clerk provides a developer-friendly API and out-of-the-box components for authentication, making integration seamless.
2. State Management: With Clerk, I could easily manage user states, ensuring that editing and content management were restricted to logged-in users.
Adding State-Based Content
With authentication in place, I updated the CMS logic to ensure:
View-Only Mode for Non-Authenticated Users: Users who aren’t logged in can view blog posts, experiences, and projects but cannot edit or add new content.
Full Access for Authenticated Users: Authenticated users can add, edit, or delete content directly through the CMS dashboard.
This state management was straightforward with Clerk’s useUser hook.
Integrating Clerk into my CMS added a seamless, secure authentication layer. This not only protected the data but also made managing content feel professional and modern. With just a few lines of code, I was able to:
- Restrict content editing to authorized users.
- Provide a personalized experience to logged-in users.
- Simplify the overall development process with Clerk’s prebuilt UI and API.
The State of the Platform Today
After overcoming these challenges and integrating all the essential features, the platform is now in a stable and functional state. It allows me to:
- Effortlessly add new blog posts to reflect on my experiences working on projects or share thoughts on topics I find interesting.
- Manage dynamic content for my portfolio, such as professional experiences and completed projects, without touching the codebase.
- Securely edit content through state-based access and authentication using Clerk.
The platform is not only a showcase of my technical skills but also a tool that simplifies content creation and management for my portfolio.
Future Enhancements
While I’m happy with how the CMS turned out, I already have a few ideas for future enhancements to make it even better:
Analytics Integration:
Add a dashboard to track blog views, user engagement, and other metrics.
Role-Based Access Control:
Enable multiple contributors by implementing role-based permissions.
Improved Blog Editor:
Enhance the blog editor with more formatting options, such as embedding videos or adding custom components.
Automated Backup System:
Periodically back up all data and media files to ensure nothing is lost.
Final Thoughts
Building this CMS platform has been one of the most fulfilling projects I’ve worked on. It has pushed me to think critically about development workflows, learn new tools, and solve real-world technical challenges. More importantly, it’s a reflection of my growth as a developer and my ability to create solutions that are both functional and scalable. You can also explore the source code on GitHub or reach out to me with any questions or feedback.
Thank you for reading! 🚀