<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sohana Akbar</title>
    <description>The latest articles on DEV Community by Sohana Akbar (@sohanaakbar7).</description>
    <link>https://dev.to/sohanaakbar7</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3878706%2Fb026acb6-e832-44ba-9999-2c0e44f18218.jpg</url>
      <title>DEV Community: Sohana Akbar</title>
      <link>https://dev.to/sohanaakbar7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sohanaakbar7"/>
    <language>en</language>
    <item>
      <title>Cost of hosting a React app on AWS vs Vercel — real numbers</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Wed, 08 Jul 2026 16:10:05 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/cost-of-hosting-a-react-app-on-aws-vs-vercel-real-numbers-gc7</link>
      <guid>https://dev.to/sohanaakbar7/cost-of-hosting-a-react-app-on-aws-vs-vercel-real-numbers-gc7</guid>
      <description>&lt;p&gt;Let's cut through the marketing fluff and look at the actual numbers. If you're building a React app in 2026, the choice between AWS and Vercel often comes down to one question: how much are you willing to pay for convenience?&lt;/p&gt;

&lt;p&gt;Here's the honest breakdown.&lt;/p&gt;

&lt;p&gt;The quick verdict&lt;br&gt;
For a typical production React app with moderate traffic (35K visitors/month, 180GB bandwidth):&lt;/p&gt;

&lt;p&gt;Platform    Monthly Cost&lt;br&gt;
Vercel Pro  $20/month&lt;br&gt;
AWS (S3 + CloudFront)   $5–8/month&lt;br&gt;
AWS is 60–75% cheaper at this scale.&lt;/p&gt;

&lt;p&gt;But that $12–15/month difference comes with tradeoffs. Here's what you're really buying.&lt;/p&gt;

&lt;p&gt;Vercel's pricing model: the convenience tax&lt;br&gt;
Vercel's pricing looks simple at first glance:&lt;/p&gt;

&lt;p&gt;Hobby: Free (personal/non-commercial only)&lt;/p&gt;

&lt;p&gt;Pro: $20/user/month — includes 1TB bandwidth, 10M edge requests, 1M function invocations, 40 CPU hours&lt;/p&gt;

&lt;p&gt;Enterprise: Custom (starts around $20–25K/year)&lt;/p&gt;

&lt;p&gt;The catch: You get billed for overages once you exceed those limits. And the limits are per-billing-cycle, not cumulative, so they reset monthly.&lt;/p&gt;

&lt;p&gt;Resource    Included (Pro)  Overage rate&lt;br&gt;
Fast Data Transfer  1 TB    $0.15/GB&lt;br&gt;
Edge Requests   10M $2 per 1M&lt;br&gt;
Function Invocations    1M  $0.40 per 1M&lt;br&gt;
Active CPU Time 40 hours    $5/hour&lt;br&gt;
At scale, overages add up fast:&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
100K visitors, 2TB bandwidth, 50M edge requests&lt;/p&gt;

&lt;p&gt;Vercel Pro:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Base: $20&lt;/li&gt;
&lt;li&gt;Bandwidth overage (1TB extra): $150&lt;/li&gt;
&lt;li&gt;Edge requests overage (40M extra): $80&lt;/li&gt;
&lt;li&gt;Total: $250/month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AWS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CloudFront (2TB): $170&lt;/li&gt;
&lt;li&gt;S3: $5&lt;/li&gt;
&lt;li&gt;Lambda: $10&lt;/li&gt;
&lt;li&gt;Total: $185/month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You save: $65/month (26%)&lt;br&gt;
AWS pricing: pay for what you use&lt;br&gt;
AWS offers multiple ways to host a React app. The most cost-effective for static React apps?&lt;/p&gt;

&lt;p&gt;S3 + CloudFront (recommended for most React apps)&lt;br&gt;
For a static React app (no server-side rendering), this is the sweet spot:&lt;/p&gt;

&lt;p&gt;Component   Cost&lt;br&gt;
S3 storage  $0.023/GB/month&lt;br&gt;
S3 requests ~$0.005 per 10K GET requests&lt;br&gt;
CloudFront (CDN)    $0.085–0.12/GB data transfer&lt;br&gt;
Lambda@Edge (optional)  Pay per request&lt;br&gt;
Real-world example: A small SaaS app with 35K visitors and 180GB bandwidth:&lt;/p&gt;

&lt;p&gt;CloudFront: $3–5&lt;/p&gt;

&lt;p&gt;S3: $1–2&lt;/p&gt;

&lt;p&gt;Lambda: $0.50&lt;/p&gt;

&lt;p&gt;Total: $5–8/month&lt;/p&gt;

&lt;p&gt;For a portfolio or low-traffic site, you can stay within AWS Free Tier entirely.&lt;/p&gt;

&lt;p&gt;AWS Amplify (the Vercel-like experience)&lt;br&gt;
If you want Git-based deployments and preview URLs without the complexity, AWS Amplify is Vercel's direct competitor:&lt;/p&gt;

&lt;p&gt;Free tier: 1,000 build minutes/month, 15GB bandwidth, 30K requests&lt;/p&gt;

&lt;p&gt;Beyond free tier: $0.01/build minute, $0.15/GB transfer, $0.023/GB storage&lt;/p&gt;

&lt;p&gt;MVP cost (low traffic with Cognito + RDS): ~$16–25/month.&lt;/p&gt;

&lt;p&gt;EC2 + CloudFront (for full-stack or SSR)&lt;br&gt;
If you need server-side rendering or custom backend logic:&lt;/p&gt;

&lt;p&gt;Component   Monthly Cost&lt;br&gt;
t4g.medium EC2  ~$24.53&lt;br&gt;
EBS storage ~$0.80&lt;br&gt;
ALB ~$16.50&lt;br&gt;
CloudFront  usage-based&lt;br&gt;
Total base  ~$54–63/month&lt;br&gt;
The multi-project math&lt;br&gt;
Here's where AWS really shines. I run 6 production SaaS apps:&lt;/p&gt;

&lt;p&gt;Platform    Cost&lt;br&gt;
Vercel: 6 apps × $20/month $120/month minimum&lt;br&gt;
AWS: All 6 apps combined    ~$20/month total&lt;br&gt;
That's &lt;strong&gt;$100+/month savings&lt;/strong&gt; — $1,200/year. Real money you can spend on marketing, hiring, or your own salary.&lt;/p&gt;

&lt;p&gt;Hidden costs and traps&lt;br&gt;
Vercel gotchas&lt;br&gt;
Hobby plan is non-commercial only — if you deploy client work, you're on Pro&lt;/p&gt;

&lt;p&gt;AI apps are expensive — streaming responses keep functions running longer (45-second stream = 45x cost of a quick API call)&lt;/p&gt;

&lt;p&gt;No gradual upgrade path — the jump from Pro to Enterprise is a cliff (from $20/user to ~$20K/year)&lt;/p&gt;

&lt;p&gt;Regional pricing varies — bandwidth costs $0.15–0.35/GB depending on region&lt;/p&gt;

&lt;p&gt;AWS gotchas&lt;br&gt;
Complex pricing — you need to understand S3, CloudFront, Lambda, and data transfer costs&lt;/p&gt;

&lt;p&gt;Learning curve — configuring IAM roles, CloudFront invalidations, and build pipelines takes time&lt;/p&gt;

