<?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: Nikola Lalović</title>
    <description>The latest articles on DEV Community by Nikola Lalović (@belikenikola).</description>
    <link>https://dev.to/belikenikola</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1809470%2F03dc1791-1ee6-4197-8544-752b5640be9a.png</url>
      <title>DEV Community: Nikola Lalović</title>
      <link>https://dev.to/belikenikola</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/belikenikola"/>
    <language>en</language>
    <item>
      <title>Recently, I published my first tech blog post about Multi-Brand CDN Architecture for CMS Media Delivery. If you read it, do not hesitate to leave comments and ask questions...</title>
      <dc:creator>Nikola Lalović</dc:creator>
      <pubDate>Sat, 31 Jan 2026 20:52:47 +0000</pubDate>
      <link>https://dev.to/belikenikola/recently-i-published-my-first-tech-blog-post-about-multi-brand-cdn-architecture-for-cms-media-4he4</link>
      <guid>https://dev.to/belikenikola/recently-i-published-my-first-tech-blog-post-about-multi-brand-cdn-architecture-for-cms-media-4he4</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/belikenikola" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1809470%2F03dc1791-1ee6-4197-8544-752b5640be9a.png" alt="belikenikola"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/belikenikola/building-a-multi-brand-cdn-architecture-lessons-from-scaling-cms-media-delivery-4pnh" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Building a Multi-Brand CDN Architecture: Lessons from Scaling CMS Media Delivery&lt;/h2&gt;
      &lt;h3&gt;Nikola Lalović ・ Jan 11&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#aws&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#programming&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#architecture&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>aws</category>
      <category>webdev</category>
      <category>programming</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Why Web Accessibility Matters (And Why It's Not Just About Avoiding Lawsuits)</title>
      <dc:creator>Nikola Lalović</dc:creator>
      <pubDate>Sat, 31 Jan 2026 20:37:32 +0000</pubDate>
      <link>https://dev.to/belikenikola/why-web-accessibility-matters-and-why-its-not-just-about-avoiding-lawsuits-4ep7</link>
      <guid>https://dev.to/belikenikola/why-web-accessibility-matters-and-why-its-not-just-about-avoiding-lawsuits-4ep7</guid>
      <description>&lt;p&gt;If you're running a real estate website in New York, you've probably heard the term "web accessibility" thrown around. Maybe you've even received one of those scary demand letters. But here's the thing — accessibility isn't just a legal checkbox. It's about making sure everyone can use your website, including the people who need it most.&lt;/p&gt;

&lt;p&gt;Let me break this down in a way that actually makes sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Reason Accessibility Matters
&lt;/h2&gt;

&lt;p&gt;Before we talk about lawsuits and compliance standards, let's talk about people.&lt;/p&gt;

&lt;p&gt;About 1 in 4 adults in the United States lives with some form of disability. That's not a small number. These are people looking for apartments, browsing property listings, trying to download your offering memorandum, or filling out a contact form to schedule a viewing.&lt;/p&gt;

&lt;p&gt;When your website isn't accessible, you're essentially putting a "closed" sign in front of a significant portion of your potential clients. Not because you meant to — but because nobody told you the door was locked.&lt;/p&gt;

&lt;p&gt;Making your website accessible means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A person using a screen reader can navigate your property listings&lt;/li&gt;
&lt;li&gt;Someone with limited mobility can use your site with just a keyboard&lt;/li&gt;
&lt;li&gt;A visitor with low vision can actually read your content&lt;/li&gt;
&lt;li&gt;Anyone can download and read your PDFs with assistive technology&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't about compliance. This is about treating people with respect and giving everyone equal access to your business.&lt;/p&gt;

&lt;h2&gt;
  
  
  Okay, But Let's Talk About the Legal Side Too
&lt;/h2&gt;

&lt;p&gt;I won't pretend the legal landscape doesn't exist — especially in New York.&lt;/p&gt;

&lt;p&gt;New York has become one of the most active states for web accessibility lawsuits. Law firms actively scan websites for accessibility issues, and real estate companies are frequent targets. Why? Because real estate sites typically have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PDF documents (offering memorandums, brochures, lease agreements)&lt;/li&gt;
&lt;li&gt;Contact forms&lt;/li&gt;
&lt;li&gt;Property search features&lt;/li&gt;
&lt;li&gt;Image-heavy listings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these are common failure points for accessibility.&lt;/p&gt;

&lt;p&gt;The standard that courts look to is &lt;strong&gt;WCAG 2.1 Level AA&lt;/strong&gt; — a set of guidelines that define what makes a website accessible. If your site doesn't meet this standard, you're potentially exposed to litigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Works (And What Doesn't)
&lt;/h2&gt;

&lt;p&gt;Here's where I need to be direct with you: &lt;strong&gt;accessibility widgets don't solve the problem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You've probably seen those little accessibility icons on websites — tools like accessiBe, UserWay, and others. They promise one-click compliance. It sounds great, right?&lt;/p&gt;

&lt;p&gt;The reality is different. Courts increasingly reject these overlay tools as proof of compliance. Multiple lawsuits have explicitly named websites that already had widgets installed. Why?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Overlay tools don't fix the underlying code problems&lt;/li&gt;
&lt;li&gt;Screen reader users often disable or avoid these overlays because they interfere with their assistive technology&lt;/li&gt;
&lt;li&gt;They create a false sense of security&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Installing a widget and claiming compliance can actually &lt;em&gt;increase&lt;/em&gt; your legal risk because it shows you were aware of the issue but chose a shortcut instead of a real solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Reduces Risk
&lt;/h2&gt;

&lt;p&gt;Real accessibility comes from building it into your website properly. Here's what that looks like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In the code itself:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proper heading structure (H1, H2, H3 in logical order)&lt;/li&gt;
&lt;li&gt;Alt text on every meaningful image&lt;/li&gt;
&lt;li&gt;Keyboard navigation that works throughout the site&lt;/li&gt;
&lt;li&gt;Forms with proper labels and error messages&lt;/li&gt;
&lt;li&gt;Sufficient color contrast&lt;/li&gt;
&lt;li&gt;Focus indicators for interactive elements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For your documents:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PDFs that are properly tagged and readable by screen readers&lt;/li&gt;
&lt;li&gt;Accessible forms that can be filled out with assistive technology&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For your process:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An accessibility statement on your website with a contact method for reporting issues&lt;/li&gt;
&lt;li&gt;Regular audits (at least annually, and after any major redesign)&lt;/li&gt;
&lt;li&gt;Documentation of your accessibility efforts&lt;/li&gt;
&lt;li&gt;A remediation process when issues are reported&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is what defense attorneys actually want to show in court — evidence of ongoing, good-faith effort to maintain accessibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tools We Use
&lt;/h2&gt;

&lt;p&gt;You don't need expensive consultants to get started. Here are practical tools that help identify issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;axe DevTools&lt;/strong&gt; — browser extension that catches many WCAG violations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WAVE&lt;/strong&gt; — visual feedback about accessibility issues on your page&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lighthouse&lt;/strong&gt; — built into Chrome, gives you a basic accessibility score&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NVDA / VoiceOver&lt;/strong&gt; — actual screen readers for manual testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key is making accessibility part of your development workflow, not an afterthought.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Better Way to Think About This
&lt;/h2&gt;

&lt;p&gt;Here's my perspective: accessibility and good web design are the same thing.&lt;/p&gt;

&lt;p&gt;A website that's easy to navigate with a keyboard is also easier to navigate for everyone. Clear headings help screen reader users, but they also help every visitor scan your content. Proper color contrast isn't just for people with low vision — it helps anyone viewing your site on a phone in bright sunlight.&lt;/p&gt;

&lt;p&gt;When you build with accessibility in mind, you build a better website. Period.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means for Your Business
&lt;/h2&gt;

&lt;p&gt;If you're a real estate company in New York, accessibility should be part of your website strategy — not because you're scared of lawsuits, but because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It opens your business to more potential clients&lt;/li&gt;
&lt;li&gt;It demonstrates that you care about all members of your community&lt;/li&gt;
&lt;li&gt;It protects you legally (yes, this matters too)&lt;/li&gt;
&lt;li&gt;It results in a better website for everyone&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The good news is that proper accessibility isn't prohibitively expensive or complicated. It requires attention and expertise, but it's entirely achievable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving Forward
&lt;/h2&gt;

&lt;p&gt;If you're concerned about your website's accessibility, here's a practical starting point:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run your site through an automated tool like WAVE or axe&lt;/li&gt;
&lt;li&gt;Try navigating your site using only your keyboard&lt;/li&gt;
&lt;li&gt;Check if your PDFs can be read by a screen reader&lt;/li&gt;
&lt;li&gt;Review your forms for proper labeling&lt;/li&gt;
&lt;li&gt;Look at your color contrast ratios&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These simple checks will tell you a lot about where you stand.&lt;/p&gt;

&lt;p&gt;Accessibility isn't a one-time fix — it's an ongoing commitment. But it's a commitment worth making, both for your business and for the people you serve.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>a11y</category>
      <category>complience</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building a Multi-Brand CDN Architecture: Lessons from Scaling CMS Media Delivery</title>
      <dc:creator>Nikola Lalović</dc:creator>
      <pubDate>Sun, 11 Jan 2026 13:29:13 +0000</pubDate>
      <link>https://dev.to/belikenikola/building-a-multi-brand-cdn-architecture-lessons-from-scaling-cms-media-delivery-4pnh</link>
      <guid>https://dev.to/belikenikola/building-a-multi-brand-cdn-architecture-lessons-from-scaling-cms-media-delivery-4pnh</guid>
      <description>&lt;p&gt;When you're managing multiple brands under one umbrella, serving media assets efficiently while maintaining brand identity and SEO performance becomes a unique challenge. Recently, I architected and implemented a solution that serves CMS media across 7 different brand domains using AWS CloudFront, improving performance, security, and SEO in one go.&lt;/p&gt;

&lt;p&gt;Here's the story of how we did it, the challenges we faced, and the lessons learned along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;Our organization operates multiple consumer brands across different markets. Each brand has its own website, but all content is managed through a single headless CMS. This setup is great for operational efficiency, but it creates some interesting technical challenges:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problems
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Security Risk&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Media files were stored in a publicly accessible Amazon S3 bucket. While convenient, this meant anyone with the bucket URL could access assets directly, bypassing our CDN and potentially causing bandwidth costs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. SEO Limitations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All images were served from a generic S3 domain (&lt;code&gt;bucket-name.s3.amazonaws.com&lt;/code&gt;). This meant no SEO benefit for individual brand domains from image searches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Performance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Direct S3 access meant no edge caching. Users far from our S3 region experienced slower load times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Multi-Tenancy Complexity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We needed each brand to appear as if it had its own dedicated image infrastructure, while actually sharing the same underlying storage and CDN.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Zero-Downtime Requirement&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We couldn't afford any downtime during migration. Thousands of existing URLs were already in production across multiple websites.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution Architecture
&lt;/h2&gt;

&lt;p&gt;The solution leverages AWS CloudFront's multi-domain capabilities combined with intelligent folder-based routing. Here's how it works:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4owgk7khfjcra3ljux50.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4owgk7khfjcra3ljux50.png" alt=" " width="800" height="1328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Architecture Decisions
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Single CloudFront Distribution with Multiple Aliases
&lt;/h4&gt;

&lt;p&gt;Instead of creating separate distributions for each brand, we use one distribution with multiple domain aliases (CNAMEs). This simplifies management while still allowing brand-specific domains.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Origin Access Control (OAC) for Security
&lt;/h4&gt;

&lt;p&gt;We implemented Origin Access Control to ensure S3 bucket access is only possible through CloudFront. Direct S3 URLs return 403 Forbidden. This was a critical security improvement over the previous public bucket setup.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Brand-Aware Upload Provider
&lt;/h4&gt;

&lt;p&gt;The CMS uses a custom upload provider that intelligently maps upload folders to CDN domains. When content creators upload to Brand A's folder, the provider automatically generates a URL using &lt;code&gt;images.brand-a.com&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Folder-Based Multi-Tenancy
&lt;/h4&gt;

&lt;p&gt;The S3 bucket structure uses simple folder prefixes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/brand-a/&lt;/code&gt; → Brand A assets&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/brand-b/&lt;/code&gt; → Brand B assets&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/shared/&lt;/code&gt; → Cross-brand assets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps everything in one bucket while maintaining logical separation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Highlights
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Custom Strapi Upload Provider
&lt;/h3&gt;

&lt;p&gt;We built a custom upload provider for our Strapi CMS that handles the folder-to-domain mapping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;brandPaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 0: Shared&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;brand-a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;brand-b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 2&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;brand-c&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 3&lt;/span&gt;
  &lt;span class="c1"&gt;// ... etc&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;brandCdnUrls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;brand-a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://images.brand-a.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;brand-b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://images.brand-b.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;brand-c&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://images.brand-c.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://images.company.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getCdnUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;folderIndex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;brand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;brandPaths&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;folderIndex&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;brandCdnUrls&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;brandCdnUrls&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shared&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The provider also handles image variants (thumbnails, responsive sizes) by ensuring they follow the same folder structure as their original images.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Multi-Domain SSL Certificate
&lt;/h3&gt;

&lt;p&gt;We used AWS Certificate Manager (ACM) to create a single certificate with Subject Alternative Names (SANs) for all brand domains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws acm request-certificate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--domain-name&lt;/span&gt; images.company.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--subject-alternative-names&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    images.brand-a.com &lt;span class="se"&gt;\&lt;/span&gt;
    images.brand-b.com &lt;span class="se"&gt;\&lt;/span&gt;
    images.brand-c.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--validation-method&lt;/span&gt; DNS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important Note&lt;/strong&gt;: CloudFront requires certificates in the &lt;code&gt;us-east-1&lt;/code&gt; region, regardless of where your distribution actually serves content.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. DNS Configuration
&lt;/h3&gt;

&lt;p&gt;Each brand domain gets a simple CNAME record pointing to the CloudFront distribution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;images.brand-a.com → CNAME → d1234abcd.cloudfront.net
images.brand-b.com → CNAME → d1234abcd.cloudfront.net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. CORS Configuration
&lt;/h3&gt;

&lt;p&gt;Since the CMS admin panel needs to preview images, we configured CORS through CloudFront Response Headers Policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"AccessControlAllowOrigins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://cms.company.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:1337"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"AccessControlAllowHeaders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"AccessControlAllowMethods"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HEAD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OPTIONS"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"OriginOverride"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Migration Challenge
&lt;/h2&gt;

&lt;p&gt;The trickiest part wasn't building the infrastructure—it was migrating existing content without breaking anything.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database URLs Everywhere
&lt;/h3&gt;

&lt;p&gt;Legacy S3 URLs weren't just in simple URL fields. They were buried:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In main URL columns&lt;/li&gt;
&lt;li&gt;Inside JSON fields (image variants: thumbnail, small, medium, large)&lt;/li&gt;
&lt;li&gt;In rich text content fields&lt;/li&gt;
&lt;li&gt;In cached API responses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We had to write careful SQL migrations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Main URLs&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'https://bucket-name.s3.region.amazonaws.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'https://images.company.com'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%bucket-name.s3.region.amazonaws.com%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- JSON columns (PostgreSQL)&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;formats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;formats&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'https://bucket-name.s3.region.amazonaws.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'https://images.company.com'&lt;/span&gt;
&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="n"&gt;jsonb&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;formats&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%bucket-name.s3.region.amazonaws.com%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Cache Gotcha
&lt;/h3&gt;

&lt;p&gt;Here's a mistake we made: We migrated the database URLs on Friday evening. On Monday morning, we noticed some images were still being served with old S3 URLs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The culprit?&lt;/strong&gt; Our CMS had an LRU (Least Recently Used) response cache that was serving stale API responses for hours. The cache had to be invalidated manually, which we hadn't planned for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson&lt;/strong&gt;: Map all your caching layers before migration. Application caches, CDN caches, browser caches—they all matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phased Rollout Strategy
&lt;/h3&gt;

&lt;p&gt;We couldn't flip a switch and move everything at once. Here's how we did it safely:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 1: Setup Infrastructure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Set up CloudFront distribution with OAC, but kept S3 public. Both old and new URLs worked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 2: Update CMS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Update the CMS provider to generate new CDN URLs for new uploads. Old content still used S3 URLs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 3: Test in Staging&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Run database migrations in staging. Test extensively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 4: Production Migration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Run database migrations in production during low-traffic hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 5: Cache Invalidation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Invalidate all caches (CloudFront, application, API).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 6: Monitor&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Monitor for 2 weeks. Once confident, remove public S3 access.&lt;/p&gt;

&lt;p&gt;The key was maintaining dual access (both old S3 URLs and new CDN URLs working) until we were 100% certain everything was migrated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Improvements
&lt;/h2&gt;

&lt;p&gt;The results were immediately noticeable:&lt;/p&gt;

&lt;h3&gt;
  
  
  Latency Reduction
&lt;/h3&gt;

&lt;p&gt;Average image load times dropped by &lt;strong&gt;~60%&lt;/strong&gt; for users in distant geographic locations, thanks to CloudFront's 400+ edge locations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Origin Offload
&lt;/h3&gt;

&lt;p&gt;Over &lt;strong&gt;95%&lt;/strong&gt; of image requests now hit the CloudFront cache, dramatically reducing load on our S3 bucket.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bandwidth Costs
&lt;/h3&gt;

&lt;p&gt;CloudFront data transfer is cheaper than S3, and we're serving more traffic for less money.&lt;/p&gt;

&lt;h3&gt;
  
  
  SEO Impact
&lt;/h3&gt;

&lt;p&gt;Within weeks, we saw image search traffic increase as brand-specific image domains began building authority in Google Image Search.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Lessons Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Plan Database Migration Early
&lt;/h3&gt;

&lt;p&gt;Don't underestimate where URLs might be hiding. Use database-wide text searches to find all occurrences before you start.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Map Your Caching Strategy
&lt;/h3&gt;

&lt;p&gt;Know every layer of caching in your system. Application caches can silently serve stale data even after you've updated the database.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Keep Legacy Access During Transition
&lt;/h3&gt;

&lt;p&gt;Maintaining dual access (old S3 URLs + new CDN URLs) gives you a safety net. Don't burn bridges until you're sure you're across.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Use Infrastructure as Code
&lt;/h3&gt;

&lt;p&gt;We used AWS CLI scripts for everything, which made replicating the setup across staging and production environments trivial. CloudFormation or Terraform would have been even better.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Origin Access Control &amp;gt; Origin Access Identity
&lt;/h3&gt;

&lt;p&gt;If you're starting fresh, use OAC (Origin Access Control) instead of the older OAI (Origin Access Identity). OAC supports more features and is AWS's recommended approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Monitor Distribution Deployment Times
&lt;/h3&gt;

&lt;p&gt;CloudFront distribution updates can take 15-30 minutes to deploy across all edge locations. Plan your deployment windows accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Test Certificate DNS Validation Early
&lt;/h3&gt;

&lt;p&gt;DNS validation for ACM certificates can sometimes take time, especially if you have multiple domains across different DNS providers. Start this process early.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. The Architecture Scales Beautifully
&lt;/h3&gt;

&lt;p&gt;Adding a new brand now takes less than an hour: create a folder in S3, add a DNS record, update the provider mapping. The infrastructure doesn't need to change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Considerations
&lt;/h2&gt;

&lt;p&gt;The total cost breakdown (approximate, for reference):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CloudFront Distribution&lt;/strong&gt;: $0 monthly fee (pay per use)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Transfer&lt;/strong&gt;: ~$0.085/GB (first 10 TB)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Requests&lt;/strong&gt;: ~$0.0075 per 10,000 HTTP requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ACM Certificate&lt;/strong&gt;: Free (for use with CloudFront)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 Storage&lt;/strong&gt;: Same as before (~$0.023/GB)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our total costs actually &lt;strong&gt;decreased&lt;/strong&gt; because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;CloudFront caching reduced S3 GET requests (which have a cost)&lt;/li&gt;
&lt;li&gt;CloudFront data transfer is cheaper than S3 data transfer&lt;/li&gt;
&lt;li&gt;We consolidated multiple buckets into one&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  When Should You Use This Architecture?
&lt;/h2&gt;

&lt;p&gt;This approach makes sense when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ You're managing multiple brands under one organization&lt;/li&gt;
&lt;li&gt;✅ SEO from brand-specific domains matters to your business&lt;/li&gt;
&lt;li&gt;✅ You need to improve global content delivery performance&lt;/li&gt;
&lt;li&gt;✅ You want to enhance security by restricting S3 access&lt;/li&gt;
&lt;li&gt;✅ You're using a headless CMS with an extensible upload provider&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It might be overkill if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ You only have one brand&lt;/li&gt;
&lt;li&gt;❌ Your audience is geographically concentrated near your S3 region&lt;/li&gt;
&lt;li&gt;❌ You don't care about brand-specific SEO for images&lt;/li&gt;
&lt;li&gt;❌ You're using a SaaS CMS without customization options&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;We're considering a few enhancements:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Adaptive Image Formats
&lt;/h3&gt;

&lt;p&gt;Automatically serving WebP/AVIF to supporting browsers&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Smart Image Optimization
&lt;/h3&gt;

&lt;p&gt;On-the-fly resizing using Lambda@Edge&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Geo-based Routing
&lt;/h3&gt;

&lt;p&gt;Serving region-specific assets based on user location&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Analytics Integration
&lt;/h3&gt;

&lt;p&gt;Better tracking of which brands' assets are most popular&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building a multi-brand CDN architecture taught me that the technical implementation is often the easy part. The real challenges are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Managing migration of existing content&lt;/li&gt;
&lt;li&gt;Understanding all the layers of caching in your system&lt;/li&gt;
&lt;li&gt;Planning for zero-downtime transitions&lt;/li&gt;
&lt;li&gt;Making architecture decisions that scale as you grow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The beauty of this solution is its simplicity: one S3 bucket, one CloudFront distribution, simple folder-based routing, and brand-aware URL generation. It scales effortlessly and costs less than more complex alternatives.&lt;/p&gt;

&lt;p&gt;If you're facing similar multi-tenant infrastructure challenges, I hope this post gives you a solid starting point. Feel free to adapt the architecture to your specific needs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03ql048gwm7qbybo04fx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03ql048gwm7qbybo04fx.png" alt=" " width="800" height="531"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Have questions or faced similar challenges?&lt;/strong&gt; I'd love to hear about your experiences with multi-brand architectures, CDN strategies, or zero-downtime migrations. What worked for you? What would you do differently?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Useful Resources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cloudfront/" rel="noopener noreferrer"&gt;AWS CloudFront Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html" rel="noopener noreferrer"&gt;Origin Access Control Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/CNAMEs.html" rel="noopener noreferrer"&gt;CloudFront Multi-Domain Setup Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>webdev</category>
      <category>programming</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
