Personal NextJS Web Site on AWS Lambda@Edge

Personal NextJS Web Site on AWS Lambda@Edge

I’ve been planning to bring up my blog or even better the entire website for quite a while. And one may say, it’s a matter of minutes in today’s day and age

I’ve been planning to bring up my personal blog for quite a while or even better the entire website with some demo AI projects (in the future). And one may say, it’s a matter of minutes in today’s day and age given how advanced tech has become. But, as a systems/cloud architect with a robust DevOps background and platform engineering skills, I wanted to learn a bit more about the front end and get into that game for what it's worth. Without even stressing enough the importance of flexibility of having your own tech stack under control.

Cause in part I've been curious as if what they say is true that it's become insanely simple to have your website running. I've also wanted to feel GitHub Copilot in practice to see its potential in something like managing your entire full stack including infrastructure as being scared that people are claiming that they're quitting tech because of a threat coming from AI systems like Devin doing all software development job for you.

After a month of research and development, I've finally managed to bring a website to life entirely hosted on AWS using a modern Jamstack tech stack. I think that with AI, even though some things are becoming incredibly easy like targeted autocomplete, you'd still need to put quite a bit of effort into learning a few software architecture/engineering skills to put everything together in a scalable and reliable way.

Approaching a frontend hosting problem

Since I’m an AWS guy (mostly), I’ve chosen AWS as a hosting platform for a website. While AWS has amazing offerings for website-specific hosting (Amazon LightsailAWS Amplify, Amazon S3 (static pages)), I did look for something more efficient in terms of cost, performance, and dev experience.

Initially, I though that static hosting on S3 would be enough for something like a blog post, but I also kept in mind that eventually, I’d want to bring more functionality and even client-side logic to the website involving some streaming for a ChatGPT chatbot-like experience. So, I knew that I was aiming higher than just a S3/CloudFront but I also didn’t want to make it insanely complex like with EKS/ECS/K8S with lots of infra to manage and a starting bill of $75/m to cover just for the cluster. And as I continued to look for the options I found SST.

After getting familiar a little with Pulumi and CDK while having substantial experience with Terraform, CDK-like experience is what I thought I wanted for my tech stack for a smoother development experience. And I guess whoever chooses to manage their infrastructure along with a full-stack application using the same technology/language (like a typescript), they’re making a great point, I get it now. But of course, I understand the caveats as well of having a modern stack controlled by a small group of people or even a single person, is that there are so many things you’ve to keep in mind despite the framework taking care of many things for you. And as you scale and grow bringing more people in, the responsibility boundaries will need to become clearer and simpler of what they’re going to manage and how.

First attempt to host it as a static website

While having somewhat experience with Frontend frameworks (such as VueJS) previously, I wanted to try something new, and my choice had fallen onto NextJS which Vercel excellently standing behind. I’m also aware of Astro and Remix, which could be quite competitors to one, but it felt like NextJS is an appropriate introduction to a fast and modern frontend stack.

I quickly learned that NextJS could be statically built provided you’re careful not to use all available features and easily hosted on S3 as a static website that as many say is a better alternative to Gatsby and Hugo. That is what I thought I wanted to do accompanied with markdown pages transformed into the HTML pages during the build process using MDX.

I found this amazing blog starter example in Vercel’s templates that uses Contentlayer under the hood. The content layer is something that will do MDX transformation for you during the build into the type-safe objects so that you can use it in your TSX code for the layout.

This worked well to a degree and I was able to host it in S3 with CloudFront connected using a static website CDK template. And the framework had managed to configure an SSL certificate for the targeted custom domain name I had under the control of Route53 (iliazlobin.com).

image0

The user experience of such a site as you can imagine is insanely fast, provided you rebuild your entire website every time there’s new piece of content. I even started planning for a build pipeline assisted with a AWS Bedrock LLM to read-proof and grammar-correct my writing.

image1

But what I didn’t like about this hosting platform is that there are so many limitations on what it’s capable of out of the box.

