惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LINUX DO - 最新话题
Cyberwarzone
Cyberwarzone
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Security Archives - TechRepublic
Security Archives - TechRepublic
S
Securelist
V2EX - 技术
V2EX - 技术
www.infosecurity-magazine.com
www.infosecurity-magazine.com
P
Privacy & Cybersecurity Law Blog
Spread Privacy
Spread Privacy
N
News and Events Feed by Topic
H
Heimdal Security Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
大猫的无限游戏
大猫的无限游戏
L
LangChain Blog
爱范儿
爱范儿
阮一峰的网络日志
阮一峰的网络日志
G
GRAHAM CLULEY
L
Lohrmann on Cybersecurity
G
Google Developers Blog
Recorded Future
Recorded Future
H
Hacker News: Front Page
Application and Cybersecurity Blog
Application and Cybersecurity Blog
The GitHub Blog
The GitHub Blog
量子位
V
V2EX
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Vercel News
Vercel News
H
Help Net Security
Know Your Adversary
Know Your Adversary
Forbes - Security
Forbes - Security
T
Threatpost
S
SegmentFault 最新的问题
Hugging Face - Blog
Hugging Face - Blog
T
Threat Research - Cisco Blogs
人人都是产品经理
人人都是产品经理
Project Zero
Project Zero
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
罗磊的独立博客
C
Check Point Blog
P
Palo Alto Networks Blog
Google DeepMind News
Google DeepMind News
Last Week in AI
Last Week in AI
L
LINUX DO - 热门话题
Apple Machine Learning Research
Apple Machine Learning Research
C
Cybersecurity and Infrastructure Security Agency CISA
A
Arctic Wolf

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
White Labeling in Angular: One Codebase, Multiple Clients
Pierre Bouil · 2026-05-04 · via DEV Community

White labeling is more common than you might think. When developing software, you often need to deploy the same application for multiple clients, each requiring their own customization: unique color palettes, logos, or specific variants for a link.

Without a proper strategy, you might be tempted to simply clone the existing repository and implement client-specific changes on demand.

However, this approach has a major drawback: maintenance hell. Every time a feature is added, or a bug is fixed, you must manually propagate that change across every single clone. This might be manageable for two or three instances, but it quickly becomes impossible to maintain once you reach a dozen clients or face a complex architectural shift.

In this article, we will explore how to create a white labelled Angular app that supports multiple targets efficiently. We will start by defining white labeling, then discuss how to design an Angular app around this concept, and finally, how to leverage Angular’s tools to achieve this in a scalable way.

Introduction to White Labeling

If you're not familiar with the term, white labeling is about creating a generic solution that you can publish multiple times under different brands.

In practice, you build your product once, then deploy it many times with different logos, colors or text. Think of it like a soda sold under different packaging: the liquid remains the same, but the product appears distinct.

The main benefit of this approach is efficiency: you develop and maintain one application instead of many, which drastically reduces the cost of onboarding new clients or updating existing ones.
That said, implementing it with enough flexibility is often a challenge. A well-designed white-labelled application separates shared functionality from brand-specific customization, letting you add new brands with minimal effort, usually through configuration rather than code changes. The key constraint is that this shared layer must not interfere with core feature development; brand-specific elements need to stay isolated so they don't complicate the evolution of your main features.

Even more important: you definitely don't want the assets of one brand leaking into the build of another.

Any software that needs to serve multiple brands from a single codebase can benefit from this: mobile apps, web services, desktop applications. The principle is technology-agnostic.

Case Study

Here is our next successful SaaS we will be adapting as a white-labelled app

Base app

For now it's rather empty, but we can already identify three main areas we will want to customize: the stylesheet (what if our brand uses a specific color?), the assets (a brand will likely have its own logo), and configuration keys such as the brand name.

In this case, let's work towards setting up a build tailored for Angular. Ideally, we would have a centralized place where all our build-specific files would live, something like:

.
├── public/             ← default assets
│   ├── favicon.ico
│   └── main.png
├── src/                ← core application
└── targets/            ← defines all the brands
    └── angular/        ← contains the angular build specific files

Enter fullscreen mode Exit fullscreen mode

