[ ] July 29, 2026
Casiana

Off the Edge: How AWS costs were cut by ~63% by moving off serverless

How a high-traffic Next.js app was migrated from a serverless AWS stack (Lambda@Edge, CloudFront, S3) to Elastic Beanstalk, cutting infrastructure costs by roughly 63%.

Off the Edge: How AWS costs were cut by ~63% by moving off serverless

Image source: Pexels

Understanding why serverless stopped paying off

Serverless is an easy sell at the start of a project. No servers to patch, automatic scaling, and pricing that’s billed per request instead of per hour. But as one of our client’s Next.js applications grew, that same pricing model quietly turned into its biggest cost driver.

Moving the project off serverless cut AWS costs by roughly 63% — with the same traffic and the same codebase.

Recognizing when a serverless architecture stops fitting a project’s traffic pattern is the first step to fixing the cost curve before it fixes itself.

Dancing with Lambda@Edge

The tango being danced was with the existing architecture. The app was deployed using the Serverless Framework’s sls-next component, which split it across four separate Lambda functions: one for server-rendered pages, one for API routes, one for image optimization, and one for background regeneration, all sitting behind CloudFront and a Lambda@Edge function.

Every page render, every API call, every regenerated page was its own billable invocation. At low traffic, that’s invisible. At scale, with thousands of dynamically rendered routes, it adds up fast, and Lambda@Edge carries a pricing premium on top of standard Lambda rates.

Not only did this drive up the bill, it also added operational friction: cold starts during traffic spikes, and a deploy process that had to sync serverless state files to and from S3 just to keep four separate Lambda versions in sync.

Moving beyond the edge

Some teams try to fix serverless cost problems by tuning memory allocations function-by-function, or micro-optimizing cold starts. That approach can help at the margins, but it doesn’t fix the underlying issue: you’re still paying for four independently-billed services instead of one.

Consolidating four billing surfaces (default Lambda, API Lambda, image Lambda, regeneration Lambda) into a single EC2 fleet removed the Lambda@Edge premium entirely.

Instead of micro-optimizing the serverless stack further, we changed the platform underneath the app. The Next.js code barely changed, the build still ran with next build and served with next start. What changed was where it ran.

Emphasizing a simpler architecture

A simpler, more predictable architecture became the foundation of the migration. The stack moved from Lambda/CloudFront/S3 to AWS Elastic Beanstalk, EC2 instances behind an Application Load Balancer, letting a single long-running Node process absorb the work that used to be split across four Lambdas.

Flat, continuous compute replaced per-invocation billing for SSR renders, API calls, image transforms, and page regeneration combined.

The traditional “ship it to the edge and hope it scales cheaply” mentality was replaced with a model where capacity is planned, not billed by the request.

Do’s for the migration

  1. Move the platform, not the app. The Next.js codebase stayed intact and only the deployment target changed — this kept the migration low-risk and fast to validate.
  2. Replace edge logic with load-balancer and web-server rules. HTTP→HTTPS redirects moved to an ALB listener rule; header rewrites and sitemap proxying moved to an nginx config via an EB platform hook.
  3. Instrument the new instances from day one. The CloudWatch agent ran on EC2 to track memory usage immediately, so instance sizing was based on real data instead of guesswork.

Don’ts for the migration

  1. Don’t assume “serverless” and “cheap” are the same thing — check whether the actual traffic shape (steady vs. spiky) matches the pricing model being paid for.
  2. Don’t carry over serverless-era deployment complexity (like syncing state files to S3) into the new setup, a single eb deploy should replace it, not sit alongside it.

Do’s for the Engineering Team

  1. Audit invocation-heavy workloads first. The project’s regeneration Lambda, firing across thousands of page combinations, was the clearest signal that per-invocation billing didn’t fit the traffic pattern.
  2. Right-size compute with real monitoring, not defaults. Use CloudWatch (or equivalent) from the first deploy, not after the bill arrives.
  3. Keep the redirect and rewrite logic centralized. Moving those rules to the ALB and nginx, instead of scattering them across edge functions, made the whole request path easier to reason about.

Don’ts for the Engineering Team

  1. Don’t over-provision Lambda memory just to buy faster cold starts. That’s a symptom of the pricing model fighting the traffic pattern, not a fix for it.
  2. Don’t treat the migration as “lift and shift” only. Take the chance to simplify the request path (fewer services, fewer billing surfaces) rather than just relocating the same complexity.

Tailoring the approach

The right platform depends entirely on the project’s traffic shape. Serverless still makes sense for spiky, unpredictable, or low-volume workloads, where paying only for what you use beats paying for idle capacity.

Workloads with steady, high-volume, dynamically rendered traffic are the ones most likely to see serverless costs outpace server costs.

Conclusion

In conclusion, cutting cloud costs isn’t about chasing the latest deployment trend, it’s about matching infrastructure to actual traffic. Overcoming per-invocation billing and Lambda@Edge premiums meant moving beyond the “deploy to the edge” mentality and back to a simpler, flatter architecture.

By reassessing whether serverless still fit the project’s traffic shape, AWS costs dropped by roughly 63% without touching the application code. For workloads that look like this one, steady, high-volume, and dynamically rendered, it’s worth running the same math before the next renewal.