For instance, there’s no way to set up a middleware to handle redirects (say from https://iliazlobin.com to https://iliazlobin.com/blog, unless it’s a client’s logic perhaps), and you’ll have to set up lambda edge or CloudFront function to rewrite routes for extension-less queries (say a client requests https://iliazlobin.com/blog/page you want to service https://iliazlobin/blog/page.html for him, without that additional edge logic it’s not possible with just CloudFront alone.

So, at that point, I knew, I didn’t want to support this mechanism every time I deploy something new, and I wanted more functionality out of NextJS, so I kept looking for server hosting options.

Considering Kubernetes for a server hosting

I gave a couple of thoughts to having a minimal EKS cluster for server hosting, similar to what I did with N8N (open-source workflow automation tool) hosting in GCP on GKE Autopilot. I talk about it in the most watched of my videos so far.

But in the case of AWS, the cluster isn’t covered with a Free Tier, so you have to pay $75/m out of your pocket to just cover the K8S control plane costs without even mentioning an additional operational effort despite the fact how much I love Kubernetes, it’s still my time, right?!

So I thought, unless there’s enough reason to have Kubernetes under the hood, meaning, I’ve got enough workload running and users using a thing, there’s no point to host it in a Kubernetes at this time with always an option to move it or pieces of it to Kubernetes when a need be.

Where AWS stands in the Compute Edge era

I’ve been exploring serverless for quite a while now, and I should say, it’s a very promising venture, especially for such a thing as Frontend. True serverless allows people to do more with less management involved. Various SaaS vendors offer serverless options and use serverless themselves under the hood while taking a big margin profit for the services they offer. And that’s because they don’t need a big team doing infrastructure and operations work for them as it’s been taken care of by tech giants and and specialized serverless providers.

There are amazing serverless platforms out there such as VercelCloudflare PagesNetlify you can go a long way with to host your entire stack. They offer a seamless website hosting experience with server side rendering and provide their edge compute infrastructure and serverless persistent options (such as Postgres and KV storages) for a complete picture of your full stack application. They literally take your code and deploy it across their distributed infrastructure so that you benefit the most from it expressed in a great user experience by being super close to the computer and data.

But these things come at a premium you’re going to be paying for distributed infrastructure, smooth experience, and data proximity achieved with a massive replication factor. And it’s not necessarily a bad thing to account for, some people will take it, and I get it.

My point in choosing AWS has been that at this point (March 2024) it’s the only cloud provider offering edge computing off of their edge network. Moreover, the number of their points of presence is constantly growing which is currently present in 100+ cities across 50+ countries. And to top it all up, AWS offers Wavelength which is the same edge computing but offered on 5G infrastructure in the most populated 200+ cities in the world. You heard it right, it’s transparent for an end user, all you have to do is request the feature, and continue operating with the same API (lambda@edge) to control it.

SST for the ease of controlling your full stack in AWS

After getting excited about it, I started looking for easy implementation options and luckily stumbled upon SST. I had some experience with AWS CDK at that point so came prepared with a better level of abstraction to manage your AWS infrastructure. It excited me right off the bat as the instructions were so on point with what I was trying to achieve. The doc has been able to effectively onboard me with the most popular design choices amongst frameworks and architectural practices of what people have been going through for years.

Along the way, I’ve learned about other amazing frameworks such as Remix, Astro, and Svelte, that at times even outperform NextJS cause being built later on the learnings of the prior. I still chose to stick with NextJS as being the most popular choice and having a large community and senior companies backing it up.

I tried using SST for two things when it comes to website hosting, I tried it for static hosting (to compare with my previous experience with pure CDK) and I tried it for NextJS of course. It amazed me how simple it is to start off, define your stack, and deploy it. The authors of the framework went up and beyond to bring the best developer experience ever. Not only does the framework take care of your entire AWS infrastructure stack through CDK/CloudFormation for multiple environments/stacks, it’s also providing you with the best developing/debugging experience ever. You can live-debug your code in the cloud without leaving your IDE! They’ve achieved that thanks to the lambda proxies and WebSocket IoT capability over the WebSocket available for every AWS customer to connect to the cloud without having to have a publicly available IP reserved for that matter. They’re also trying to address the most painful point in managing AWS which is permission management by introducing the concept of resource binding. Once bound, your compute unit is going to be provided with IAM permissions following the principle of the least privilege (in most common cases) so that you don’t have to go through the full cycle of determining the minimum permission for your thing.

The framework provides just enough abstractions (CDK constructs built on top of) to instantiate your initial stack and move along. They have a good starting standalone example leveraging npm packages (nodejs mono repo) I recommend using for a project being scalable and managed with a limited number of hands. There are a couple of software architecture decisions (DDD, pnpm workspace/packages, aliases, etc) I had to make after experimenting with it a little that allowed me to have a scalable project design to grow in any sensible direction. And they also have plenty of examples that will support you on the journey of adoption.

There are caveats you’ve to be aware of if you’re aiming this thing for a bigger reason, I can definitely fill you in as having plenty of experience with AWS prior. Even though they’re trying to follow the best security practices, at times it’s not enough or even not in place the way they tie up the components. At times, you may observe admin permissions are given (as with the console role), it could be that they aren’t using recommended approaches (legacy identities instead of origin access controls).

Once you start optimizing your infra you’ll start noticing that some configuration is missing in the exposed construct parameters so you have to find workarounds. One of the recent examples I had to go through was to add Lambda Insights support to an image optimizer function. Unfortunately, you can’t do it through the high-level configuration element at the moment, so I had to hack it through the plan adjustment:

transform: (args: Plan) => {
  // logger.info('transforming cdk plan: ', args)
  const imageOptimizer = args.origins?.imageOptimizer
  // logger.debug('imageOptimizer: ', imageOptimizer)
  if (
    imageOptimizer.type === 'function' ||
    imageOptimizer.type === 'image-optimization-function'
  ) {
    console.debug(
      'changing image optimizer function properties to include lambda insights layer',
    )
    const layers = imageOptimizer.function.layers || []
    const funcProps = imageOptimizer.function as SsrFunctionProps

    // logger.debug('funcProps.timeout: ', funcProps.timeout) // undefined
    // const duration: Duration = '10 seconds'

    const newProps = {
      ...funcProps,
      layers: [...layers, lambdaInsightsLayer],
      // timeout: duration, // Trace: TypeError: props.timeout.toSeconds is not a function
    }
    imageOptimizer.function = newProps
  }

I’ve also discovered a couple of bugs along the way in an inability to set up a timeout for this image optimizer lambda function. This has to do with a series of property overloads they’re doing without rigorous testing of it.

I already also had to go through a npm package patching once to provide me with the ability to handle Distribution resources as a part of my stack for image hosting.

Once you start seeing this shortage, you realize, that’s where your and your team’s effort is going to go to make things correctly. But that’s a small price to pay for convenience and perhaps enhance your discipline. At the end of the day, if that’s what you need, the architectural pattern is great and well thought out, you can choose to continue on the journey by contributing to open source or forking it for your organization to branch off of.

Conclusion

All in all, I believe SST is an incredible framework allowing your team and just yourself to easily deploy a modern frontend application in an AWS cloud following best practices of managing your full stack. It educates you on modern development practices by demonstrating powerful capabilities and allows you to unlock the even better potential of controlling your entire application and infrastructure stacks together in a seamless way. I’ll have more to tell you about and demonstrate to you once I get familiar with nuances and come up with great examples of what’s possible using this piece of technology and others that could improve your architectural decision-making multiple times!