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

推荐订阅源

博客园 - 【当耐特】
小众软件
小众软件
S
SegmentFault 最新的问题
GbyAI
GbyAI
量子位
爱范儿
爱范儿
L
LangChain Blog
Vercel News
Vercel News
A
About on SuperTechFans
腾讯CDC
博客园_首页
酷 壳 – CoolShell
酷 壳 – CoolShell
月光博客
月光博客
博客园 - 聂微东
Stack Overflow Blog
Stack Overflow Blog
H
Help Net Security
U
Unit 42
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
V
V2EX
V
Visual Studio Blog
美团技术团队
D
DataBreaches.Net
The GitHub Blog
The GitHub Blog
N
Netflix TechBlog - Medium

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
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