&lt;p&gt;Surprise bills — set billing alerts from day one (stories of $10K+ surprise AWS bills aren't urban legends)&lt;/p&gt;

&lt;p&gt;Which should you choose?&lt;br&gt;
Choose Vercel if:&lt;br&gt;
You're building with Next.js (first-class support, zero config)&lt;/p&gt;

&lt;p&gt;You want preview deployments for every PR&lt;/p&gt;

&lt;p&gt;You hate infrastructure and want to focus on code&lt;/p&gt;

&lt;p&gt;Your traffic is predictable and within Pro plan limits&lt;/p&gt;

&lt;p&gt;You're willing to pay $20–150/month for convenience&lt;/p&gt;

&lt;p&gt;Choose AWS if:&lt;br&gt;
You have multiple projects (savings compound fast)&lt;/p&gt;

&lt;p&gt;You want cost predictability at scale&lt;/p&gt;

&lt;p&gt;You already use AWS services (Cognito, RDS, DynamoDB, SES)&lt;/p&gt;

&lt;p&gt;You have time to learn the basics of S3 + CloudFront&lt;/p&gt;

&lt;p&gt;You're building a SaaS where every dollar of margin matters&lt;/p&gt;

&lt;p&gt;Real numbers comparison&lt;br&gt;
Scenario    Vercel Pro  AWS (S3+CF) Savings&lt;br&gt;
Portfolio (low traffic) $20 ~$0–3 ~$17&lt;br&gt;
Small SaaS (35K visitors, 180GB)    $20 ~$5–8 ~$12–15&lt;br&gt;
Growing SaaS (100K visitors, 2TB)   ~$250   ~$185   ~$65&lt;br&gt;
6 production apps   $120+   ~$20    ~$100+&lt;br&gt;
Bottom line&lt;br&gt;
Vercel is the premium experience — you pay for simplicity, preview deployments, and Next.js optimization. It's worth it if your time is valuable and your traffic is predictable.&lt;/p&gt;

&lt;p&gt;AWS is the cost-effective choice — you trade setup complexity for significantly lower costs, especially as you scale or run multiple projects.&lt;/p&gt;

&lt;p&gt;For most solo developers and small teams, the answer is: start on Vercel (it's free), and move to AWS when the bill starts to hurt.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>infrastructure</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>CloudFront Invalidations from CI: Automate Cache Clears</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Tue, 07 Jul 2026 14:21:35 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/cloudfront-invalidations-from-ci-automate-cache-clears-1a8c</link>
      <guid>https://dev.to/sohanaakbar7/cloudfront-invalidations-from-ci-automate-cache-clears-1a8c</guid>
      <description>&lt;p&gt;If you've ever deployed a website to S3 and CloudFront, you know the frustration: you push your changes, wait a minute, refresh the page, and... still see the old version. 😅&lt;/p&gt;

&lt;p&gt;The problem is CloudFront caching—it's great for performance, but it means users can see stale content after you deploy updates . By default, CloudFront serves cached content until it expires (up to 24 hours!) .&lt;/p&gt;

&lt;p&gt;The solution? Automate CloudFront cache invalidation from your CI/CD pipeline. Let's break down why and how.&lt;/p&gt;

&lt;p&gt;Why Automate Invalidation?&lt;br&gt;
Doing invalidations manually through the AWS Console is:&lt;/p&gt;

&lt;p&gt;❌ Error-prone (forgetting to do it)&lt;/p&gt;

&lt;p&gt;❌ Slow (breaks your deployment flow)&lt;/p&gt;

&lt;p&gt;❌ Not scalable (as your team grows)&lt;/p&gt;

&lt;p&gt;Automating invalidation ensures users always see the latest content immediately after deployment .&lt;/p&gt;

&lt;p&gt;The Core Command&lt;br&gt;
Whether you use GitHub Actions, Jenkins, or AWS CodePipeline, the underlying AWS CLI command is the same:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
aws cloudfront create-invalidation \&lt;br&gt;
  --distribution-id EDFDVBD6EXAMPLE \&lt;br&gt;
  --paths "/&lt;em&gt;"&lt;br&gt;
The --paths parameter supports wildcards—/&lt;/em&gt; invalidates everything, while /index.html or /assets/* invalidates specific paths .&lt;/p&gt;

&lt;p&gt;CI/CD Integration Patterns&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;GitHub Actions
This is probably the most popular approach. In your workflow YAML, add a step to create the invalidation after your deployment:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;yaml&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;name: Invalidate CloudFront
run: |
aws cloudfront create-invalidation \
  --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
  --paths "/*"
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
Store your distribution ID and AWS credentials as GitHub Secrets .&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;AWS CodePipeline with Lambda
CodePipeline can trigger a Lambda function after S3 sync, which then creates the invalidation :&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;python&lt;br&gt;
import boto3&lt;/p&gt;

&lt;p&gt;def lambda_handler(event, context):&lt;br&gt;
    cloudfront = boto3.client('cloudfront')&lt;br&gt;
    response = cloudfront.create_invalidation(&lt;br&gt;
        DistributionId='YOUR_DISTRIBUTION_ID',&lt;br&gt;
        InvalidationBatch={&lt;br&gt;
            'Paths': {&lt;br&gt;
                'Quantity': 1,&lt;br&gt;
                'Items': ['/*']&lt;br&gt;
            },&lt;br&gt;
            'CallerReference': str(event.get('timestamp', 'manual'))&lt;br&gt;
        }&lt;br&gt;
    )&lt;br&gt;
    return response&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Terraform
The aws_cloudfront_distribution resource doesn't natively support invalidation. However, you can use local-exec provisioner as a workaround :&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;hcl&lt;br&gt;
resource "aws_cloudfront_distribution" "s3_distribution" {&lt;br&gt;
  # ... configuration ...&lt;/p&gt;

&lt;p&gt;provisioner "local-exec" {&lt;br&gt;
    command = "aws cloudfront create-invalidation --distribution-id ${self.id} --paths '/*'"&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
⚠️ Note: This runs after resource creation, but for every terraform apply, consider using a null_resource with triggers for more control.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Jenkins Pipeline
Similar to GitHub Actions—just call the AWS CLI from your Jenkinsfile:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;groovy&lt;br&gt;
stage('Invalidate Cache') {&lt;br&gt;
    steps {&lt;br&gt;
        sh '''&lt;br&gt;
            aws cloudfront create-invalidation \&lt;br&gt;
              --distribution-id ${CLOUDFRONT_DIST_ID} \&lt;br&gt;
              --paths "/*"&lt;br&gt;
        '''&lt;br&gt;
    }&lt;br&gt;
}&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Package.json Scripts
For smaller projects, you can chain commands in npm scripts :&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;json&lt;br&gt;
{&lt;br&gt;
  "scripts": {&lt;br&gt;
    "build.prod": "ng build --prod --aot",&lt;br&gt;
    "aws.deploy": "aws s3 sync dist/ s3://&lt;a href="http://www.mywebsite.com" rel="noopener noreferrer"&gt;www.mywebsite.com&lt;/a&gt; --delete",&lt;br&gt;
    "aws.invalidate": "aws cloudfront create-invalidation --distribution-id [ID] --paths /",&lt;br&gt;
    "deploy": "npm run build.prod &amp;amp;&amp;amp; npm run aws.deploy &amp;amp;&amp;amp; npm run aws.invalidate"&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
IAM Permissions&lt;br&gt;
Your CI user needs at minimum:&lt;/p&gt;

&lt;p&gt;json&lt;br&gt;
{&lt;br&gt;
  "Version": "2012-10-17",&lt;br&gt;
  "Statement": [{&lt;br&gt;
    "Effect": "Allow",&lt;br&gt;
    "Action": ["cloudfront:CreateInvalidation"],&lt;br&gt;
    "Resource": "*"&lt;br&gt;
  }]&lt;br&gt;
}&lt;br&gt;
If you need to wait for completion (e.g., for testing), add cloudfront:GetInvalidation .&lt;/p&gt;

&lt;p&gt;Best Practices &amp;amp; Considerations&lt;br&gt;
Don't invalidate everything every time. If only index.html and main.js change, invalidate just those paths to be more efficient .&lt;/p&gt;

&lt;p&gt;Cache invalidation costs money. The first 1,000 paths per month are free; after that, it's charged . Invalidate with intention.&lt;/p&gt;

&lt;p&gt;Consider using versioned filenames (e.g., main.a1b2c3.js) so you can keep caching forever and invalidate only the entry point. This is how modern frameworks roll.&lt;/p&gt;

&lt;p&gt;Wait for completion? Some plugins (like ember-cli-deploy-cloudfront) offer a waitForInvalidation flag, but this can take several minutes—only use it when absolutely necessary .&lt;/p&gt;

&lt;p&gt;Real-World Example&lt;br&gt;
A typical CI/CD flow looks like this :&lt;/p&gt;

&lt;p&gt;Developer pushes code to GitHub&lt;/p&gt;

&lt;p&gt;GitHub Actions/AWS CodePipeline triggers build&lt;/p&gt;

&lt;p&gt;Build assets are uploaded to S3&lt;/p&gt;

&lt;p&gt;CloudFront invalidation runs automatically&lt;/p&gt;

&lt;p&gt;Users see fresh content instantly&lt;/p&gt;

&lt;p&gt;No manual steps. No stale cache. 🚀&lt;/p&gt;

&lt;p&gt;Summary&lt;br&gt;
CloudFront invalidations are a necessary evil when deploying sites with a CDN. The good news is that automating them is straightforward with any CI/CD tool—GitHub Actions, AWS CodePipeline, Jenkins, or Terraform.&lt;/p&gt;

&lt;p&gt;Choose the approach that fits your stack, add the invalidation step to your pipeline, and never think about stale cache again.&lt;/p&gt;

&lt;p&gt;What's your preferred way to handle CloudFront invalidations? Drop a comment below!&lt;/p&gt;

</description>
      <category>automation</category>
      <category>aws</category>
      <category>cicd</category>
      <category>devops</category>
    </item>
    <item>
      <title>Deploying Preview Environments on AWS with PR Labels</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Mon, 06 Jul 2026 14:38:59 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/deploying-preview-environments-on-aws-with-pr-labels-3hoa</link>
      <guid>https://dev.to/sohanaakbar7/deploying-preview-environments-on-aws-with-pr-labels-3hoa</guid>
      <description>&lt;p&gt;Preview environments have become an essential part of modern development workflows. They allow teams to review changes in a live environment before merging code, significantly improving collaboration and reducing bugs.&lt;/p&gt;

&lt;p&gt;In this article, I'll walk you through setting up preview environments on AWS that are triggered by PR labels. This approach gives you complete control over your infrastructure while keeping costs low and workflows simple.&lt;/p&gt;

&lt;p&gt;Why Preview Environments?&lt;br&gt;
Preview environments (also called review apps) are temporary deployments of your application from pull requests. They let teammates, product managers, and QA teams visually review changes before they hit production .&lt;/p&gt;

&lt;p&gt;The benefits are clear:&lt;/p&gt;

&lt;p&gt;Faster feedback loops – reviewers see exactly what changed&lt;/p&gt;

&lt;p&gt;Reduced risk – issues are caught before merging&lt;/p&gt;

&lt;p&gt;Better collaboration – non-technical stakeholders can participate in reviews&lt;/p&gt;

&lt;p&gt;Confidence in changes – you can run E2E tests against real infrastructure &lt;/p&gt;

&lt;p&gt;The Label-Based Approach&lt;br&gt;
Using PR labels to trigger preview deployments is a pattern that's gained significant traction. Here's why it works so well:&lt;/p&gt;

&lt;p&gt;Intent-based activation – you choose when to spin up an environment&lt;/p&gt;

&lt;p&gt;Automatic cleanup – removing the label or closing the PR tears everything down&lt;/p&gt;

&lt;p&gt;Team-friendly – no complex commands required, just add a label&lt;/p&gt;

&lt;p&gt;As one popular implementation describes it: "You can manage preview environments by adding or removing the pullpreview label on your Pull Requests" .&lt;/p&gt;

&lt;p&gt;Architecture Overview&lt;br&gt;
The architecture depends on your specific needs, but a typical setup includes:&lt;/p&gt;

&lt;p&gt;Option 1: Simple VM-based (using PullPreview)&lt;/p&gt;

&lt;p&gt;This approach provisions AWS Lightsail instances for each preview environment . Each instance runs Docker Compose to spin up your application and its dependencies. It's simple, cheap, and works with any app that can be containerized.&lt;/p&gt;

&lt;p&gt;Option 2: Infrastructure as Code (AWS CDK)&lt;/p&gt;

&lt;p&gt;For more complex applications, you can use AWS CDK to define your infrastructure in TypeScript. This gives you more control and enables sophisticated setups with:&lt;/p&gt;

&lt;p&gt;CloudFront CDN in front of S3 buckets&lt;/p&gt;

&lt;p&gt;RDS databases with proper lifecycle management&lt;/p&gt;

&lt;p&gt;VPC configurations and security groups &lt;/p&gt;

&lt;p&gt;Option 3: Kubernetes with GitOps&lt;/p&gt;

&lt;p&gt;Larger teams might use Kubernetes with ArgoCD's ApplicationSet and PullRequest generator. This approach creates isolated namespaces per PR, with full service mesh capabilities .&lt;/p&gt;

&lt;p&gt;Implementation: PullPreview Approach&lt;br&gt;
Let me show you the simplest way to get started, using the PullPreview GitHub Action.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set Up AWS Credentials
First, create an IAM user with Lightsail permissions. AWS doesn't provide a default Lightsail policy, so you'll need to create one :&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;json&lt;br&gt;
{&lt;br&gt;
    "Version": "2012-10-17",&lt;br&gt;
    "Statement": [&lt;br&gt;
        {&lt;br&gt;
            "Effect": "Allow",&lt;br&gt;
            "Action": "lightsail:&lt;em&gt;",&lt;br&gt;
            "Resource": "&lt;/em&gt;"&lt;br&gt;
        }&lt;br&gt;
    ]&lt;br&gt;
}&lt;br&gt;
Then store the access keys as repository secrets:&lt;/p&gt;

&lt;p&gt;AWS_ACCESS_KEY_ID&lt;/p&gt;

&lt;p&gt;AWS_SECRET_ACCESS_KEY&lt;/p&gt;

&lt;p&gt;AWS_REGION (e.g., us-east-1)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the Workflow File
Add .github/workflows/pullpreview.yml to your repository:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;yaml&lt;br&gt;
name: PullPreview&lt;br&gt;
on:&lt;br&gt;
  pull_request:&lt;br&gt;
    types: [labeled, unlabeled, synchronize, closed, reopened]&lt;/p&gt;

&lt;p&gt;jobs:&lt;br&gt;
  deploy:&lt;br&gt;
    permissions:&lt;br&gt;
      contents: read&lt;br&gt;
      deployments: write&lt;br&gt;
      pull-requests: write&lt;br&gt;
      statuses: write&lt;br&gt;
    runs-on: ubuntu-latest&lt;br&gt;
    timeout-minutes: 30&lt;br&gt;
    steps:&lt;br&gt;
      - uses: actions/checkout@v4&lt;br&gt;
      - uses: pullpreview/action@v5&lt;br&gt;
        with:&lt;br&gt;
          admins: your-github-username&lt;br&gt;
          compose_files: docker-compose.pullpreview.yml&lt;br&gt;
          ports: 80,443&lt;br&gt;
          default_port: 443&lt;br&gt;
        env:&lt;br&gt;
          AWS_ACCESS_KEY_ID: "${{ secrets.AWS_ACCESS_KEY_ID }}"&lt;br&gt;
          AWS_SECRET_ACCESS_KEY: "${{ secrets.AWS_SECRET_ACCESS_KEY }}"&lt;br&gt;
          AWS_REGION: "us-east-1"&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the Docker Compose File
Your docker-compose.pullpreview.yml defines what runs in the preview environment:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;yaml&lt;br&gt;
services:&lt;br&gt;
  proxy:&lt;br&gt;
    image: caddy:2&lt;br&gt;
    command: "caddy reverse-proxy --from '${PULLPREVIEW_URL}' --to web:4567"&lt;br&gt;
    ports:&lt;br&gt;
      - "80:80"&lt;br&gt;
      - "443:443"&lt;/p&gt;

&lt;p&gt;web:&lt;br&gt;
    build: .&lt;br&gt;
    environment:&lt;br&gt;
      DATABASE_URL: "postgres://user:pass@db/postgres"&lt;/p&gt;

&lt;p&gt;db:&lt;br&gt;
    image: postgres:13&lt;br&gt;
    environment:&lt;br&gt;
      POSTGRES_PASSWORD: p4ssw0rd&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the Label
In your GitHub repository, create a label called pullpreview. When you add this label to a PR, the preview environment will spin up automatically.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Implementation: CDK Approach&lt;br&gt;
For teams needing more infrastructure control, the AWS CDK approach offers greater flexibility .&lt;/p&gt;

&lt;p&gt;Define Your Stack&lt;br&gt;
typescript&lt;br&gt;
// lib/awesome-stack.ts&lt;br&gt;
import * as cloudfront from "@aws-cdk/aws-cloudfront";&lt;br&gt;
import * as s3 from "@aws-cdk/aws-s3";&lt;br&gt;
import * as cdk from "@aws-cdk/core";&lt;/p&gt;

&lt;p&gt;export default class AwesomeStack extends cdk.Stack {&lt;br&gt;
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {&lt;br&gt;
    super(scope, id, props);&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const bucket = new s3.Bucket(this, "Bucket", {
  autoDeleteObjects: true,
  removalPolicy: cdk.RemovalPolicy.DESTROY, // Clean up when PR closes
});

const distribution = new cloudfront.Distribution(this, "Distribution", {
  defaultBehavior: {
    origin: new cloudfrontOrigins.S3Origin(bucket),
    viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
  },
});

// Export the URL for GitHub Deployments API
new cdk.CfnOutput(this, "DeploymentUrl", {
  value: "https://" + distribution.distributionDomainName
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;br&gt;
}&lt;br&gt;
Trigger Based on PR Number&lt;br&gt;
Use the PR number and branch name to create unique stack names:&lt;/p&gt;

&lt;p&gt;yaml&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;name: set STAGE variable&lt;br&gt;
run: echo "STAGE=pr-${{ github.event.number }}-${{ env.GITHUB_HEAD_REF_SLUG }}" &amp;gt;&amp;gt; $GITHUB_ENV&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;name: deploy the stack&lt;br&gt;
run: yarn deploy&lt;br&gt;
This ensures each PR gets its own isolated infrastructure .&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cleanup: Don't Leave Resources Running&lt;br&gt;
Preview environments should be ephemeral. Here's how to ensure cleanup:&lt;/p&gt;

&lt;p&gt;With PullPreview: Removing the pullpreview label or closing the PR automatically destroys the Lightsail instance.&lt;/p&gt;

&lt;p&gt;With CDK: Create a cleanup workflow that triggers on PR closure:&lt;/p&gt;

&lt;p&gt;yaml&lt;br&gt;
name: "Pull Request clean-up"&lt;/p&gt;

&lt;p&gt;on:&lt;br&gt;
  pull_request:&lt;br&gt;
    types: [unlabeled, closed]&lt;/p&gt;

&lt;p&gt;jobs:&lt;br&gt;
  clean-up:&lt;br&gt;
    if: |&lt;br&gt;
      (github.event.action == 'unlabeled' &amp;amp;&amp;amp; github.event.label.name == '🚀 deploy') ||&lt;br&gt;
      (github.event.action == 'closed' &amp;amp;&amp;amp; contains(github.event.pull_request.labels.*.name, '🚀 deploy'))&lt;br&gt;
    runs-on: ubuntu-latest&lt;br&gt;
    steps:&lt;br&gt;
      - name: destroy the stack&lt;br&gt;
        run: cdk destroy "AwesomeStack-${{ env.STAGE }}" --force&lt;br&gt;
Cost Considerations&lt;br&gt;
One of the advantages of this approach is cost control:&lt;/p&gt;

&lt;p&gt;Lightsail instances start at ~$10/month, prorated to actual usage &lt;/p&gt;

&lt;p&gt;CDK resources can be set to auto-delete with RemovalPolicy.DESTROY&lt;/p&gt;

&lt;p&gt;Spot instances in Kubernetes can further reduce costs for non-critical workloads &lt;/p&gt;

&lt;p&gt;Advanced Features to Consider&lt;br&gt;
Multi-service dependencies: When Service A depends on Service B, developers should be able to point to either staging or another preview environment .&lt;/p&gt;

&lt;p&gt;Persistent state: Keep database volumes across deploys so reviewers don't lose data when new commits arrive .&lt;/p&gt;

&lt;p&gt;SSH access: Grant trusted team members SSH access to preview instances for troubleshooting .&lt;/p&gt;

&lt;p&gt;Conclusion&lt;br&gt;
Preview environments triggered by PR labels provide a powerful way to review changes in a production-like setting. Whether you choose the simplicity of PullPreview with Lightsail, the control of AWS CDK, or the scalability of Kubernetes, the core pattern remains the same: code → label → preview → review → cleanup.&lt;/p&gt;

&lt;p&gt;The key benefits are clear:&lt;/p&gt;

&lt;p&gt;Low cost – pay only for what you use&lt;/p&gt;

&lt;p&gt;Simple activation – just add a label&lt;/p&gt;

&lt;p&gt;Automatic cleanup – no manual resource management&lt;/p&gt;

&lt;p&gt;Privacy – your code stays in your AWS account&lt;/p&gt;

&lt;p&gt;Start small, get the workflow working for a simple app, then expand to your full microservices architecture. Your team's confidence in shipping changes will increase dramatically when everyone can see and test the real thing before it merges.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cicd</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Deploying Preview Environments on AWS with PR Labels</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Sun, 05 Jul 2026 14:58:35 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/deploying-preview-environments-on-aws-with-pr-labels-50gg</link>
      <guid>https://dev.to/sohanaakbar7/deploying-preview-environments-on-aws-with-pr-labels-50gg</guid>
      <description>&lt;p&gt;Preview environments—temporary, isolated deployments of your code—have become essential for modern development workflows. They let you test changes in a production-like setting before merging, catch issues early, and give stakeholders a tangible way to review work in progress. &lt;/p&gt;

&lt;p&gt;One of the most elegant ways to trigger these environments? PR labels. Slap a label on a pull request, and boom—a full environment spins up on AWS. Remove the label, and it tears down. Simple, powerful, and cost-effective. Let me walk you through how to set this up.&lt;/p&gt;

&lt;p&gt;Why PR Labels?&lt;br&gt;
Managing preview environments manually is a nightmare. You don't want to:&lt;/p&gt;

&lt;p&gt;Spin up environments for every PR ($$$)&lt;/p&gt;

&lt;p&gt;Manually track which PRs need environments&lt;/p&gt;

&lt;p&gt;Remember to clean up after merge&lt;/p&gt;

&lt;p&gt;PR-based labels solve this beautifully. You're in control—only PRs flagged with your chosen label (like preview or deploy) get environments. Remove the label or close the PR? Environment destroyed. &lt;/p&gt;

&lt;p&gt;The Architecture at a Glance&lt;br&gt;
There are several solid approaches depending on your stack and complexity needs. Here are the main patterns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Simple Path: PullPreview + AWS Lightsail
If you want the simplest route and your app runs on Docker Compose, PullPreview is hard to beat . It's a GitHub Action that provisions a cheap AWS Lightsail instance (around $10/month, prorated) whenever you label a PR . The workflow stays entirely within GitHub Actions—your code never touches third-party servers .&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's a minimal workflow file:&lt;/p&gt;

&lt;p&gt;yaml&lt;/p&gt;

&lt;h1&gt;
  
  
  .github/workflows/pullpreview.yml
&lt;/h1&gt;

&lt;p&gt;name: PullPreview&lt;br&gt;
on:&lt;br&gt;
  pull_request:&lt;br&gt;
    types: [labeled, unlabeled, synchronize, closed, reopened]&lt;br&gt;
  push:&lt;br&gt;
    branches:&lt;br&gt;
      - main&lt;/p&gt;

&lt;p&gt;jobs:&lt;br&gt;
  deploy:&lt;br&gt;
    if: github.event_name == 'push' || github.event.label.name == 'pullpreview' || contains(github.event.pull_request.labels.*.name, 'pullpreview')&lt;br&gt;
    runs-on: ubuntu-latest&lt;br&gt;
    timeout-minutes: 30&lt;br&gt;
    steps:&lt;br&gt;
      - uses: actions/checkout@v4&lt;br&gt;
      - uses: pullpreview/action@v5&lt;br&gt;
        with:&lt;br&gt;
          admins: your-github-username&lt;br&gt;
          always_on: main  # Keeps a staging environment for main branch&lt;br&gt;
          compose_files: docker-compose.yml&lt;br&gt;
          default_port: 80&lt;br&gt;
        env:&lt;br&gt;
          AWS_ACCESS_KEY_ID: "${{ secrets.AWS_ACCESS_KEY_ID }}"&lt;br&gt;
          AWS_SECRET_ACCESS_KEY: "${{ secrets.AWS_SECRET_ACCESS_KEY }}"&lt;br&gt;
          AWS_REGION: "us-east-1"&lt;br&gt;
What makes this great:&lt;/p&gt;

&lt;p&gt;No Kubernetes complexity—each preview is a single VM &lt;/p&gt;

&lt;p&gt;State persistence across deploys via Docker volumes &lt;/p&gt;

&lt;p&gt;SSH access for troubleshooting (GitHub keys auto-installed) &lt;/p&gt;

&lt;p&gt;Privacy-first—code never leaves your infrastructure &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The CDK Approach: Full Infrastructure as Code
If you want total control and your team is comfortable with TypeScript, AWS CDK gives you infrastructure-as-code superpowers . You define your entire preview environment in code, and CDK handles the heavy lifting.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The basic flow:&lt;/p&gt;

&lt;p&gt;Label a PR → triggers GitHub Action&lt;/p&gt;

&lt;p&gt;Action derives a unique stack name (AwesomeStack-pr-42-feat-new-feature) &lt;/p&gt;

&lt;p&gt;CDK deploys a full stack (CloudFront + S3, ECS, or whatever you need) &lt;/p&gt;

&lt;p&gt;Deployment URL shows up in GitHub's UI via Deployments API&lt;/p&gt;

&lt;p&gt;Remove label or close PR → cdk destroy cleans everything up &lt;/p&gt;

&lt;p&gt;Key CDK snippets:&lt;/p&gt;

&lt;p&gt;typescript&lt;br&gt;
// lib/awesome-stack.ts - Infrastructure defined as code&lt;br&gt;
const bucket = new s3.Bucket(this, "Bucket", {&lt;br&gt;
  autoDeleteObjects: true,&lt;br&gt;
  removalPolicy: cdk.RemovalPolicy.DESTROY,  // Critical for cleanup!&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;const distribution = new cloudfront.Distribution(this, "Distribution", {&lt;br&gt;
  defaultBehavior: {&lt;br&gt;
    origin: new cloudfrontOrigins.S3Origin(bucket),&lt;br&gt;
    viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,&lt;br&gt;
  },&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;// Export the URL for GitHub integration&lt;br&gt;
new cdk.CfnOutput(this, "DeploymentUrl", {&lt;br&gt;
  value: "https://" + distribution.distributionDomainName&lt;br&gt;
});&lt;br&gt;
Pro tip: The removalPolicy: cdk.RemovalPolicy.DESTROY is essential—without it, your S3 buckets and databases stick around even after PRs close, costing you money .&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Kubernetes Route: ArgoCD + EKS
For teams already running Kubernetes on AWS (EKS), ArgoCD's ApplicationSet with PullRequest generator offers a GitOps-native approach .&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The workflow:&lt;/p&gt;

&lt;p&gt;Developer creates PR with preview label&lt;/p&gt;

&lt;p&gt;GitHub Actions builds Docker image, pushes to ECR&lt;/p&gt;

&lt;p&gt;ArgoCD detects the PR via ApplicationSet, provisions resources in a dedicated namespace&lt;/p&gt;

&lt;p&gt;Preview environment gets its own Ingress URL&lt;/p&gt;

&lt;p&gt;Remove label or close PR → ArgoCD deletes everything &lt;/p&gt;

&lt;p&gt;This approach works especially well for microservices with dependencies. One neat trick from production implementations: use a hybrid approach where databases are provisioned per-PR but dependent services are shared from staging, saving costs while keeping data isolated .&lt;/p&gt;

&lt;p&gt;Two Big Considerations&lt;br&gt;
Cost Control&lt;br&gt;
Preview environments can get expensive if you're not careful. Here are strategies to keep costs in check:&lt;/p&gt;

&lt;p&gt;Spot instances for non-critical workloads (perfect for previews) &lt;/p&gt;

&lt;p&gt;Auto-cleanup—remove the preview label if a PR goes stale for a certain period &lt;/p&gt;

&lt;p&gt;Lightsail nano instances (512MB RAM) for lighter workloads &lt;/p&gt;

&lt;p&gt;Scheduled cleanup jobs to catch dangling resources &lt;/p&gt;

&lt;p&gt;Security&lt;br&gt;
IAM policies should be least-privilege—give actions only what they need &lt;/p&gt;

&lt;p&gt;CIDR restrictions to limit access to specific IP ranges &lt;/p&gt;

&lt;p&gt;Never hardcode credentials—use GitHub Secrets for AWS access keys &lt;/p&gt;

&lt;p&gt;The Bottom Line&lt;br&gt;
PR-based preview environments on AWS transform how you ship code. Whether you go with:&lt;/p&gt;

&lt;p&gt;PullPreview + Lightsail for dead-simple setup&lt;/p&gt;

&lt;p&gt;AWS CDK for full infrastructure-as-code flexibility&lt;/p&gt;

&lt;p&gt;ArgoCD + EKS for Kubernetes-native GitOps&lt;/p&gt;

&lt;p&gt;…the core principle is the same: label-driven, ephemeral environments that give your team confidence to ship faster.&lt;/p&gt;

&lt;p&gt;My recommendation: Start with PullPreview if you're using Docker Compose and want to be live in minutes. Graduate to CDK if you need custom infrastructure. And if you're already deep in Kubernetes, ArgoCD is your friend.&lt;/p&gt;

&lt;p&gt;The key is to make preview environments so easy that your team actually uses them. Because when you combine speed with safety, that's when development gets truly enjoyable.&lt;/p&gt;

&lt;p&gt;Happy deploying! 🚀&lt;/p&gt;

</description>
      <category>automation</category>
      <category>aws</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>EBS vs EFS vs S3 for Frontend Assets — Which and When?</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Sat, 04 Jul 2026 13:55:49 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/ebs-vs-efs-vs-s3-for-frontend-assets-which-and-when-1dl1</link>
      <guid>https://dev.to/sohanaakbar7/ebs-vs-efs-vs-s3-for-frontend-assets-which-and-when-1dl1</guid>
      <description>&lt;p&gt;Let's cut through the cloud storage noise. You're building a frontend on AWS, and suddenly you're faced with three storage options that sound suspiciously similar. EBS, EFS, S3 — they all store stuff, right?&lt;/p&gt;

&lt;p&gt;Wrong. Picking the wrong one is like using a forklift to hang a picture frame. It might work, but you're going to have a bad time.&lt;/p&gt;

&lt;p&gt;Here's the developer-friendly breakdown of which storage service to use for your frontend assets, and exactly when to use it .&lt;/p&gt;

&lt;p&gt;The Short Answer (Because You're Busy)&lt;br&gt;
Your Frontend Need  Use This    Why&lt;br&gt;
Static files (HTML, CSS, JS, images) served to users    S3 + CloudFront Built for this. Scalable, cheap, globally fast &lt;br&gt;
A server's OS and application code  EBS It's your virtual hard drive. Fast, low-latency &lt;br&gt;
Multiple servers sharing the same content   EFS Shared network drive. Perfect for web farms &lt;br&gt;
S3 — The Static Asset Superhero 🦸&lt;br&gt;
What it is: Object storage. Think of it as an infinite Dropbox for your files .&lt;/p&gt;

&lt;p&gt;When to use: This should be your default choice for frontend assets. S3 is designed to store and serve static files — your HTML, CSS, JavaScript bundles, images, videos, and fonts .&lt;/p&gt;

&lt;p&gt;Why it wins for frontend:&lt;/p&gt;

&lt;p&gt;Virtually unlimited scale — handles traffic spikes without breaking a sweat &lt;/p&gt;

&lt;p&gt;11 nines durability — your files aren't going anywhere (99.999999999%) &lt;/p&gt;

&lt;p&gt;Cheap — pay for what you use &lt;/p&gt;

&lt;p&gt;HTTP access — files are accessible via URL, perfect for web browsers &lt;/p&gt;

&lt;p&gt;Pro tip: Pair S3 with CloudFront (AWS's CDN) for production. CloudFront caches your assets at edge locations worldwide, reducing latency and saving you S3 data transfer costs .&lt;/p&gt;

&lt;p&gt;When NOT to use S3:&lt;/p&gt;

&lt;p&gt;For files your EC2 instances need to read/write like a file system&lt;/p&gt;

&lt;p&gt;For server-side code execution (it's just storage, not compute)&lt;/p&gt;

&lt;p&gt;When you need low-latency block-level access &lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
// Pseudo-code: Upload your frontend build&lt;br&gt;
aws s3 sync ./build/ s3://my-frontend-bucket/ --delete&lt;br&gt;
EBS — The Personal Hard Drive 💽&lt;br&gt;
What it is: Block storage that attaches to a single EC2 instance. It's your virtual hard drive .&lt;/p&gt;

&lt;p&gt;When to use: For the server itself, not the assets it serves. EBS is what your EC2 instance uses as its boot volume and for any data that needs fast, low-latency access .&lt;/p&gt;

&lt;p&gt;The frontend connection:&lt;/p&gt;

&lt;p&gt;Your EC2 server's operating system lives on an EBS volume&lt;/p&gt;

&lt;p&gt;Your application code on the server runs from EBS&lt;/p&gt;

&lt;p&gt;If you're running a database on EC2 (like PostgreSQL or MySQL), that lives on EBS &lt;/p&gt;

&lt;p&gt;Why NOT for frontend assets:&lt;/p&gt;

&lt;p&gt;Single-instance only — can't share across multiple servers &lt;/p&gt;

&lt;p&gt;AZ-specific — tied to one Availability Zone &lt;/p&gt;

&lt;p&gt;Overkill for serving files — you're paying for provisioned capacity whether you use it or not&lt;/p&gt;

&lt;p&gt;Example: Your Node.js/Next.js application running on an EC2 instance uses EBS for the OS, runtime, and application code. But the static images your users download? Those should be in S3.&lt;/p&gt;

&lt;p&gt;EFS — The Team Shared Drive 📂&lt;br&gt;
What it is: A managed NFS file system that multiple EC2 instances can mount simultaneously .&lt;/p&gt;

&lt;p&gt;When to use: When multiple servers need to access the same files at the same time .&lt;/p&gt;

&lt;p&gt;Frontend use cases:&lt;/p&gt;

&lt;p&gt;A web server farm where all instances need access to the same content files &lt;/p&gt;

&lt;p&gt;Shared configuration files across microservices&lt;/p&gt;

&lt;p&gt;Content management systems where uploaded media must be accessible from any instance&lt;/p&gt;

&lt;p&gt;Development environments where team members share code &lt;/p&gt;

&lt;p&gt;The EFS architecture in action: Imagine you have 5 EC2 instances behind a load balancer running the same WordPress site. They all need access to the uploaded media files. EFS lets them all mount the same file system .&lt;/p&gt;

&lt;p&gt;Why NOT for frontend assets:&lt;/p&gt;

&lt;p&gt;Higher cost than S3 — you pay per GB used &lt;/p&gt;

&lt;p&gt;NFS overhead — adds latency compared to direct S3 access &lt;/p&gt;

&lt;p&gt;Overkill if you don't need shared write access&lt;/p&gt;

&lt;p&gt;The Real-World Architecture 🏗️&lt;br&gt;
In production, you often use all three together:&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
[User Browser] &lt;br&gt;
    ↓&lt;br&gt;
[CloudFront CDN] &lt;br&gt;
    ↓&lt;br&gt;
[S3 Bucket] ← Static assets (images, CSS, JS)&lt;br&gt;
    |&lt;br&gt;
[Application Load Balancer]&lt;br&gt;
    ↓&lt;br&gt;
[EC2 Instance #1] ←─┐&lt;br&gt;
    ↓               │&lt;br&gt;
[EBS Root Volume]   │&lt;br&gt;
    ↓               │&lt;br&gt;
[Application Code]  │&lt;br&gt;
                    │&lt;br&gt;
[EFS Shared Storage]←┘  ← Shared content (CMS uploads, configs)&lt;br&gt;
This pattern uses:&lt;/p&gt;

&lt;p&gt;S3 for globally accessible static assets &lt;/p&gt;

&lt;p&gt;EBS for each server's OS and application &lt;/p&gt;

&lt;p&gt;EFS for content shared across servers &lt;/p&gt;

&lt;p&gt;Decision Tree 🌳&lt;br&gt;
Start here:&lt;/p&gt;

&lt;p&gt;Do users need to access these files via URL?&lt;/p&gt;

&lt;p&gt;Yes → S3 (add CloudFront for production)&lt;/p&gt;

&lt;p&gt;No → Go to #2&lt;/p&gt;

&lt;p&gt;Does a single server need this as a mounted drive?&lt;/p&gt;

&lt;p&gt;Yes → EBS&lt;/p&gt;

&lt;p&gt;No → Go to #3&lt;/p&gt;

&lt;p&gt;Do multiple servers need to read/write the same files?&lt;/p&gt;

&lt;p&gt;Yes → EFS&lt;/p&gt;

&lt;p&gt;No → You probably want S3 anyway&lt;/p&gt;

&lt;p&gt;The Bottom Line&lt;br&gt;
Stop overthinking this. For 90% of frontend asset use cases, S3 is the answer. It's what it's built for, it's what it does best, and your wallet will thank you.&lt;/p&gt;

&lt;p&gt;Use EBS for what the server needs to run. Use EFS when you genuinely need file sharing across instances. But if you're serving files to users? S3 is your friend .&lt;/p&gt;

&lt;p&gt;And whatever you do, don't use EBS to serve static assets. That's like using a Ferrari to deliver pizzas — technically possible, but wildly inefficient and expensive .&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>aws</category>
      <category>frontend</category>
      <category>infrastructure</category>
    </item>
    <item>
      <title>How AWS WAF Rules Stopped Bot Traffic to My Portfolio</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Fri, 03 Jul 2026 09:46:55 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/how-aws-waf-rules-stopped-bot-traffic-to-my-portfolio-2l3g</link>
      <guid>https://dev.to/sohanaakbar7/how-aws-waf-rules-stopped-bot-traffic-to-my-portfolio-2l3g</guid>
      <description>&lt;p&gt;It started innocently enough—a spike in CloudWatch metrics that looked like success. More visitors than ever before? My portfolio was going viral!&lt;/p&gt;

&lt;p&gt;Then I looked at the logs. The "visitors" weren't human. They were bots. Scrapers, crawlers, and automated scripts churning through my portfolio like it was an all-you-can-eat buffet. My API was getting hammered, costs were creeping up, and legitimate traffic was getting choked out.&lt;/p&gt;

&lt;p&gt;Time to fight back. Here's how I used AWS WAF to reclaim my portfolio from the bots.&lt;/p&gt;

&lt;p&gt;The Bot Problem&lt;br&gt;
Bots aren't inherently evil—search engines and monitoring services are helpful. But scrapers and crawlers? They're digital parasites that:&lt;/p&gt;

&lt;p&gt;Skew analytics (making you think your portfolio's a viral hit when it's just Python scripts)&lt;/p&gt;

&lt;p&gt;Burn through resources (your AWS bill isn't as fun to look at as your portfolio)&lt;/p&gt;

&lt;p&gt;Slow down real visitors (because your API's too busy serving bots)&lt;/p&gt;

&lt;p&gt;I needed a way to separate the wheat from the chaff—legitimate visitors from automated pests.&lt;/p&gt;

&lt;p&gt;Enter AWS WAF Bot Control&lt;br&gt;
AWS WAF Bot Control is a managed rule group that detects and mitigates bot traffic with minimal configuration. The best part? It costs $10/month—well worth the peace of mind and resource savings.&lt;/p&gt;

&lt;p&gt;Level 1: The Common Protection&lt;br&gt;
The Common inspection level catches self-identifying bots using traditional detection techniques. It:&lt;/p&gt;

&lt;p&gt;Detects web scraping frameworks, search engines, and automated browsers&lt;/p&gt;

&lt;p&gt;Labels traffic from these bots&lt;/p&gt;

&lt;p&gt;Blocks unverified bots by default&lt;/p&gt;

&lt;p&gt;Think of it as the bouncer who checks ID at the door. Most bots aren't smart enough to fake it.&lt;/p&gt;

&lt;p&gt;Level 2: The Targeted Protection&lt;br&gt;
For more sophisticated bots that don't self-identify, I enabled the Targeted inspection level. This uses advanced techniques like:&lt;/p&gt;

&lt;p&gt;Behavior-based detection&lt;/p&gt;

&lt;p&gt;Browser interrogation&lt;/p&gt;

&lt;p&gt;Machine learning analysis of traffic patterns&lt;/p&gt;

&lt;p&gt;The ML rules analyze timestamps, browser characteristics, and other behavioral signals to spot coordinated bot activity.&lt;/p&gt;

&lt;p&gt;My Custom Rule Configuration&lt;br&gt;
Blocking Verified Bots&lt;br&gt;
Some bots identify themselves honestly but aren't welcome. I added a label-matching rule that runs after the Bot Control rule group to block specific verified bots:&lt;/p&gt;

&lt;p&gt;json&lt;br&gt;
{&lt;br&gt;
    "Name": "match_rule",&lt;br&gt;
    "Statement": {&lt;br&gt;
      "LabelMatchStatement": {&lt;br&gt;
        "Scope": "LABEL",&lt;br&gt;
        "Key": "awswaf:managed:aws:bot-control:bot:verified"&lt;br&gt;
      }&lt;br&gt;
    },&lt;br&gt;
    "Action": {&lt;br&gt;
      "Block": {}&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
This blocks all verified bots in one shot. Want to block only specific ones? Match on the bot name label instead:&lt;/p&gt;

&lt;p&gt;json&lt;br&gt;
"LabelMatchStatement": {&lt;br&gt;
    "Scope": "LABEL",&lt;br&gt;
    "Key": "awswaf:managed:aws:bot-control:bot:name:bingbot"&lt;br&gt;
}&lt;br&gt;
Rate-Based Rules: The Silent Guardian&lt;br&gt;
Sometimes bots aren't malicious—they're just... too much. I added rate-based rules to handle this gracefully.&lt;/p&gt;

&lt;p&gt;A blanket rule protects against HTTP floods:&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
Limit: 500 requests per 5 minutes&lt;br&gt;
Action: BLOCK&lt;br&gt;
Then more targeted rules with stricter limits:&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
Scope-down: if uri_path starts_with '/api'&lt;br&gt;
Limit: 100 requests per 5 minutes&lt;br&gt;
Aggregation key: IP&lt;br&gt;
This means if some scraper decides to hammer my API endpoint 500 times in a minute, it gets blocked while legitimate traffic flows freely.&lt;/p&gt;

&lt;p&gt;Creating Exceptions for Good Bots&lt;br&gt;
Not all bots are bad. I wanted search engines to index my portfolio. You can set specific rules to allow verified search bots while blocking others:&lt;/p&gt;

&lt;p&gt;json&lt;br&gt;
{&lt;br&gt;
    "Name": "allow-search-bots",&lt;br&gt;
    "Statement": {&lt;br&gt;
      "AndStatement": {&lt;br&gt;
        "Statements": [&lt;br&gt;
          {&lt;br&gt;
            "LabelMatchStatement": {&lt;br&gt;
              "Scope": "LABEL",&lt;br&gt;
              "Key": "awswaf:managed:aws:bot-control:bot:verified"&lt;br&gt;
            }&lt;br&gt;
          },&lt;br&gt;
          {&lt;br&gt;
            "NotStatement": {&lt;br&gt;
              "Statement": {&lt;br&gt;
                "ByteMatchStatement": {&lt;br&gt;
                  "FieldToMatch": {&lt;br&gt;
                    "SingleHeader": {&lt;br&gt;
                      "Name": "user-agent"&lt;br&gt;
                    }&lt;br&gt;
                  },&lt;br&gt;
                  "SearchString": "googlebot",&lt;br&gt;
                  "PositionalConstraint": "CONTAINS"&lt;br&gt;
                }&lt;br&gt;
              }&lt;br&gt;
            }&lt;br&gt;
          }&lt;br&gt;
        ]&lt;br&gt;
      }&lt;br&gt;
    },&lt;br&gt;
    "Action": {"Block": {}}&lt;br&gt;
}&lt;br&gt;
The Results&lt;br&gt;
The impact was immediate:&lt;/p&gt;

&lt;p&gt;API costs dropped by ~40% (fewer pointless requests)&lt;/p&gt;

&lt;p&gt;CloudWatch metrics normalized (actual traffic! Humans!)&lt;/p&gt;

&lt;p&gt;Response times improved (serving fewer bots = faster responses)&lt;/p&gt;

&lt;p&gt;Analytics became useful again (I could actually see real user behavior)&lt;/p&gt;

&lt;p&gt;Lessons Learned&lt;br&gt;
Start with Common protection — It handles 80% of the bot problem with zero configuration&lt;/p&gt;

&lt;p&gt;Layer your rules — Combine Bot Control with rate-based rules for defense in depth&lt;/p&gt;

&lt;p&gt;Use scope-down statements — Only inspect what matters. Target API endpoints or dynamic content to save on inspection costs&lt;/p&gt;

&lt;p&gt;Monitor the Bot Control dashboard — AWS provides pre-built dashboards showing bot activity levels. Use them to understand what you're dealing with&lt;/p&gt;

&lt;p&gt;The Bottom Line&lt;br&gt;
AWS WAF made protecting my portfolio from bots surprisingly painless. A few JSON rules, $10/month, and some dashboard monitoring—and my portfolio serves humans again.&lt;/p&gt;

&lt;p&gt;The bots are still out there. They'll keep scraping. But now? They're hitting a wall. And I'm sleeping better knowing my AWS bill won't spike because some scraper decided to index my entire portfolio 50 times a day.&lt;/p&gt;

&lt;p&gt;Your portfolio deserves better than being bot food. Time to give it the protection it deserves.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>S3 Static Hosting with HTTP-Errors Redirects — Solved</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Wed, 01 Jul 2026 13:51:48 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/s3-static-hosting-with-http-errors-redirects-solved-3je2</link>
      <guid>https://dev.to/sohanaakbar7/s3-static-hosting-with-http-errors-redirects-solved-3je2</guid>
      <description>&lt;p&gt;If you have ever deployed a Single Page Application (SPA) or a static site using Amazon S3, you might have encountered a frustratingly common issue: refresh errors. You navigate to yoursite.com/about, and everything works perfectly. But the moment you hit the refresh button—or share that link with a friend—you are greeted with a glaring 403 Forbidden or 404 Not Found error.&lt;/p&gt;

&lt;p&gt;This is one of the most infamous pitfalls of static hosting. Here is why it happens and the definitive way to fix it.&lt;/p&gt;

&lt;p&gt;The Root Cause&lt;br&gt;
S3 is an object storage service, not a web server.&lt;/p&gt;

&lt;p&gt;When you upload a React or Vue app, you usually have a single index.html that acts as your application shell. Your app uses client-side routing (like React Router) to handle navigation. However, S3 does not know about your routing logic. When you request /about, S3 looks for a literal file named about in the root directory. Since that file does not exist, S3 returns a 404 error.&lt;/p&gt;

&lt;p&gt;To solve this, you need to instruct S3 to serve index.html for all requests, regardless of the path. This is usually done via the "Static website hosting" settings.&lt;/p&gt;

&lt;p&gt;The "Old" Solution: S3 Redirection Rules&lt;br&gt;
The standard advice is to use the S3 "Static website hosting" feature and set the "Error document" to index.html. This creates a redirection rule.&lt;/p&gt;

&lt;p&gt;While this works, it has a significant downside: HTTP status codes.&lt;/p&gt;

&lt;p&gt;When S3 returns index.html for a missing page, it often sends a 200 OK status. This is fine for the user interface, but it is a nightmare for SEO. Search engines cannot distinguish between a valid page and a missing page, and analytics tools fail to track 404s correctly.&lt;/p&gt;

&lt;p&gt;The "Solved" Solution: CloudFront and Custom Error Responses&lt;br&gt;
The most robust and professional solution involves CloudFront, AWS's Content Delivery Network (CDN). Instead of relying on S3's redirection, we use CloudFront's custom error responses to serve the index.html file while preserving the correct HTTP status.&lt;/p&gt;

&lt;p&gt;Step-by-Step Implementation&lt;br&gt;
Step 1: Set up the S3 Bucket&lt;br&gt;
Upload your static files. Ensure the bucket is configured for static website hosting (or use the newer S3 website endpoint). For CloudFront, it is often better to use the REST endpoint to avoid redirect issues.&lt;/p&gt;

&lt;p&gt;Step 2: Configure CloudFront Behavior&lt;br&gt;
In your CloudFront distribution settings, find the "Error Pages" section.&lt;/p&gt;

&lt;p&gt;Step 3: Create a Custom Error Response&lt;br&gt;
You need to configure rules for specific HTTP error codes. Add a rule for both 403: Forbidden and 404: Not Found.&lt;/p&gt;

&lt;p&gt;For each error, set the following:&lt;/p&gt;

&lt;p&gt;Customize Error Response: Yes&lt;/p&gt;

&lt;p&gt;Response Page Path: /index.html (or the root of your app)&lt;/p&gt;

&lt;p&gt;HTTP Response Code: Set this to 200 only if you want to hide the error. However, the best practice for SEO is to set this to 404 if it is a real missing page, or leave it as 200 if you are using a SPA router.&lt;/p&gt;

&lt;p&gt;The SPA Trick: For SPAs, you usually want the error response to be 200 (so the app loads and the router takes over). But, if you want to be precise, you can implement server-side logic to determine if the file exists. For most SPAs, the 200 override is the accepted standard.&lt;/p&gt;

&lt;p&gt;The "S3 Redirects" Caveat&lt;br&gt;
If you were using the S3 static website hosting endpoint (with s3-website-region.amazonaws.com), you might have been using RoutingRules to handle redirects. This approach has been largely superseded by CloudFront because:&lt;/p&gt;

&lt;p&gt;HTTPS: S3 website endpoints do not support HTTPS natively. You must use CloudFront for SSL certificates.&lt;/p&gt;

&lt;p&gt;Speed: CloudFront serves content from edge locations, drastically reducing latency.&lt;/p&gt;

&lt;p&gt;Granularity: CloudFront allows you to handle errors without changing the URL in the browser, whereas S3 redirects often perform an explicit redirect (changing the URL).&lt;/p&gt;

&lt;p&gt;The Ultimate Configuration&lt;br&gt;
If you are using CloudFront + S3, your configuration should look like this:&lt;/p&gt;

&lt;p&gt;S3: Just store the files. Do not rely on the "Static website hosting" redirection feature. Use the bucket's REST endpoint (not the website endpoint).&lt;/p&gt;

&lt;p&gt;CloudFront Origin: Point to the S3 bucket REST endpoint.&lt;/p&gt;

&lt;p&gt;Error Pages:&lt;/p&gt;

&lt;p&gt;Error Code: 403&lt;/p&gt;

&lt;p&gt;Response Page Path: /index.html&lt;/p&gt;

&lt;p&gt;HTTP Response Code: 200&lt;/p&gt;

&lt;p&gt;Error Code: 404&lt;/p&gt;

&lt;p&gt;Response Page Path: /index.html&lt;/p&gt;

&lt;p&gt;HTTP Response Code: 200&lt;/p&gt;

&lt;p&gt;A Note on http-errors and Nginx&lt;br&gt;
Sometimes, developers use the http-errors middleware in Node.js or Nginx configurations to serve the index.html file. The logic is identical: you are creating a "catch-all" route.&lt;/p&gt;

&lt;p&gt;If you are using a reverse proxy like Nginx in front of your S3 bucket (via a proxy pass), the equivalent configuration is:&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
try_files $uri $uri/ /index.html;&lt;br&gt;
This tells the server to check if the file exists ($uri), and if not, fall back to index.html.&lt;/p&gt;

&lt;p&gt;Why This is "Solved"&lt;br&gt;
By using this CloudFront-first approach, you get:&lt;/p&gt;

&lt;p&gt;Perfect SPA Routing: Deep links and refreshes work flawlessly.&lt;/p&gt;

&lt;p&gt;Global Speed: Edge caching.&lt;/p&gt;

&lt;p&gt;Security: Full HTTPS support.&lt;/p&gt;

&lt;p&gt;SEO Friendliness: You control exactly which status codes are returned.&lt;/p&gt;

&lt;p&gt;So, ditch the S3 static hosting redirect rules and embrace CloudFront. It is the standard, production-grade method for hosting modern web applications on AWS.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>frontend</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>A/B Testing at Warp Speed: How Cloudflare Workers Revolutionize HTML Experiments</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Tue, 30 Jun 2026 15:31:52 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/ab-testing-at-warp-speed-how-cloudflare-workers-revolutionize-html-experiments-1kdf</link>
      <guid>https://dev.to/sohanaakbar7/ab-testing-at-warp-speed-how-cloudflare-workers-revolutionize-html-experiments-1kdf</guid>
      <description>&lt;p&gt;Let's be honest—traditional A/B testing is broken.&lt;/p&gt;

&lt;p&gt;If you've ever implemented client-side testing, you know the drill. Your users load the page, wait for JavaScript to execute, and then—flicker—the content changes. It's jarring. It hurts your Core Web Vitals. And worst of all, your experiments might be measuring user frustration instead of genuine engagement.&lt;/p&gt;

&lt;p&gt;But what if you could run A/B tests with zero flicker? What if you could modify your HTML before it even reaches the browser? That's exactly what Cloudflare Workers make possible .&lt;/p&gt;

&lt;p&gt;The Server-Side Advantage&lt;br&gt;
A/B testing at the edge means making decisions about which variant a user sees before any HTML is sent to their browser . Instead of:&lt;/p&gt;

&lt;p&gt;Load the page&lt;/p&gt;

&lt;p&gt;Execute JavaScript&lt;/p&gt;

&lt;p&gt;Flicker&lt;/p&gt;

&lt;p&gt;Apply the variant&lt;/p&gt;

&lt;p&gt;Track the result&lt;/p&gt;

&lt;p&gt;You get:&lt;/p&gt;

&lt;p&gt;Decision made at the edge&lt;/p&gt;

&lt;p&gt;Correct HTML streamed immediately&lt;/p&gt;

&lt;p&gt;Zero flicker&lt;/p&gt;

&lt;p&gt;Better performance&lt;/p&gt;

&lt;p&gt;Companies like Ninetailed are already using Cloudflare Workers to achieve 2-3x cost savings compared to traditional serverless platforms, all while delivering personalized experiences with minimal latency .&lt;/p&gt;

&lt;p&gt;How It Actually Works&lt;br&gt;
The concept is surprisingly elegant. Your Cloudflare Worker intercepts incoming requests, checks for a cookie to maintain user consistency, and routes users to the appropriate variant .&lt;/p&gt;

&lt;p&gt;Here's a simplified version that's production-ready:&lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
const NAME = "myExampleWorkersABTest";&lt;/p&gt;

&lt;p&gt;export default {&lt;br&gt;
  async fetch(req) {&lt;br&gt;
    const url = new URL(req.url);&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Determine which group this user is in
const cookie = req.headers.get("cookie");

if (cookie &amp;amp;&amp;amp; cookie.includes(`${NAME}=control`)) {
  url.pathname = "/control" + url.pathname;
} else if (cookie &amp;amp;&amp;amp; cookie.includes(`${NAME}=test`)) {
  url.pathname = "/test" + url.pathname;
} else {
  // New user—randomly assign them (50/50 split)
  const group = Math.random() &amp;lt; 0.5 ? "test" : "control";
  url.pathname = `/${group}` + url.pathname;

  // Fetch and modify the response to set a cookie
  let res = await fetch(url);
  res = new Response(res.body, res);
  res.headers.append("Set-Cookie", `${NAME}=${group}; path=/`);
  return res;
}

return fetch(url);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;br&gt;
}&lt;br&gt;
With this approach, your users stay in the same experiment group across sessions—no confusing inconsistencies .&lt;/p&gt;

&lt;p&gt;Beyond Simple Routing: HTML Rewriting&lt;br&gt;
The real magic happens when you combine Workers with HTMLRewriter. This powerful API lets you modify HTML as it streams through your Worker .&lt;/p&gt;

&lt;p&gt;Need to change a headline, swap out a call-to-action button, or inject different meta tags? HTMLRewriter can do it all in real-time, without touching your origin server .&lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
const transformer = new HTMLRewriter()&lt;br&gt;
  .on("h1", {&lt;br&gt;
    element(element) {&lt;br&gt;
      element.setInnerContent("Experience the future of testing");&lt;br&gt;
    }&lt;br&gt;
  })&lt;br&gt;
  .on("meta[name='description']", {&lt;br&gt;
    element(element) {&lt;br&gt;
      element.setAttribute("content", "Zero-flicker A/B testing at the edge");&lt;br&gt;
    }&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;return transformer.transform(originalResponse);&lt;br&gt;
This approach is so powerful that Optimizely has built their Edge Delivery SDK specifically for this use case, leveraging Cloudflare's HTMLRewriter to execute web experiments .&lt;/p&gt;

&lt;p&gt;Real-World Performance Impact&lt;br&gt;
The numbers are compelling. By moving A/B testing logic to Cloudflare Workers:&lt;/p&gt;

&lt;p&gt;60-70% faster context creation compared to client-only SDKs &lt;/p&gt;

&lt;p&gt;Zero flicker—HTML is already modified when it reaches the browser &lt;/p&gt;

&lt;p&gt;Global low latency—your tests run from Cloudflare's edge network &lt;/p&gt;

&lt;p&gt;One developer who built a server-side A/B testing solution on Workers discovered an unexpected benefit: they could apply HTML patches for SEO tweaks without deploying new code. "Update meta tags, headings, or content structure without deploying new code"—that's a game-changer for teams with slow deployment cycles .&lt;/p&gt;

&lt;p&gt;Production Considerations&lt;br&gt;
When implementing A/B testing at scale, keep these patterns in mind:&lt;/p&gt;

&lt;p&gt;Cookie-Based Sticky Sessions&lt;br&gt;
Maintain user consistency by setting cookies. Users who see the "test" variant should keep seeing it . The SDK approach handles this automatically with built-in cookie management .&lt;/p&gt;

&lt;p&gt;Configuration Management&lt;br&gt;
Use Workers KV to store experiment configurations separately from code. This allows you to start, stop, or modify experiments without deploying new code .&lt;/p&gt;

&lt;p&gt;Cache Strategy&lt;br&gt;
Ensure your CDN caching respects variation logic. A common pattern: set Cache-Control: private or use cache keys that vary by the experiment cookie .&lt;/p&gt;

&lt;p&gt;The Bottom Line&lt;br&gt;
Cloudflare Workers aren't just making A/B testing faster—they're fundamentally changing how we think about experimentation. When you can modify HTML at the edge with zero performance penalty, you unlock possibilities that were previously impossible:&lt;/p&gt;

&lt;p&gt;Run experiments without client-side flicker&lt;/p&gt;

&lt;p&gt;Test SEO changes instantly&lt;/p&gt;

&lt;p&gt;Personalize content based on real-time conditions&lt;/p&gt;

&lt;p&gt;Save money while delivering better experiences &lt;/p&gt;

&lt;p&gt;The next time someone tells you A/B testing is slowing down their site, show them what's possible at the edge. Zero flicker. Global reach. Complete control.&lt;/p&gt;

&lt;p&gt;Ready to implement your own edge-powered experiments? Check out the Cloudflare Workers examples and start streaming HTML with confidence. Your users—and your conversion rates—will thank you.&lt;/p&gt;

</description>
      <category>performance</category>
      <category>serverless</category>
      <category>testing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Terraform for Frontend Hosting — 40 Lines to Prod</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Mon, 29 Jun 2026 09:26:12 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/terraform-for-frontend-hosting-40-lines-to-prod-228c</link>
      <guid>https://dev.to/sohanaakbar7/terraform-for-frontend-hosting-40-lines-to-prod-228c</guid>
      <description>&lt;p&gt;Deploying a frontend application shouldn't require a PhD in DevOps. In this post, I'll show you how to get a production-ready static site online with just 40 lines of Terraform.&lt;/p&gt;

&lt;p&gt;Why Terraform for Frontend?&lt;br&gt;
Infrastructure as Code (IaC) isn't just for backend engineers. When you codify your frontend infrastructure, you get:&lt;/p&gt;

&lt;p&gt;Repeatable deployments across environments&lt;/p&gt;

&lt;p&gt;Version-controlled infrastructure alongside your code&lt;/p&gt;

&lt;p&gt;Easy rollbacks (just revert the Terraform state)&lt;/p&gt;

&lt;p&gt;No manual AWS console clicking (we all hate that)&lt;/p&gt;

&lt;p&gt;The Setup&lt;br&gt;
We'll deploy a static site to AWS S3 with CloudFront CDN and Route 53 DNS. All the essentials for a production-grade frontend.&lt;/p&gt;

&lt;p&gt;Prerequisites&lt;br&gt;
bash&lt;/p&gt;

&lt;h1&gt;
  
  
  Install Terraform
&lt;/h1&gt;

&lt;p&gt;brew install terraform  # macOS&lt;/p&gt;

&lt;h1&gt;
  
  
  or download from terraform.io
&lt;/h1&gt;

&lt;h1&gt;
  
  
  AWS CLI configured
&lt;/h1&gt;

&lt;p&gt;aws configure&lt;br&gt;
The 40 Lines&lt;br&gt;
Here's the entire main.tf:&lt;/p&gt;

&lt;p&gt;hcl&lt;br&gt;
terraform {&lt;br&gt;
  required_providers {&lt;br&gt;
    aws = {&lt;br&gt;
      source  = "hashicorp/aws"&lt;br&gt;
      version = "~&amp;gt; 5.0"&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;provider "aws" {&lt;br&gt;
  region = "us-east-1"&lt;br&gt;
}&lt;/p&gt;

&lt;h1&gt;
  
  
  S3 Bucket for static hosting
&lt;/h1&gt;

&lt;p&gt;resource "aws_s3_bucket" "site" {&lt;br&gt;
  bucket = var.domain_name&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;resource "aws_s3_bucket_website_configuration" "site" {&lt;br&gt;
  bucket = aws_s3_bucket.site.id&lt;br&gt;
  index_document {&lt;br&gt;
    suffix = "index.html"&lt;br&gt;
  }&lt;br&gt;
  error_document {&lt;br&gt;
    key = "404.html"&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;resource "aws_s3_bucket_public_access_block" "site" {&lt;br&gt;
  bucket = aws_s3_bucket.site.id&lt;br&gt;
  block_public_acls       = false&lt;br&gt;
  block_public_policy     = false&lt;br&gt;
  ignore_public_acls      = false&lt;br&gt;
  restrict_public_buckets = false&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;resource "aws_s3_bucket_policy" "site" {&lt;br&gt;
  bucket = aws_s3_bucket.site.id&lt;br&gt;
  policy = jsonencode({&lt;br&gt;
    Version = "2012-10-17"&lt;br&gt;
    Statement = [&lt;br&gt;
      {&lt;br&gt;
        Sid       = "PublicReadGetObject"&lt;br&gt;
        Effect    = "Allow"&lt;br&gt;
        Principal = "&lt;em&gt;"&lt;br&gt;
        Action    = "s3:GetObject"&lt;br&gt;
        Resource  = "${aws_s3_bucket.site.arn}/&lt;/em&gt;"&lt;br&gt;
      }&lt;br&gt;
    ]&lt;br&gt;
  })&lt;br&gt;
}&lt;/p&gt;

&lt;h1&gt;
  
  
  CloudFront CDN
&lt;/h1&gt;

&lt;p&gt;resource "aws_cloudfront_distribution" "site" {&lt;br&gt;
  origin {&lt;br&gt;
    domain_name = aws_s3_bucket_website_configuration.site.website_endpoint&lt;br&gt;
    origin_id   = var.domain_name&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;custom_origin_config {
  http_port              = 80
  https_port             = 443
  origin_protocol_policy = "http-only"
  origin_ssl_protocols   = ["TLSv1.2"]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;enabled             = true&lt;br&gt;
  default_root_object = "index.html"&lt;/p&gt;

&lt;p&gt;default_cache_behavior {&lt;br&gt;
    allowed_methods  = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]&lt;br&gt;
    cached_methods   = ["GET", "HEAD"]&lt;br&gt;
    target_origin_id = var.domain_name&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;forwarded_values {
  query_string = false
  cookies {
    forward = "none"
  }
}

viewer_protocol_policy = "redirect-to-https"
min_ttl                = 0
default_ttl            = 3600
max_ttl                = 86400
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;price_class = "PriceClass_100"&lt;/p&gt;

&lt;p&gt;restrictions {&lt;br&gt;
    geo_restriction {&lt;br&gt;
      restriction_type = "none"&lt;br&gt;
    }&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;viewer_certificate {&lt;br&gt;
    cloudfront_default_certificate = true&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;aliases = [var.domain_name]&lt;/p&gt;

&lt;p&gt;custom_error_response {&lt;br&gt;
    error_code         = 404&lt;br&gt;
    response_page_path = "/404.html"&lt;br&gt;
    response_code      = 404&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;h1&gt;
  
  
  Variables
&lt;/h1&gt;

&lt;p&gt;variable "domain_name" {&lt;br&gt;
  description = "Your domain name (e.g., example.com)"&lt;br&gt;
  type        = string&lt;br&gt;
}&lt;/p&gt;

&lt;h1&gt;
  
  
  Outputs
&lt;/h1&gt;

&lt;p&gt;output "website_url" {&lt;br&gt;
  value = "https://${var.domain_name}"&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;output "cloudfront_domain" {&lt;br&gt;
  value = aws_cloudfront_distribution.site.domain_name&lt;br&gt;
}&lt;br&gt;
That's it. 42 lines including whitespace.&lt;/p&gt;

&lt;p&gt;Deployment Workflow&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Initialize Terraform
bash
terraform init&lt;/li&gt;
&lt;li&gt;Plan your infrastructure
bash
terraform plan -var="domain_name=yourdomain.com"&lt;/li&gt;
&lt;li&gt;Apply the changes
bash
terraform apply -var="domain_name=yourdomain.com" -auto-approve&lt;/li&gt;
&lt;li&gt;Deploy your frontend code
bash
aws s3 sync ./dist s3://yourdomain.com&lt;/li&gt;
&lt;li&gt;Invalidate CloudFront cache (optional)
bash
aws cloudfront create-invalidation --distribution-id  --paths "/*"
CI/CD Integration
Add this to your GitHub Actions workflow:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;yaml&lt;br&gt;
name: Deploy Frontend&lt;br&gt;
on:&lt;br&gt;
  push:&lt;br&gt;
    branches: [main]&lt;/p&gt;

&lt;p&gt;jobs:&lt;br&gt;
  deploy:&lt;br&gt;
    runs-on: ubuntu-latest&lt;br&gt;
    steps:&lt;br&gt;
      - uses: actions/checkout@v3&lt;br&gt;
      - uses: actions/setup-node@v3&lt;br&gt;
        with:&lt;br&gt;
          node-version: 18&lt;br&gt;
      - run: npm ci &amp;amp;&amp;amp; npm run build&lt;br&gt;
      - uses: hashicorp/setup-terraform@v2&lt;br&gt;
      - run: terraform init&lt;br&gt;
      - run: terraform apply -auto-approve -var="domain_name=${{ secrets.DOMAIN }}"&lt;br&gt;
      - run: aws s3 sync ./dist s3://${{ secrets.DOMAIN }}&lt;br&gt;
Pro Tips&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use Terraform State Backend
Add this to store state remotely (essential for teams):&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;hcl&lt;br&gt;
terraform {&lt;br&gt;
  backend "s3" {&lt;br&gt;
    bucket = "my-terraform-state-bucket"&lt;br&gt;
    key    = "frontend/terraform.tfstate"&lt;br&gt;
    region = "us-east-1"&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Environment Separation
Use Terraform workspaces or separate terraform.tfvars files:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;hcl&lt;/p&gt;

&lt;h1&gt;
  
  
  terraform.tfvars.prod
&lt;/h1&gt;

&lt;p&gt;domain_name = "prod.example.com"&lt;/p&gt;

&lt;h1&gt;
  
  
  terraform.tfvars.staging
&lt;/h1&gt;

&lt;p&gt;domain_name = "staging.example.com"&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add Custom Headers with CloudFront Functions
hcl
resource "aws_cloudfront_function" "security_headers" {
name    = "security-headers"
runtime = "cloudfront-js-1.0"
code    = &amp;lt;&amp;lt;EOF
function handler(event) {
var response = event.response;
response.headers['strict-transport-security'] = { value: 'max-age=63072000' };
response.headers['x-frame-options'] = { value: 'SAMEORIGIN' };
return response;
}
EOF
}
What About Other Cloud Providers?
The same principle applies. Here's a GCP example:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;hcl&lt;br&gt;
resource "google_storage_bucket" "site" {&lt;br&gt;
  name     = var.domain_name&lt;br&gt;
  location = "US"&lt;br&gt;
  website {&lt;br&gt;
    main_page_suffix = "index.html"&lt;br&gt;
    not_found_page   = "404.html"&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;resource "google_storage_bucket_iam_binding" "public" {&lt;br&gt;
  bucket = google_storage_bucket.site.name&lt;br&gt;
  role   = "roles/storage.objectViewer"&lt;br&gt;
  members = ["allUsers"]&lt;br&gt;
}&lt;br&gt;
Real-World Results&lt;br&gt;
Using this approach, I've:&lt;/p&gt;

&lt;p&gt;Reduced deployment time from 15 minutes to 2 minutes&lt;/p&gt;

&lt;p&gt;Eliminated manual errors (no more "oops, I forgot to enable CDN")&lt;/p&gt;

&lt;p&gt;Made rollbacks instant (terraform apply with a previous state)&lt;/p&gt;

&lt;p&gt;Saved $50/month by automating resource cleanup with terraform destroy&lt;/p&gt;

&lt;p&gt;The Big Picture&lt;br&gt;
Infrastructure as Code for frontend isn't just about deploying faster—it's about treating your infrastructure with the same rigor as your application code. When your infrastructure is in version control, you can:&lt;/p&gt;

&lt;p&gt;Review changes via PRs&lt;/p&gt;

&lt;p&gt;Track who changed what and why&lt;/p&gt;

&lt;p&gt;Reproduce environments exactly&lt;/p&gt;

&lt;p&gt;Audit for security compliance&lt;/p&gt;

&lt;p&gt;Next Steps&lt;br&gt;
Add SSL certificates with AWS ACM&lt;/p&gt;

&lt;p&gt;Implement custom error pages&lt;/p&gt;

&lt;p&gt;Set up CloudFront Functions for A/B testing&lt;/p&gt;

&lt;p&gt;Add monitoring with CloudWatch alarms&lt;/p&gt;

&lt;p&gt;Implement Blue/Green deployments&lt;/p&gt;

&lt;p&gt;Final Thoughts&lt;br&gt;
Forty lines of Terraform replaced hours of manual AWS console clicking, created repeatable deployments, and gave me infrastructure that's actually maintainable. Your frontend deserves the same reliability as your backend.&lt;/p&gt;

&lt;p&gt;Remember: The goal isn't to write the least lines of code—it's to create infrastructure that's simple, maintainable, and predictable. Forty lines just happens to be how simple it can be.&lt;/p&gt;

&lt;p&gt;What's your experience with IaC for frontend? Drop a comment below!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>frontend</category>
      <category>terraform</category>
    </item>
    <item>
      <title>AWS S3 + CloudFront + OAC: The Correct Way to Host a SPA</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Sat, 27 Jun 2026 13:21:47 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/aws-s3-cloudfront-oac-the-correct-way-to-host-a-spa-23jk</link>
      <guid>https://dev.to/sohanaakbar7/aws-s3-cloudfront-oac-the-correct-way-to-host-a-spa-23jk</guid>
      <description>&lt;p&gt;So you've built a slick single-page application with React, Vue, or Angular. Now comes the crucial question—how do you host it securely on AWS without making your S3 bucket the wild west of the internet? Let's dive into what I consider the correct way: using S3 with CloudFront and Origin Access Control (OAC).&lt;/p&gt;

&lt;p&gt;Why the Old Way Isn't Good Enough&lt;br&gt;
I've seen countless tutorials that recommend enabling static website hosting on an S3 bucket and making it public. This always raises a red flag for me .&lt;/p&gt;

&lt;p&gt;The problem? Public S3 buckets are security liabilities. Even if your bucket only contains static files today, exposing it directly to the internet creates risks. AWS themselves recommend blocking all public access . Plus, serving content directly from S3 means your users might experience higher latency, especially if they're far from your bucket's region .&lt;/p&gt;

&lt;p&gt;The Three Pillars of Secure SPA Hosting&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep Your S3 Bucket Private
First things first—your S3 bucket should remain completely private with all public access blocked . This might seem counterintuitive for a website, but trust me, it's the foundation of a secure setup.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When creating your bucket, disable all public access and ensure your bucket policy doesn't allow public reads. This forces traffic to flow through CloudFront, which becomes your single entry point .&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use Origin Access Control (OAC)
Here's where the magic happens. OAC is the modern way to let CloudFront securely access your private S3 bucket . It's the successor to the older Origin Access Identity (OAI) and provides better security with more granular permissions .&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Configuring OAC:&lt;/p&gt;

&lt;p&gt;In CloudFront, create a new OAC setting&lt;/p&gt;

&lt;p&gt;Select "S3" as the origin type and keep the signing behavior as default&lt;/p&gt;

&lt;p&gt;In your distribution, set the origin to use this OAC &lt;/p&gt;

&lt;p&gt;Important: When configuring your S3 origin in CloudFront, use the REST API endpoint (e.g., your-bucket.s3.amazonaws.com), not the static website endpoint . The static website endpoint and OAC are incompatible .&lt;/p&gt;

&lt;p&gt;Update Your Bucket Policy:&lt;/p&gt;

&lt;p&gt;Your S3 bucket policy should look like this to allow CloudFront access:&lt;/p&gt;

&lt;p&gt;json&lt;br&gt;
{&lt;br&gt;
    "Version": "2012-10-17",&lt;br&gt;
    "Statement": {&lt;br&gt;
        "Sid": "AllowCloudFrontServicePrincipalReadOnly",&lt;br&gt;
        "Effect": "Allow",&lt;br&gt;
        "Principal": {&lt;br&gt;
            "Service": "cloudfront.amazonaws.com"&lt;br&gt;
        },&lt;br&gt;
        "Action": "s3:GetObject",&lt;br&gt;
        "Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*",&lt;br&gt;
        "Condition": {&lt;br&gt;
            "StringEquals": {&lt;br&gt;
                "AWS:SourceArn": "arn:aws:cloudfront::YOUR_ACCOUNT_ID:distribution/YOUR_DISTRIBUTION_ID"&lt;br&gt;
            }&lt;br&gt;
        }&lt;br&gt;
    }&lt;br&gt;
}&lt;br&gt;
Replace the placeholders with your actual bucket name, account ID, and distribution ID .&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Handle SPA Routing Properly
SPAs use client-side routing, meaning paths like /dashboard or /about are handled by JavaScript, not by the server. If a user refreshes the page or enters a deep link directly, CloudFront will look for a file that doesn't exist and return a 404.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fix? Configure CloudFront's custom error responses to route 403 and 404 errors back to index.html with a 200 status code :&lt;/p&gt;

&lt;p&gt;In your CloudFront distribution, go to the Error Pages tab&lt;/p&gt;

&lt;p&gt;Create custom error responses for both 403: Forbidden and 404: Not Found&lt;/p&gt;

&lt;p&gt;Set the response page path to /index.html and the response code to 200: OK &lt;/p&gt;

&lt;p&gt;This tells CloudFront to serve index.html when a file isn't found, allowing your SPA router to take over and handle the navigation.&lt;/p&gt;

&lt;p&gt;Alternative Approach: You can also use a CloudFront Function to rewrite requests:&lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
function handler(event) {&lt;br&gt;
    const request = event.request;&lt;br&gt;
    const uri = request.uri;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (uri.endsWith('/') || !uri.includes('.')) {
    request.uri = '/index.html';
}
return request;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;br&gt;
This function rewrites requests without file extensions or trailing slashes to index.html .&lt;/p&gt;

&lt;p&gt;Optimizing Performance with Caching&lt;br&gt;
Cache Static Assets Aggressively&lt;br&gt;
Your JavaScript and CSS files typically include unique hashes in their filenames (e.g., main-abc123.js), making them immutable. You can cache these aggressively with CloudFront .&lt;/p&gt;

&lt;p&gt;Create a separate cache behavior for your static assets. For React, the pattern might be static/&lt;em&gt;, while other frameworks might use assets/&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;Go to your CloudFront distribution's Behaviors tab&lt;/p&gt;

&lt;p&gt;Create a behavior with a path pattern matching your static assets&lt;/p&gt;

&lt;p&gt;Use the managed CachingOptimized policy or create a custom one with long TTLs &lt;/p&gt;

&lt;p&gt;You'll see the benefit immediately—CloudFront will serve cached assets with an x-cache: Hit from cloudfront header, dramatically reducing latency for returning visitors .&lt;/p&gt;

&lt;p&gt;Don't Cache index.html&lt;br&gt;
Your index.html should have a short cache duration or no caching at all. This ensures users get the latest version of your app without clearing their cache . Consider using max-age=60 with stale-while-revalidate for a balance of performance and freshness .&lt;/p&gt;

&lt;p&gt;Custom Domain and SSL&lt;br&gt;
Once your setup is working, you'll want to add a custom domain. You'll need to:&lt;/p&gt;

&lt;p&gt;Request an SSL certificate through ACM (AWS Certificate Manager) in the us-east-1 region&lt;/p&gt;

&lt;p&gt;Add your domain as an alternate domain name in CloudFront&lt;/p&gt;

&lt;p&gt;Create a CNAME record in your DNS provider pointing to your CloudFront distribution domain name &lt;/p&gt;

&lt;p&gt;CloudFront will handle SSL termination, providing HTTPS for your custom domain.&lt;/p&gt;

&lt;p&gt;The Bottom Line&lt;br&gt;
This architecture—private S3 bucket + CloudFront with OAC—gives you security, performance, and cost-effectiveness. Your S3 bucket stays private, your content gets delivered globally through CloudFront's edge network, and your SPA routes work flawlessly .&lt;/p&gt;

&lt;p&gt;Skip the outdated tutorials that make your bucket public. Do it the right way with OAC, and your future self (and your users) will thank you.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>SPA vs SSR Deployment: The Infrastructure Showdown</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Fri, 26 Jun 2026 14:13:05 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/spa-vs-ssr-deployment-the-infrastructure-showdown-3f07</link>
      <guid>https://dev.to/sohanaakbar7/spa-vs-ssr-deployment-the-infrastructure-showdown-3f07</guid>
      <description>&lt;p&gt;Introduction&lt;br&gt;
The age-old debate between Single Page Applications (SPA) and Server-Side Rendering (SSR) usually centers around user experience, performance, and developer productivity. But there's a critical aspect that often gets overlooked until it's too late: the infrastructure.&lt;/p&gt;

&lt;p&gt;How you deploy and host your application fundamentally changes based on your rendering strategy. And trust me, learning this the hard way — like I did with my first production SSR app — is not fun.&lt;/p&gt;

&lt;p&gt;Let's dive deep into the infrastructure differences between SPA and SSR deployments, and help you make the right choice for your next project.&lt;/p&gt;

&lt;p&gt;The Short Answer&lt;br&gt;
Aspect  SPA SSR&lt;br&gt;
Hosting Static file hosting (S3, CDN, Netlify)  Dynamic server (Node.js, Python, etc.)&lt;br&gt;
Compute Minimal (just serve files)  Significant (render on every request)&lt;br&gt;
Scale   Easy (CDN caching)  Complex (needs load balancing)&lt;br&gt;
Cost    Low Higher&lt;br&gt;
Deployment  Simple  Complex&lt;br&gt;
Cold Starts None    Problematic&lt;br&gt;
SPA Deployment: Static Simplicity&lt;br&gt;
How It Works&lt;br&gt;
A Single Page Application consists of static files:&lt;/p&gt;

&lt;p&gt;index.html&lt;/p&gt;

&lt;p&gt;JavaScript bundles&lt;/p&gt;

&lt;p&gt;CSS files&lt;/p&gt;

&lt;p&gt;Assets (images, fonts)&lt;/p&gt;

&lt;p&gt;These files are served directly to the browser, where all rendering happens client-side.&lt;/p&gt;

&lt;p&gt;Infrastructure Components&lt;br&gt;
text&lt;br&gt;
[CDN/Static Host] → [Browser]&lt;br&gt;
Typical Setup:&lt;/p&gt;

&lt;p&gt;Cloud storage (AWS S3, Google Cloud Storage)&lt;/p&gt;

&lt;p&gt;CDN (CloudFront, Cloudflare, Fastly)&lt;/p&gt;

&lt;p&gt;Optional: API Gateway + Backend services&lt;/p&gt;

&lt;p&gt;Deployment Process&lt;br&gt;
bash&lt;/p&gt;

&lt;h1&gt;
  
  
  Build your SPA
&lt;/h1&gt;

&lt;p&gt;npm run build&lt;/p&gt;

&lt;h1&gt;
  
  
  Deploy to S3
&lt;/h1&gt;

&lt;p&gt;aws s3 sync build/ s3://my-bucket/&lt;/p&gt;

&lt;h1&gt;
  
  
  Invalidate CloudFront cache
&lt;/h1&gt;

&lt;p&gt;aws cloudfront create-invalidation --paths "/*"&lt;br&gt;
The Good&lt;br&gt;
✅ Incredibly simple — just copy files&lt;br&gt;
✅ Cheap — storage and bandwidth only&lt;br&gt;
✅ Easy scaling — CDN handles traffic spikes&lt;br&gt;
✅ No server management — zero DevOps overhead&lt;br&gt;
✅ Fast deployments — seconds to push updates&lt;/p&gt;

&lt;p&gt;The Bad&lt;br&gt;
❌ SEO challenges — need pre-rendering or dynamic rendering&lt;br&gt;
❌ Initial load performance — must download all JavaScript&lt;br&gt;
❌ Limited dynamic content — can't personalize per request&lt;/p&gt;

&lt;p&gt;SSR Deployment: Dynamic Complexity&lt;br&gt;
How It Works&lt;br&gt;
Server-Side Rendering executes your application on the server for each request, generating complete HTML pages dynamically.&lt;/p&gt;

&lt;p&gt;Infrastructure Components&lt;br&gt;
text&lt;br&gt;
[Load Balancer] → [Application Servers] → [Cache] → [Browser]&lt;br&gt;
                         ↓&lt;br&gt;
                   [API Services]&lt;br&gt;
Typical Setup:&lt;/p&gt;

&lt;p&gt;Load balancer (ALB, Nginx)&lt;/p&gt;

&lt;p&gt;Application servers (ECS, EC2, Kubernetes)&lt;/p&gt;

&lt;p&gt;In-memory cache (Redis)&lt;/p&gt;

&lt;p&gt;API layer&lt;/p&gt;

&lt;p&gt;Database&lt;/p&gt;

&lt;p&gt;Deployment Process&lt;br&gt;
yaml&lt;/p&gt;

&lt;h1&gt;
  
  
  Simplified docker-compose for SSR
&lt;/h1&gt;

&lt;p&gt;version: '3'&lt;br&gt;
services:&lt;br&gt;
  app:&lt;br&gt;
    build: .&lt;br&gt;
    ports:&lt;br&gt;
      - "3000:3000"&lt;br&gt;
    environment:&lt;br&gt;
      - NODE_ENV=production&lt;br&gt;
    deploy:&lt;br&gt;
      replicas: 3&lt;br&gt;
The Good&lt;br&gt;
✅ SEO friendly — full HTML for crawlers&lt;br&gt;
✅ Better perceived performance — content visible faster&lt;br&gt;
✅ Personalization — per-request rendering&lt;br&gt;
✅ Shared state — server can pre-fetch data&lt;/p&gt;

&lt;p&gt;The Bad&lt;br&gt;
❌ Complex infrastructure — servers, load balancers, caching&lt;br&gt;
❌ Higher costs — compute resources for every request&lt;br&gt;
❌ Cold start issues — especially with serverless&lt;br&gt;
❌ Scaling challenges — stateful rendering&lt;br&gt;
❌ More failure points — infrastructure can fail&lt;/p&gt;

&lt;p&gt;Critical Infrastructure Decisions&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Caching Strategy
SPA:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;text&lt;br&gt;
Cache-Control: public, max-age=31536000, immutable&lt;br&gt;
Versioned assets = infinite cache&lt;/p&gt;

&lt;p&gt;HTML file = short cache or no cache&lt;/p&gt;

&lt;p&gt;SSR:&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
Cache-Control: public, max-age=60&lt;br&gt;
Dynamic caching with invalidation&lt;/p&gt;

&lt;p&gt;Cache per user/region&lt;/p&gt;

&lt;p&gt;Must handle cache stampedes&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scaling
SPA Scaling:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Horizontal scaling is trivial&lt;/p&gt;

&lt;p&gt;CDN handles 99% of traffic&lt;/p&gt;

&lt;p&gt;SSR Scaling:&lt;/p&gt;

&lt;p&gt;Need auto-scaling groups&lt;/p&gt;

&lt;p&gt;Connection pooling&lt;/p&gt;

&lt;p&gt;Database connection limits&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cold Starts
SPA: None. Files are served instantly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;SSR:&lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
// Serverless SSR cold start&lt;br&gt;
exports.handler = async (event) =&amp;gt; {&lt;br&gt;
  // This code runs on every cold start&lt;br&gt;
  const app = require('./app'); // Heavy!&lt;br&gt;
  return app.render(event);&lt;br&gt;
};&lt;br&gt;
Avoid serverless for SSR at high scale&lt;/p&gt;

&lt;p&gt;Use always-on instances&lt;/p&gt;

&lt;p&gt;Or implement warmup strategies&lt;/p&gt;

&lt;p&gt;Real-World Performance Impact&lt;br&gt;
Page Load Time Breakdown&lt;br&gt;
SPA (First Visit):&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
📦 Download HTML: 50ms&lt;br&gt;
📦 Download JavaScript: 800ms&lt;br&gt;
⚡ Parse &amp;amp; Execute: 600ms&lt;br&gt;
🎨 Render: 200ms&lt;br&gt;
Total: ~1650ms to interactive&lt;br&gt;
SSR (First Visit):&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
🖥️ Server Render: 200ms&lt;br&gt;
📦 Download HTML: 150ms&lt;br&gt;
📦 Download JavaScript (hydration): 400ms&lt;br&gt;
🎨 Hydrate: 400ms&lt;br&gt;
Total: ~1150ms to interactive&lt;br&gt;
Cost Comparison (Monthly, 1M visits)&lt;br&gt;
SPA:&lt;/p&gt;

&lt;p&gt;Storage: ~$5&lt;/p&gt;

&lt;p&gt;Bandwidth: ~$100&lt;/p&gt;

&lt;p&gt;CDN: ~$20&lt;/p&gt;

&lt;p&gt;Total: ~$125&lt;/p&gt;

&lt;p&gt;SSR:&lt;/p&gt;

&lt;p&gt;Servers (3 instances): ~$300&lt;/p&gt;

&lt;p&gt;Load Balancer: ~$20&lt;/p&gt;

&lt;p&gt;Cache (Redis): ~$50&lt;/p&gt;

&lt;p&gt;Database: ~$100&lt;/p&gt;

&lt;p&gt;Total: ~$470&lt;/p&gt;

&lt;p&gt;Hybrid Approaches&lt;br&gt;
Static Site Generation (SSG)&lt;br&gt;
Build once, deploy everywhere&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
[Build Server] → [Static Files] → [CDN]&lt;br&gt;
Best of both worlds&lt;/p&gt;

&lt;p&gt;Perfect for content sites&lt;/p&gt;

&lt;p&gt;Limited personalization&lt;/p&gt;

&lt;p&gt;Incremental Static Regeneration&lt;br&gt;
javascript&lt;br&gt;
// Next.js ISR&lt;br&gt;
export async function getStaticProps() {&lt;br&gt;
  return {&lt;br&gt;
    props: { data },&lt;br&gt;
    revalidate: 60 // Regenerate every 60 seconds&lt;br&gt;
  };&lt;br&gt;
}&lt;br&gt;
Edge Rendering&lt;br&gt;
Render at the edge for speed:&lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
// Cloudflare Workers&lt;br&gt;
export default {&lt;br&gt;
  async fetch(request) {&lt;br&gt;
    const html = await renderPage(request);&lt;br&gt;
    return new Response(html, {&lt;br&gt;
      headers: { 'Content-Type': 'text/html' }&lt;br&gt;
    });&lt;br&gt;
  }&lt;br&gt;
};&lt;br&gt;
Making the Choice: Decision Framework&lt;br&gt;
Choose SPA if:&lt;br&gt;
You're building a dashboard/admin panel&lt;/p&gt;

&lt;p&gt;SEO is not critical&lt;/p&gt;

&lt;p&gt;You have a dedicated API layer&lt;/p&gt;

&lt;p&gt;Budget is limited&lt;/p&gt;

&lt;p&gt;Small team with no dedicated DevOps&lt;/p&gt;

&lt;p&gt;Choose SSR if:&lt;br&gt;
SEO is critical for your business&lt;/p&gt;

&lt;p&gt;Personalization is required&lt;/p&gt;

&lt;p&gt;You need fast initial paint&lt;/p&gt;

&lt;p&gt;You have budget for infrastructure&lt;/p&gt;

&lt;p&gt;Team has backend expertise&lt;/p&gt;

&lt;p&gt;Choose SSG if:&lt;br&gt;
Content is mostly static&lt;/p&gt;

&lt;p&gt;Need good SEO&lt;/p&gt;

&lt;p&gt;Want SPA-like hosting simplicity&lt;/p&gt;

&lt;p&gt;Content changes infrequently&lt;/p&gt;

&lt;p&gt;Migration Gotchas&lt;br&gt;
Moving from SPA to SSR&lt;br&gt;
API changes: Need to support server-side data fetching&lt;/p&gt;

&lt;p&gt;Authentication: Session management on server&lt;/p&gt;

&lt;p&gt;Environment variables: Can't use client-side env vars&lt;/p&gt;

&lt;p&gt;Build process: Need Node.js compatibility&lt;/p&gt;

&lt;p&gt;Moving from SSR to SPA&lt;br&gt;
SEO strategy: Need pre-rendering solution&lt;/p&gt;

&lt;p&gt;API exposure: More APIs become public&lt;/p&gt;

&lt;p&gt;Performance: Need to optimize bundle sizes&lt;/p&gt;

&lt;p&gt;State management: Must move state to client&lt;/p&gt;

&lt;p&gt;Monitoring &amp;amp; Observability&lt;br&gt;
SPA Monitoring&lt;br&gt;
javascript&lt;br&gt;
// Client-side monitoring&lt;br&gt;
window.addEventListener('error', (e) =&amp;gt; {&lt;br&gt;
  sendToAnalytics({&lt;br&gt;
    type: 'client_error',&lt;br&gt;
    message: e.message,&lt;br&gt;
    stack: e.error?.stack&lt;br&gt;
  });&lt;br&gt;
});&lt;br&gt;
SSR Monitoring&lt;br&gt;
javascript&lt;br&gt;
// Server-side monitoring&lt;br&gt;
app.use((err, req, res, next) =&amp;gt; {&lt;br&gt;
  logger.error({&lt;br&gt;
    message: err.message,&lt;br&gt;
    stack: err.stack,&lt;br&gt;
    requestId: req.id,&lt;br&gt;
    userId: req.user?.id&lt;br&gt;
  });&lt;br&gt;
  res.status(500).send('Server Error');&lt;br&gt;
});&lt;br&gt;
Key Metrics to Track:&lt;/p&gt;

&lt;p&gt;SPA SSR&lt;br&gt;
Bundle size Render time&lt;br&gt;
Time to Interactive CPU usage&lt;br&gt;
API response times  Memory usage&lt;br&gt;
Client errors   Request queue length&lt;br&gt;
CDN cache hit ratio Garbage collection pauses&lt;br&gt;
Conclusion&lt;br&gt;
The infrastructure differences between SPA and SSR are significant and should influence your decision early in the project lifecycle.&lt;/p&gt;

&lt;p&gt;SPA offers simplicity, cost-effectiveness, and easy scaling at the expense of SEO and initial load performance.&lt;/p&gt;

&lt;p&gt;SSR provides better SEO and perceived performance but demands more complex infrastructure and higher costs.&lt;/p&gt;

&lt;p&gt;There's no "one size fits all" answer. Evaluate your specific requirements:&lt;/p&gt;

&lt;p&gt;Does SEO matter for your business?&lt;/p&gt;

&lt;p&gt;Can you afford the infrastructure costs?&lt;/p&gt;

&lt;p&gt;Do you have the team expertise?&lt;/p&gt;

&lt;p&gt;What are your performance requirements?&lt;/p&gt;

&lt;p&gt;And remember — you're not locked in forever. Many teams start with one approach and migrate as needs evolve. Just plan for the transition if you think you might need it.&lt;/p&gt;

&lt;p&gt;Resources&lt;br&gt;
Tools Mentioned&lt;br&gt;
Next.js - React framework with SSR/SSG&lt;/p&gt;

&lt;p&gt;Vercel - Deployment platform&lt;/p&gt;

&lt;p&gt;AWS Amplify - SPA hosting&lt;/p&gt;

&lt;p&gt;Cloudflare Workers - Edge rendering&lt;/p&gt;

&lt;p&gt;Further Reading&lt;br&gt;
Next.js Deployment Documentation&lt;/p&gt;

&lt;p&gt;AWS S3 Static Website Hosting&lt;/p&gt;

&lt;p&gt;Vercel vs Netlify for SPAs&lt;/p&gt;

&lt;p&gt;What's your experience with SPA vs SSR deployment? Have you migrated between the two? Share your stories in the comments below!&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>devops</category>
      <category>infrastructure</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Serverless Frontends: When to Use Lambda@Edge</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Wed, 24 Jun 2026 15:17:15 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/serverless-frontends-when-to-use-lambdaedge-3h1h</link>
      <guid>https://dev.to/sohanaakbar7/serverless-frontends-when-to-use-lambdaedge-3h1h</guid>
      <description>&lt;p&gt;Serverless is everywhere, and the frontend world has fully embraced it. We've got static sites on S3, APIs on Lambda, and everything managed by the cloud. But there's one piece of the puzzle that often gets overlooked: the edge.&lt;/p&gt;

&lt;p&gt;Lambda@Edge is a powerful tool that lets you run serverless code at AWS's global edge locations, right where your users are. It can supercharge your frontend performance, but it's not always the right solution. Let's dive into when you should use Lambda@Edge for your serverless frontend—and when you should look elsewhere.&lt;/p&gt;

&lt;p&gt;What Actually Is Lambda@Edge?&lt;br&gt;
Lambda@Edge is an extension of AWS Lambda that runs your code in response to CloudFront events . Instead of your code executing in a single region, it runs across CloudFront's global network of edge locations, bringing compute power closer to your users .&lt;/p&gt;

&lt;p&gt;The magic happens through four CloudFront events :&lt;/p&gt;

&lt;p&gt;Viewer Request – When CloudFront receives a request from a user&lt;/p&gt;

&lt;p&gt;Origin Request – Before CloudFront forwards the request to your origin&lt;/p&gt;

&lt;p&gt;Origin Response – When CloudFront receives a response from your origin&lt;/p&gt;

&lt;p&gt;Viewer Response – Before CloudFront returns the response to the user&lt;/p&gt;

&lt;p&gt;This event-driven architecture lets you intercept and modify requests and responses at various points in the content delivery flow .&lt;/p&gt;

&lt;p&gt;The Big Question: Lambda@Edge or CloudFront Functions?&lt;br&gt;
Before we go further, there's a crucial decision you need to make. AWS offers two edge compute options: Lambda@Edge and CloudFront Functions. They're not interchangeable—each serves different purposes.&lt;/p&gt;

&lt;p&gt;Here's the quick comparison :&lt;/p&gt;

&lt;p&gt;Feature CloudFront Functions    Lambda@Edge&lt;br&gt;
Languages   JavaScript (ES5.1)  Node.js, Python&lt;br&gt;
Events  Viewer Request/Response only    All four events&lt;br&gt;
Execution time  &amp;lt; 1ms   5s (viewer) / 30s (origin)&lt;br&gt;
Memory  2 MB    128 MB – 10 GB&lt;br&gt;
Code size   10 KB   1 MB (viewer) / 50 MB (origin)&lt;br&gt;
Network access  ❌ No  ✅ Yes&lt;br&gt;
Request body access ❌ No  ✅ Yes&lt;br&gt;
Scale   Millions/sec    10,000/sec per region&lt;br&gt;
Rule of thumb: Use CloudFront Functions for simple header manipulation, cache key normalization, or lightweight URL rewrites. Reach for Lambda@Edge when you need complex logic, network calls, or access to the request body .&lt;/p&gt;

&lt;p&gt;When Lambda@Edge Shines for Frontends&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Dynamic Content Personalization
Your frontend might need to serve different content based on user location, device type, or preferences. Lambda@Edge can customize responses at the edge without going back to your origin .&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;javascript&lt;br&gt;
// Example: Rewriting URLs based on user-agent&lt;br&gt;
exports.handler = (event, context, callback) =&amp;gt; {&lt;br&gt;
    const request = event.Records[0].cf.request;&lt;br&gt;
    const headers = request.headers;&lt;br&gt;
    const userAgent = headers['user-agent']?.[0]?.value || '';&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (userAgent.includes('Mobile')) {
    request.uri = '/mobile' + request.uri;
}

callback(null, request);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;};&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Server-Side Rendering (SSR) Without Servers
One of the most compelling use cases is running SSR at the edge. Instead of spinning up servers or using API Gateway + Lambda (which adds latency), you can render pages directly at CloudFront edge locations .&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a Nuxt.js app, for instance, you can deploy the SSR logic to Lambda@Edge, with static assets served directly from S3. The result? Global scalability with significantly lower latency than traditional approaches .&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Image Optimization on the Fly
Modern frontends need responsive images for different devices. Lambda@Edge can resize, compress, or transform images in real-time based on device capabilities .&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This offloads processing from your origin and reduces bandwidth costs. A viewer request trigger can detect the device type and request appropriately sized images from your origin or a transformation service.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A/B Testing and Feature Flags
Want to test a new version of your frontend with 10% of users? Lambda@Edge can direct users to different versions based on cookies, headers, or even randomized logic .&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The function runs at the edge, so the routing decision happens before the request reaches your origin. Your origin doesn't even need to know about the test.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Security at the Edge
Protect your frontend by validating JWT tokens, adding security headers (HSTS, CSP), or blocking malicious bots before they reach your origin . This is especially valuable for SPAs or content-heavy applications where you want to prevent unauthorized access to your assets.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;javascript&lt;br&gt;
// Origin request trigger: Validate JWT before hitting origin&lt;br&gt;
exports.handler = (event, context, callback) =&amp;gt; {&lt;br&gt;
    const request = event.Records[0].cf.request;&lt;br&gt;
    const headers = request.headers;&lt;br&gt;
    const token = headers.authorization?.[0]?.value || '';&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (!isValidJWT(token)) {
    const response = {
        status: '401',
        statusDescription: 'Unauthorized',
        body: 'Invalid token'
    };
    callback(null, response);
} else {
    callback(null, request);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;};&lt;br&gt;
Real-World Impact: Performance Matters&lt;br&gt;
A Japanese cloud blog recently tested Lambda@Edge performance and found remarkable results. Using Lambda@Edge without Origin Shield, they achieved consistent ~220ms response times from US, Japan, and Singapore locations .&lt;/p&gt;

&lt;p&gt;The numbers tell a compelling story:&lt;/p&gt;

&lt;p&gt;With Lambda@Edge: ~220ms from Singapore&lt;/p&gt;

&lt;p&gt;Without Lambda@Edge (direct to Lambda URL): ~860ms from Singapore &lt;/p&gt;

&lt;p&gt;That's nearly 4x faster—a massive win for user experience globally.&lt;/p&gt;

&lt;p&gt;When You Should Think Twice&lt;br&gt;
Lambda@Edge isn't a silver bullet. Here are scenarios where it might not be the right fit:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Simple HTTP Manipulations&lt;br&gt;
If all you're doing is adding headers, normalizing cache keys, or simple URL redirects, CloudFront Functions are cheaper, faster to deploy, and easier to manage .&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Heavy Computation or Long-Running Tasks&lt;br&gt;
Lambda@Edge has strict limits:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Viewer triggers: 5 seconds max&lt;/p&gt;

&lt;p&gt;Origin triggers: 30 seconds max &lt;/p&gt;

&lt;p&gt;Complex operations or large file processing belong in a regular Lambda function, not at the edge.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When Edge Complexity Outweighs Benefit
Just because you can move something to the edge doesn't mean you should. Edge compute will always be more constrained than a full AWS region. Consider:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;State management is harder at the edge&lt;/p&gt;

&lt;p&gt;Deployments take minutes (updates must propagate globally) &lt;/p&gt;

&lt;p&gt;Debugging and logging are more complex &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;High-Frequency, Simple Logic at Massive Scale
While Lambda@Edge scales to 10,000 requests/second per region, CloudFront Functions handle millions of requests/second at a fraction of the cost . For massive-scale header manipulation or cache key normalization, CloudFront Functions are the clear winner.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Best Practices for Lambda@Edge Frontends&lt;br&gt;
Cache Everything You Can&lt;br&gt;
Lambda@Edge costs money per request and execution time. Implement aggressive caching strategies to minimize invocations . CloudFront's cache can store 3XX redirects, API responses, and even dynamically generated content .&lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
// In your response, set caching headers&lt;br&gt;
response.headers['cache-control'] = [{&lt;br&gt;
    key: 'Cache-Control',&lt;br&gt;
    value: 'public, max-age=3600, stale-while-revalidate=300'&lt;br&gt;
}];&lt;br&gt;
Mind the 1MB Package Limit&lt;br&gt;
Viewer-triggered Lambda@Edge functions have a 1 MB compressed package limit . If your code requires heavy dependencies:&lt;/p&gt;

&lt;p&gt;Minimize dependencies&lt;/p&gt;

&lt;p&gt;Use lightweight libraries&lt;/p&gt;

&lt;p&gt;Offload heavy work to DynamoDB or S3&lt;/p&gt;

&lt;p&gt;Split functions into smaller pieces &lt;/p&gt;

&lt;p&gt;Watch for Cold Starts&lt;br&gt;
Lambda@Edge can experience cold starts like regular Lambdas . For latency-sensitive frontends, implement warming strategies or keep your function's execution time as low as possible.&lt;/p&gt;

&lt;p&gt;Consider Concurrency Limits&lt;br&gt;
Your account has a default Lambda concurrency limit of 1,000 executions per region. For high-traffic frontends, you may need to request a limit increase . Calculate your needs:&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
Concurrency needed = (requests/second) × (function duration in seconds)&lt;br&gt;
The Verdict&lt;br&gt;
Lambda@Edge is a powerful tool for serverless frontends when you need:&lt;/p&gt;

&lt;p&gt;Global performance – Run code where your users are&lt;/p&gt;

&lt;p&gt;Dynamic content – Personalization, SSR, A/B testing&lt;/p&gt;

&lt;p&gt;Security – Authentication and protection at the edge&lt;/p&gt;

&lt;p&gt;Network access – Calls to databases, APIs, or other services&lt;/p&gt;

&lt;p&gt;But don't reach for it by default. For simple operations, CloudFront Functions are faster, cheaper, and simpler to manage. And for heavy processing that doesn't need to run on every request, keep it in a regular Lambda in your region.&lt;/p&gt;

&lt;p&gt;The edge is an incredible place to run code—but like any powerful tool, use it where it makes the most impact. For the right serverless frontend, Lambda@Edge can be the difference between good global performance and truly exceptional user experiences.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>frontend</category>
      <category>performance</category>
      <category>serverless</category>
    </item>
  </channel>
</rss>