Defining the New Target

Our goal is to run this:

pnpm build --configuration angular

Enter fullscreen mode Exit fullscreen mode

This should generate a bundle that contains the core of the application, but with everything defined in targets/angular/ overriding the default content.

Since that will impact the build step, our journey will start in the angular.json file, by defining the new angular target:

{
  ...
  "projects": {
    "architect": {
      "build": {
        "builder": "@angular/build:application",
          "options": { ... },
          "configurations": {
            "production": { ... },    
            "development": { ... },
            "angular": { }
          }
        }
      }
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

That alone won't be enough, but we can already execute the command without an error:

angular-build-target-creation

Replacing Images

The fastest way to recognize a brand at a glance is the logo, and Angular has a great one.

Following our initial goal, we will create it in targets/angular/assets/main.png. Using the same name across targets is a good convention that makes it easy to identify which file is used where without too many variants everywhere.

.
├── public/
│   ├── favicon.ico
│   └── main.png
├── src/
└── targets/
    └── angular/
        └── assets/
            └── main.png

Enter fullscreen mode Exit fullscreen mode

By using the same name, we can take advantage of how the Angular architect handles the assets. In the angular.json, the assets array defines a set of targets that will be copied to the output directory. If two entries share the same name, the last one will override the first.

In our case, we can instruct it to first copy the content of public/ with all default assets for our app, and then copy the content of targets/angular/assets to override those defaults:

{
  "angular": {
    "assets": [
      {
        "glob": "**/*",
        "input": "public"
      },
      {
        "glob": "**/*",
        "input": "targets/angular/assets"
      }
    ]
  }
}

Enter fullscreen mode Exit fullscreen mode

{
  "build": {
    "configuration": {
      "angular": {
        "assets": [
          {
            "glob": "**/*",
            "input": "public"
          },
          {
            "glob": "**/*",
            "input": "targets/angular/assets"
          }
        ]
      }
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

With that change, rebuilding our app outputs a new bundle that uses the assets we defined:

add-assets-to-build-target

Great! But now the text feels a bit off, let's fix that.

Replacing Styles

I'm not very good with design and CSS, but that plain black text alongside the Angular logo looks a bit aggressive, it would be nice to have some purple in there.

In our example app, we have the following styles:

/* 📂 theme.css */
:root {
  --brand-primary: black;
}

/* 📂 styles.css */
html {
  color: var(--brand-primary);
}

Enter fullscreen mode Exit fullscreen mode

These styles are referenced in angular.json under architect.build.options:

{
  "build": {
    "configuration": {
      "options": {
        ...
        "styles": ["src/styles.css", "src/theme.css"]
      }
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

For our angular configuration, we do not want to change the base styles.css which will probably handle a lot of customization, specifics, and even TailwindCSS layers maybe. However, overriding the CSS variables is something we can do without causing any harm.

For that, let's first design our own theme in targets/angular/styles/theme.css:

.
├── public/
├── src/
└── targets/
    └── angular/
        ├── assets/
        │   └── main.png
        └── styles/
            └── theme.css

Enter fullscreen mode Exit fullscreen mode

We can now add some purple in there:

:root {
  --brand-primary: purple;
}

Enter fullscreen mode Exit fullscreen mode

Now that everything is in place, we can simply instruct Angular to use this theme file when we are building the build configuration:

{
  "build": {
    "configuration": {
      "angular": {
          "assets": [...],
          "styles": ["src/styles.css", "targets/angular/styles/theme.css"]
        ]
      }
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

Let's rebuild our app, and marvel at our design decision:

add-styles-to-build-target

Replacing TypeScript Files

The design looks fine (right?) but the content is not quite there yet: we are definitely not publishing for white label, yet the title says otherwise. Let's look at how the component is defined:

// 📂 src/app/app.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { environment } from '../environment';

@Component({
  selector: 'app-root',
  template: `
    <h1>Welcome to {{ targetName }}</h1>
    <img src="main.png" alt="brand logo" height="80px" width="auto" />
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App {
  protected readonly targetName = environment.targetName;
}

Enter fullscreen mode Exit fullscreen mode

The name is defined neither by the CSS nor by the assets, but by a value read from an environment.ts file:

// 📂 environment.ts
export const environment = {
  targetName: 'white label',
};

Enter fullscreen mode Exit fullscreen mode

For this use case, we can take advantage of another option of the Angular architect's target: fileReplacements. This property allows you to define a collection of file replacements, each mapping a source path to the file that should replace it.

In our case, let's replace this environment file with our own:

.
├── public/
│   ├── favicon.ico
│   └── main.png
├── src/
└── targets/
    └── angular/
        ├── assets/
        │   └── main.png
        ├── overrides/
        │   └── environment.ts
        └── styles/
            └── theme.css

Enter fullscreen mode Exit fullscreen mode

// 📂 targets/angular/overrides/environment.ts
export const environment = {
  targetName: 'Angular',
};

Enter fullscreen mode Exit fullscreen mode

We can then instruct Angular to use this file instead of the default one:

{
  "build": {
    "configuration": {
      "angular": {
          "assets": [...],
          "styles": ["src/styles.css", "targets/angular/styles/theme.css"],
          "fileReplacements": [
          {
            "replace": "src/environment.ts",
            "with": "targets/angular/overrides/environment.ts"
          }
        ]
      }
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

Finally, rebuilding our app one more time will generate an output rendered as:

add-environment-to-build-target

📝 Note
In this specific case, we just changed the title, but if we were to have multiple backends, a different CDN or another specific configuration, we would now be able to swap any of those at build time. Of course, overriding other files simply requires adding them to the fileReplacements array too.

That's it! After a few steps we successfully adapted the core application for a specific build target without touching the application itself.

Let's ensure everything is working by adding a new target:

.
├── public/
│   ├── favicon.ico
│   └── main.png
├── src/
└── targets/
    ├── angular/
    │   ├── assets/
    │   │   └── main.png
    │   ├── overrides/
    │   │   └── environment.ts
    │   └── styles/
    │       └── theme.css
    └── red-corp/
        ├── assets/
        │   └── main.png
        ├── overrides/
        │   └── environment.ts
        └── styles/
            └── theme.css

Enter fullscreen mode Exit fullscreen mode

And creating its associated build target:

{
  "build": {
    "configuration": {
      "angular": {
        "assets": [...],
        "styles": ["src/styles.css", "targets/angular/styles/theme.css"],
        "fileReplacements": [...]
      },
+     "red-corp": {
+       "assets": [...],
+       "styles": ["src/styles.css", "targets/red-corp/styles/theme.css"],
+       "fileReplacements": [
+         {
+           "replace": "src/environment.ts",
+           "with": "targets/red-corp/overrides/environment.ts"
+         }
+       ]
+     }
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

Finally, we can run pnpm ng b --configuration red-corp, which will generate the following output that will serve:

build-target-red-corp

Congrats!

Automating Brand Creation with Schematics

Despite working well, the addition of a new target can be a bit tedious and error prone if done manually.

Fortunately, Angular has a concept designed for that exact purpose: schematics:

A schematic is a template-based code generator that supports complex logic. It is a set of instructions for transforming a software project by generating or modifying code. Schematics are packaged into collections and installed with npm.

That sounds like something we are already doing: creating a bunch of folders and authoring angular.json.

Since this is not the focus of the article, I won't dive into the implementation details here, but you are welcome to browse the article example's sources to see how it's done.

Adding a new target is now a breeze:

ng-schematic-usage

Conclusion

In this article, we explored how to build a white-labelled Angular application that supports multiple clients from a single codebase.

We started by defining what white labeling is and why a naive approach leads to maintenance problems at scale.

We then worked through a concrete example, progressively introducing Angular's build configuration to override assets, styles and TypeScript files on a per-target basis.

Finally, we saw how schematics can remove the remaining manual steps, making the addition of a new client a matter of running a single command.

The result is a setup where the core application remains untouched regardless of how many brands you support, and where onboarding a new client is just a matter of dropping files into a new folder and wiring up a configuration.

If you would like to play around with the example, feel free to browse the sources on GitHub!

Photo by Andrzej Gdula on Unsplash