Bottom Line Up Front: At Graphite, we moved from a simple SPA deployment to a robust containerized environment using AWS ECS, enhancing our ability to manage, deploy, and scale our Next.js application.
AWS re:Invent 2023 is in full swing, and we’re here in the heart of Las Vegas, seeing the sights, exploring the conference, and spreading the word about stacking. It’s the perfect time to showcase how Graphite uses AWS in our own infrastructure, in combination with Next.js, to support our growing user base. In this post I’ll walk you through our journey from a simple SPA architecture to the adoption of a more robust solution. I'll explain why we considered various hosting options, the technical considerations that guided our decision, and why we ultimately chose to host our Next.js web application on ECS.
First, some context.
The software development world moves incredibly fast - so much so that the tools and practices that served us well yesterday can quickly become limitations today. For our team at Graphite, this became especially salient as we reevaluated our web hosting strategy for our code review platform.
As a startup, the choices we make now for infrastructure can either pay dividends in empowering our growth or they can be ticking time-bombs of tech debt that create roadblocks in the future. Choosing the quick and easy path in the moment is always tempting, particularly for a small team, but the time you’ll save if you do your homework first can far outweigh the benefits of immediate gratification.
We've historically hosted our splash page and web app as single-page applications (SPAs) in S3, distributed with CloudFront—a setup known for its speed, simplicity, and reliability. However, as our platform and user base grew, the limitations of our current system began to show. It was time for change.
Create React App had served us well, but we were in need of more sophisticated features such as faster build times and bundle splitting. We had to choose a new direction that would allow us to implement these capabilities without significantly increasing our system complexity or hosting costs.
After much exploration and debate, we decided on Next.js, primarily on account of its rapid widespread adoption and the reliability of its maintainers at Vercel.
You can read more about our journey in depth and how we moved to Next.js in just 5 PRs, however, migrating to Next wasn’t just about adopting a new framework; it was also about how to host it effectively while maintaining our performance standards and cost efficiency.
Vercel seemed like the obvious choice for hosting a Next.js app in terms of performance, simplicity, and devex, and for many companies is probably the right answer. However, it didn't align with our preference for keeping all of our hosting within our AWS VPC, and the costs were intimidating compared to our near-zero expenses with S3.
AWS Amplify also caught our attention initially, but the devex wasn’t a great fit for our enterprise-level needs - Amplify offers little visibility into builds and lacks the sophisticated deployment features we needed.
Ultimately, we decided to go with a familiar technology our team already trusted: containers on Amazon Elastic Container Service (ECS). Containerizing our Next.js application unified our frontend and backend hosting strategies, allowing us to use templated terraform configurations and take advantage of the load balancers already in place for our backend, setting the stage for blue-green deployments and simplified rollbacks.
This choice may seem unconventional—running Next.js in a container rather than utilizing edge compute features—but it was a calculated decision. We conducted rigorous testing, including a head-to-head comparison of our Next.js splash page hosted on edge lambdas versus ECS containers, and found no significant difference in performance.
Let’s take a look at the real-world applications of our hosting strategy and how it stands up to the demands of a growing enterprise platform.
The shift to hosting our Next.js application on AWS ECS didn't happen in a vacuum; it came soon after our launch out of beta. We needed to scale and needed infrastructure that would scale with us, both in terms of performance, and feature set.
The six main benefits of deploying Next.js on AWS ECS :
Performance: One of the initial concerns with containerized hosting was performance, especially when compared to edge computing solutions. However, our empirical tests showed that Next.js running in containers on ECS performed just as well as an edge compute setup. The ability to leverage Amazon's reliable infrastructure meant that we didn't sacrifice the speed our users had come to expect.
Scalability: As our user base grows, so does the load on our services. ECS facilitates easy scaling of our containerized services to meet increasing demand. By using templated Terraform configurations, we can replicate environments quickly and efficiently, which is also essential for a growing startup like ours.
Cost Efficiency: Keeping an eye on compute costs is crucial for an early-stage startup. With our ECS setup, we continue to benefit from AWS's pay-as-you-go pricing model, maintaining cost-effectiveness while enjoying the advantages of a more dynamic hosting environment.
Consistency and Simplification: By unifying our hosting strategy for both frontend and backend services on ECS, we’ve simplified our infrastructure. This consistency reduces cognitive load for our team and streamlines our development pipeline, making the entire deployment process more straightforward.
Deployment Flexibility: ECS allows us to implement sophisticated deployment strategies. We've adopted blue-green deployment, which reduces downtime and risk by running two identical production environments, only one of which serves live production traffic at any time. This means we can deploy and test a new version without impacting our users, switching over only when we're confident in the update.
Security and Compliance: Hosting within our AWS VPC keeps all of our services within a secure, controlled environment. This is not just about peace of mind; it's about ensuring that we adhere to best practices and compliance requirements, which is non-negotiable for enterprise-grade applications.
While these benefits were a huge upgrade over our previous SPA setup, even the best infrastructure requires careful management and optimization. Next, we’ll share some of the tips and best practices that have helped us make the most of our ECS hosting environment.
Migrating to a containerized environment with ECS was a significant technical undertaking that required a lot of iteration, and along the way we developed the following list of best practices:
Optimize for cold starts: When using server-side rendering with Next.js on ECS, it’s crucial to optimize for cold starts. Efficient container startup times are vital for ensuring that new instances can handle traffic spikes gracefully.
Automate your infrastructure: Use infrastructure as code (IaC) tools like Terraform to automate the deployment and management of your ECS infrastructure. This ensures consistency, reduces human error, and makes your infrastructure easily reproducible.
Utilize ECS features for deployment: Take full advantage of ECS features like service discovery, which makes it easier to connect the containers within your environment, and task definitions to control deployments and rollbacks precisely.
Optimize costs: Regularly review your resource utilization and optimize container sizing to manage costs effectively. AWS offers a range of pricing options and services that can help manage and reduce your spending. Twice now, one of our engineers has noticed a huge upward trend in AWS cost, and introduced optimizations that saved us hundreds of thousands of dollars annually.
By adhering to these best practices, we ensure that our ECS-hosted applications at Graphite remain robust, secure, and ready to scale. In the final section, I'll wrap up with a summary of our journey and the actionable takeaways that you can apply to your own projects.
Our journey at Graphite to find the optimal hosting solution for our Next.js application led us down a path filled with technical and strategic considerations. Our final landing spot, AWS ECS, provided the sweet spot of performance, control, and scalability—aligning perfectly with our growth trajectory and technical needs. Here are the actionable takeaways from our experience:
Evaluate your requirements: Carefully assess your current and future infrastructure needs before committing to a migration or a new hosting strategy.
Don’t fear the container: Embrace containerization for its benefits in consistency, scalability, and performance. With the right expertise, containers can be a game-changer for your deployment processes.
Invest in automation: Leverage IaC for repeatable, reliable deployment processes. Automation isn't just a time-saver; it's a critical component of modern, scalable infrastructure.
Stay cost-aware: Keep an eye on your usage and optimize costs without compromising on necessary resources.
Continuous learning: The tech landscape is constantly evolving and it’s crucial to not stay locked-in to previous strategies based on sunk-cost fallacies. Try to be flexible and adapt as new technologies emerge.
As a final note, while the above approach has worked well for Graphite, the right solution is always context-dependent, and your mileage may vary. Hopefully this is helpful to teams considering the best way to host their Next.js applications - let us know if you find our learnings useful!