Blog v3: simplifying and improving my blog
I deliberately built a complex AWS ecosystem for this blog to play and learn. Now I'm tearing it down. Here's why, and where things stand.
For the full context of v1 and v2, see the original article: How I Decided on the Technology Behind the Blog.
1. The original goal
When I created this blog, the goal was very specific:
Use as many AWS services as possible, practice with them on a real project, at near-zero cost.
And I delivered. For years, the blog was my personal AWS lab: I deployed Amplify, migrated to Terraform, used CodePipeline and CodeBuild for CI/CD, set up a full serverless backend with API Gateway, Lambda, Step Functions, DynamoDB, SES, SNS, SSM Parameter Store and EventBridge for contact forms, feedback, and email subscriptions. I ended up managing exactly 7 repositories related to the blog, essentially zero cost thanks to the free tier and AWS credits.
Every service I added had a clear purpose: not just reading about it in theory, but building it myself in production, integrating it, breaking it, understanding its real limits, and learning from the experience. That’s what I was after.
At some point, that question came up:
Do I keep maintaining all this out of inertia, or do I simplify and keep only what still makes sense?
2. What was there: the inventory
To understand the actual scale, here’s the before state.
Archived repositories
| Repository | Description | Visibility |
|---|---|---|
| blog-legacy-web-code | Original Jekyll source code | Public |
blog-legacy-web-code-pipeline | CI/CD pipeline for web code deployment | Private |
blog-legacy-frontend-infrastructure | Blog Terraform infrastructure, mainly S3 and CloudFront | Private |
blog-legacy-frontend-infrastructure-pipeline | CodePipeline pipeline for infrastructure deployment | Private |
blog-legacy-contact-service | AWS SAM: Step Functions, Lambda, DynamoDB, SES and supporting services for the contact form | Private |
blog-legacy-feedback-service | AWS SAM: Step Functions, Lambda, DynamoDB, SES and supporting services for the feedback form | Private |
| blog-legacy-backend-infrastructure | CDK TypeScript: API Gateway, Lambda, DynamoDB and backend resources for comments and subscriptions | Public |
Total: exactly 7 repositories.
Removed AWS services
The hosting layer stayed, but everything managed inside AWS for backend and CI/CD is gone.
These are the main services I stopped using for the blog:
- Amazon CodePipeline: pipelines for frontend, infrastructure, contact service, and feedback service
- AWS CodeBuild: Terraform and SAM builds tied to those pipelines
- Amazon API Gateway: REST endpoints for the forms
- AWS Lambda: functions to process contact, feedback, and subscriptions
- AWS Step Functions: workflow orchestration for the form flows
- Amazon DynamoDB: tables for contacts, feedback, and subscribers
- Amazon SES: email notifications
- Amazon SNS: messaging and auxiliary notifications
- AWS Systems Manager Parameter Store: configuration and parameters used by the services
- Amazon EventBridge: events and automations related to the backend
- Amazon EventBridge Pipes: connections between event sources and processing services
- AWS CDK / CloudFormation: backend stacks and associated resources
- IAM roles and policies: permissions required by all those services
- Amazon CloudWatch Logs: logs from Lambdas, builds, Step Functions and other components
Not all of these carried the same weight, but all of them were part of the service ecosystem. The blog reached over 15 AWS resources when counting the full backend, CI/CD, observability, configuration, and permissions stack.
All of it was deployed, all of it worked, all of it was serverless, and all of it cost zero euros. I removed it anyway.
3. Why remove something that works
Each of those resources existed for a valid reason at the time. The problem is that reason no longer applies.
I knew those services. I’d built them, integrated them, broken them, and written about them here on my blog. Once you’ve done that, keeping the infrastructure running adds nothing new. It’s just something else to maintain, and in architecture, what delivers no value is debt. Maybe not right now, but it will be eventually. That’s certain.
Knowing what to remove, and when, is just as important as knowing what to build.
The mindset shift is this: before, I chose services to learn; now, I choose them to solve. The simplest solution that works wins, regardless of whether it comes from AWS or anywhere else.
4. The current architecture: v3
4.1. Hosting: keeping what made sense
The hosting layer barely changed from v2. S3 + CloudFront is still the right choice for a static site: no servers to manage, global cache distribution, HTTPS, and near-zero cost.
flowchart LR
A(User) --> B(Route53)
B --> C(CloudFront)
C --> F("CloudFront Functions")
C --> D(S3)
The AWS services currently running for the blog:
- S3: static site generated by Jekyll
- CloudFront: global CDN, per-resource-type caching, HTTPS
- CloudFront Functions: URL rewriting so Jekyll works correctly behind CloudFront
- ACM: SSL certificate for the domain
- Route53: DNS
Total: 5 AWS services maintained.
The relevant change in this layer was replacing Lambda@Edge with CloudFront Functions. For this use case, simple URL rewriting, I didn’t need the extra flexibility of Lambda@Edge. CloudFront Functions fits better: less complexity, fewer moving parts, and more than enough to solve the problem.
What also changed is how I manage it. In v2, the blog had its own dedicated infrastructure repository. In v3, the infrastructure lives inside a shared Terraform project I use to deploy all my static sites. Each site has its own configuration folder but shares the same reusable base.
1
2
3
4
5
6
7
deploy-websites/
├── s3.tf
├── cloudfront.tf
├── cert_generation.tf
├── route53.tf
└── projects/
└── blog/ ← blog-specific variables
One shared repository instead of a separate infrastructure repository per project.
4.2. CI/CD: from CodePipeline to GitHub Actions
Before, in v2:
flowchart LR
A(GitHub push) --> B(Amazon CodePipeline)
B --> C(AWS CodeBuild)
C --> D(Terraform apply)
D --> E(S3 + CloudFront)
Now, in v3:
flowchart LR
A(GitHub push) --> B(GitHub Actions)
B --> C(Terraform apply / Jekyll build)
C --> D(S3 + CloudFront)
The result is identical. GitHub Actions lives in the same repository as the code, is more straightforward to maintain, eliminates all pipeline management in AWS, and is free.
Note: The change wasn’t driven by cost. My v2 solution wasn’t free, but it ended up costing me zero euros. The main reason was operational simplicity: fewer pieces, fewer repositories, fewer pipelines, and less surface to maintain.
4.3. Backend: removed
The contact and feedback forms existed on the blog, but in practice nobody used them, and I wasn’t trying to change that either. Email subscriptions were different: there were subscribers stored in DynamoDB, but I never got around to implementing automated sending, and honestly had no intention of doing so. I decided to delete that data and avoid maintaining a half-built feature that wasn’t delivering real value.
Maintaining API Gateway, Lambda, Step Functions, DynamoDB, SES, SNS, SSM Parameter Store, EventBridge, EventBridge Pipes, permissions, logs, and associated deployments for features that weren’t delivering real value made no sense. Yes, it was serverless and cost me zero euros with no usage — but still.
The fact that something has no cost doesn’t mean it has no impact. Every resource adds configuration, permissions, logs, dependencies, and potential failure points. Once it stops delivering value, all it does is increase the complexity of the solution and the effort required to understand and maintain it.
Comments still work via giscus and GitHub Discussions, an external solution with no infrastructure of its own to manage.
5. Full redesign and AI assistants
The simplification wasn’t only technical. The way I build and maintain the blog also changed.
v3 includes a complete UX redesign: new navigation structure, reorganized content, and a cleaner overall experience. I actually started these changes almost 9 months ago, motivated by the release of Kiro, but left it unfinished because it didn’t fully convince me and I ended up focusing on other things. It was time to pick it back up and finally close out this new v3 of the blog.
And of course, AI assistants have also changed how I work quite a bit. They help me organize ideas, review structure, improve writing, develop blog features, generate drafts to iterate on, and spot improvements that used to take much longer to surface.
AI doesn’t do everything, but it does speed things up. It’s an incredible amplifier — just make sure you stay in control and understand what it’s doing.
I don’t want AI to do the work for me — what would be the point? I want it to help me do it better and faster, but the blog should remain mine. It’s my space to play, learn, and share what I discover. The day that stops being true, the blog will have lost most of its reason to exist.
I have a post about what I learned using a coding assistant after six months of heavy use if you want to see how I apply it in practice.
6. Before vs now
| Aspect | v2 | v3 |
|---|---|---|
| Repositories | Exactly 7 blog-related repositories | 2 repositories: blog-web-code and deploy-websites |
| CI/CD | CodePipeline + CodeBuild | GitHub Actions |
| Hosting | S3 + CloudFront + Lambda@Edge, with dedicated Terraform repository | S3 + CloudFront + CloudFront Functions, inside a shared Terraform module |
| Backend | API Gateway + Lambda + Step Functions + DynamoDB + SES + SNS + SSM Parameter Store + EventBridge + EventBridge Pipes | Removed |
| AWS services in use | Over 15 services involved in total | 5 services: S3, CloudFront, CloudFront Functions, Route53, and ACM |
| Infrastructure as code | CDK + SAM + Terraform | Terraform |
| UX and content | Original design, organic growth | Full redesign |
| Creative process | Mostly manual | Supported by AI assistants |
| Selection criteria | More AWS services = more learning | The simplest solution that solves the problem |
7. Closing thoughts
Building that full ecosystem was a deliberate decision, and it was worth it. Tearing it down has also been deliberate, and so far I have no regrets.
The difference between the two phases isn’t technical. It’s about criteria: early on, I chose services to learn. Now I want simplicity, efficiency, and focus.
That’s probably one of the clearest signs of growth in architecture: not adding more pieces, but knowing how to simplify and which ones you no longer need.
Before, I learned by adding services. Now, I learn by simplifying solutions.
