Contributor
Prasetya Ikra Priyadi
[email protected]It has been two years since I first deployed my personal web application to the internet. This project was my first full-stack application powered by Next.js, designed to serve as a web portfolio showcasing my work. At the time, it was a simple but effective setup: a static Next.js application hosted on Vercel’s serverless platform, with Firebase handling authentication and database management. I kept the design straightforward with basic CSS, and it worked well for the purpose of that stage in my career.
However, as I’ve gained more experience in web development and evolved into a full-stack engineer, I realized that my portfolio should reflect those advancements. It’s no longer enough for it to be a static showcase—I want it to have more dynamic functionality. Whether it’s adding new features, improving performance, or transitioning to server-side rendering (SSR), it’s time to upgrade this application into something that better represents my growth and capabilities in web development.
In version 1.0 of my personal web application, I built a basic Next.js 13 application using the Static Export build feature. This setup was ideal for static hosting platforms like Vercel, making deployment and production management straightforward. At the time, I chose to stick with the Pages Router because the App Router was still in its experimental stage, and I didn’t want to risk adopting something that wasn’t fully stable.
For styling, I relied on basic CSS files, which I used as parameters for my React components. This approach was simple and effective, especially as I was still learning the nuances of CSS. However, managing responsiveness across different screen sizes proved challenging. I only optimized the design for screens up to 1280px wide, which left room for improvement in terms of adaptability on smaller or larger devices.
Content management, such as handling project information, was done using Firebase. I even implemented an admin panel to manage project data. Authentication was powered by Firebase Authentication, and all data was stored in Firestore, a NoSQL database. While this setup worked well at the time, it came with its own set of challenges. Using Firestore instead of a traditional SQL database like PostgreSQL made features like pagination, search functionality, and data aggregation more difficult to implement efficiently.
In terms of coding style, I didn’t implement any best practices like atomic design, which made the repository less maintainable. At the time, I thought it was fine since I was the only one managing the app. However, I now realize the importance of following best practices, especially when it comes to scalability and long-term maintenance. Implementing proper structure and design patterns is a key part of my improvement process as I upgrade the application.
Overall, I was pleased with the final product at that time. It served its purpose of showcasing my work effectively. However, as I continued to evolve into a more experienced full-stack engineer, I recognized there was plenty of room for improvement. That realization led me to embark on the next phase of upgrading the application to better reflect my growth and offer more dynamic functionality.
There was plenty of room for improvement in my web application. With the same goal of showcasing my work as a Web Portfolio, I planned to enhance it by adding more advanced features, such as content article creation. I also saw the value in managing a self-built platform as a Full Stack Engineer. It not only allows me to practice my skills but also enables me to apply my knowledge in developing a real-world digital product. Additionally, having a fully self-managed platform can be a strong selling point when seeking new opportunities, as it demonstrates practical experience in both frontend and backend development.
Next.js has evolved rapidly since version 13, and it’s now at version 15 with many feature upgrades. Although it’s a relatively new release, I chose version 15 over the more stable version 14 for several reasons. The caching system in Next.js 15 is more advanced and works seamlessly with Server Components. Additionally, it uses React 19, the latest version, which offers better performance and new features. Migrating to the App Router was another critical step since it’s the direction Next.js will continue for future development. While some issues may arise due to the new release, I believe adapting to newer technologies will enhance my learning experience.
I also built the app with SSR (Server-Side Rendering) support so it can be deployed as a Node.js application in Standalone Mode. This allows me to access the Route Handlers for server-side backend functionality, turning the application into a fully integrated full-stack app where the frontend and backend are built together. For running the application on my VPS, I opted to use Bun.js as the JavaScript runtime. I’m excited to explore Bun since it’s reportedly better in terms of performance compared to basic Nodejs.
For coding style, I upgraded the project by introducing TypeScript to enhance the development experience. With over two years of experience using TypeScript, I’ve come to rely on it for type-checking and reducing errors. I also implemented Atomic Design principles to follow best practices, making the codebase more maintainable and easier to manage in the future.
In version 1.0, I built the front end using basic HTML tags and CSS. This setup worked well for simple customization, but as I gained more experience, especially during my time working with eFishery, I realized the value of using a design system. A system design improves development efficiency because it manages critical aspects like responsiveness, reusable components, and consistent styling templates. For version 2.0 of my app, I chose Chakra UI (v2) as my primary design system.
Chakra UI provides simple, ready-to-use components that are especially intuitive if you’re familiar with React components. It essentially offers a base React component that you can easily customize using CSS-in-JS styling, making it more efficient to apply styles without needing to manage separate CSS files as I did in version 1.0.
One of the major challenges I faced in version 1.0 was ensuring proper responsiveness across different screen sizes. This was difficult to manage manually, but with Chakra UI, it’s much simpler. The framework includes built-in responsiveness support, allowing me to easily adapt styles for various screen sizes. Additionally, Chakra UI automatically handles accessibility, which improves the usability of my app without requiring extra effort. This has significantly sped up development and made the process more efficient overall.
In version 1.0, I built the Admin Panel entirely from scratch, which involved creating the layout and implementing authorization checks. Developing this feature was challenging, particularly because I had to manage everything manually using basic HTML and CSS. The process was time-consuming, and setting up authentication and user access control felt especially complicated. To simplify authentication, I used Firebase Authentication, which provided a quick solution but still required a significant amount of effort to integrate into a fully functional admin panel.
For version 2.0, I decided to use Refine.dev, a framework specifically designed to simplify the development of admin panels. Refine.dev comes with built-in authentication and Role-Based Access Control (RBAC), making it much easier to implement authorization. It also provides a basic admin panel layout out of the box, so I didn’t have to spend time building it from scratch. Another feature I found particularly useful is the Data Provider schema, which helps manage HTTP requests on the client side. This framework has significantly accelerated my development process and made managing the Admin Panel much easier.
For version 2.0, I have three key features in mind for the Admin Panel:
Authentication Management
Since Refine.dev has built-in authentication, I plan to use it to handle user authentication entirely on my own. This means I will manage token generation, storage, and backend validation without relying on Firebase Authentication. Although this approach is more complex, I believe implementing a custom authentication system is a valuable skill for a software engineer like me. It’s essential to understand the fundamentals of authentication to become a well-rounded, proficient Full Stack Engineer. By taking control of this process, I’ll gain deeper insights into security practices and improve my ability to develop more robust and secure applications
User Management
This feature will allow me to manage users who can access the admin panel and contribute to the app in the future. For now, I’m the only user, but as the application grows, I want the flexibility to add other users or collaborators with ease.
Article Management
I want to expand the functionality of this web application beyond just being a portfolio by including a feature to manage articles. This will allow me to share my thoughts, experiences, and processes through written articles, creating more value for the app’s visitors.
Asset Library
For managing assets such as images and files, I am building an Asset Library. The library will store a list of uploaded assets, with the files themselves stored in Cloudflare R2. I plan to save the metadata for each asset in a database and display them in a gallery menu. This approach allows me to securely store assets, easily upload new ones, and reuse them when needed across the app.
These improvements will make the application more dynamic and flexible, allowing for easier content management and better long-term maintainability.
Using Firestore as a database was an easy and convenient approach, especially since it’s free (for now) and fully managed by Firebase. Its NoSQL structure, which stores data as document objects rather than in a traditional column-row format like SQL, made it simple to implement initially. However, I ran into limitations that led me to consider switching to another DBMS for version 2.0 of my web app.
One of Firestore’s significant challenges is how it handles more advanced queries like pagination, full-text search, and managing relationships between documents. These operations, which are straightforward in SQL databases, can become cumbersome and inefficient with Firestore’s NoSQL model.
For these reasons, I decided to migrate to PostgreSQL, hosted on Supabase. Their free tier is sufficient for my current needs and should last for a few months. PostgreSQL will handle all of my server-side data processes, including user authentication, storing article data, and managing asset metadata.
Since I’ve been using PostgreSQL for about two years, the implementation process has been relatively smooth. However, this is my first time deploying it in a production environment, which presents new challenges and learning opportunities. The switch to PostgreSQL allows me to build a more scalable, efficient, and flexible backend for my web application moving forward.
Using Vercel's serverless functions for my previous deployment was an easy and convenient choice, as it’s fully managed by Vercel, which reduced my worries about performance and scaling. However, after upgrading to version 2.0 of my application, I encountered a significant issue, Route Handlers stopped working after deployment, even though everything functioned perfectly in local development.
While I’m still investigating the root cause, this issue led me to switch to hosting my app on a VPS with 1 vCPU and 1 GB of RAM, which is more than sufficient for hosting a small application and should last for a few months before I migrate to a more powerful service.
To streamline the deployment process, I implemented an automated GitHub Actions Workflow, similar to what I did during my previous work at eFishery. This workflow automatically triggers when I push a new tag to my GitHub repository, building the app, transferring the artifacts to the VPS, building a Docker container from those artifacts, and deploying the container using Docker Compose. Given my experience working with Bitbucket Pipelines over the past two years, implementing this workflow was straightforward and efficient, making it easier to manage and scale my application in a production environment
The final step in upgrading my application was using Cloudflare as the primary security layer. Since my app is hosted on a VPS, I am responsible for handling all security aspects, which includes domain registration, DNS management, caching, CDN, and protection features like DDoS mitigation. Cloudflare manages all of these aspects efficiently, simplifying my job while ensuring my application remains accessible and secure.
One of the key focuses was ensuring that the app is accessible via the HTTPS protocol, with SSL/TLS certificates provided and managed by Cloudflare for end-to-end encryption. Setting this up was a bit complex, especially with the configuration of caching rules and DNS records, but it was an exciting learning process as I gained more hands-on experience with web security and application performance optimization.
While there’s still a lot to learn about advanced Cloudflare configurations and additional security enhancements, I’m confident that this setup provides a solid foundation for my application. For now, everything is working perfectly, and I’m looking forward to further improving and optimizing the app’s performance and security in the future.
my personal web application has undergone significant changes, evolving from a simple static portfolio to a dynamic, full-stack platform. This transformation reflects not just technical advancements but also my growth as a full-stack engineer.
The initial version of my app, powered by Next.js 13 and Firebase, was an essential starting point in my journey. It effectively showcased my early projects but had limitations in terms of scalability, functionality, and design. As I gained experience, I recognized the need for better practices, more dynamic features, and a stronger backend infrastructure to support my growth.
Version 2.0 represents a major leap forward. Upgrading to Next.js 15 with Server-Side Rendering (SSR) and transitioning to Bun.js has enabled me to build a full-stack application with improved performance and flexibility. Implementing TypeScript and adopting Atomic Design principles have improved maintainability, while Chakra UI has streamlined responsiveness and accessibility. Refine.dev has simplified the development of a robust admin panel with essential features like authentication, user, and article management.
The migration from Firestore to PostgreSQL ensures better handling of complex queries, relationships, and scalability. Hosting the application on my VPS with CI/CD pipelines built using GitHub Actions has provided full control over deployments and infrastructure, making the app more adaptable to future needs.
This journey has equipped me with valuable skills across both frontend and backend development, and the result is a self-managed platform that truly reflects my growth as an engineer. Moving forward, I’m excited to continue refining the application and exploring new opportunities that showcase my technical expertise and adaptability in real-world environments.
Built with Next.js and Chakra UI, deployed with Vercel. All text is set in the Inter typeface.
Copyright 2025 | prasetya_webspace-v2.0.8