top of page

UI per Tenant

  • Writer: Anand Nerurkar
    Anand Nerurkar
  • Apr 26
  • 7 min read

Updated: Apr 29

how do we diesign UI for multi tenat saas architecture


Great question! Designing the UI for a multi-tenant SaaS architecture requires both technical and UX considerations to support multiple tenants (clients/organizations), each potentially with their own branding, configurations, roles, and permissions—while sharing a single codebase.


Here's a structured breakdown of how to approach UI design for a multi-tenant SaaS system:

🔷 1. Tenant-Aware UI Design Principles

✅ Shared UI with Tenant Context

  • Use a single UI codebase that dynamically adapts to the tenant.

  • Pass tenant ID or slug (e.g., app.com/tenant1) to the frontend at login time or embed in token.

  • Fetch tenant-specific settings from a Tenant Config Service.


🔷 2. Tenant-Specific Branding & Theming

💡 Strategy:

  • Allow each tenant to configure:

🛠️ How:

  • Use a theming engine (e.g., Tailwind + CSS variables or Material-UI theme overrides).

  • Fetch brand settings via API on login or initial load and cache locally.

  • Apply branding dynamically using context-aware ThemeProvider.

js

<ThemeProvider theme={tenantTheme}> <App /> </ThemeProvider>

🔷 3. Role-Based Access Control (RBAC)

👥 Per-tenant RBAC

  • UI must dynamically adjust menus, routes, and actions based on:

    • Tenant role

    • User permissions (read-only, admin, approver)

🛠️ How:

  • Centralized route config with metadata for access control.

  • Show/hide components or menu items based on user roles.

  • Use React Context or Redux for user + role data injection.


🔷 4. Feature Flag Support per Tenant

  • Enable/disable features per tenant using a feature flag service (e.g., LaunchDarkly, Unleash).

  • This allows controlled rollout, A/B testing, or premium features.

ts

if (flags['show-new-dashboard']) { return <NewDashboard />; } else { return <LegacyDashboard />; }


🔷 5. Tenant-aware Routing & URLs

  • Use subdomain-based or path-based routing:

Approach

Example

Subdomain-based

Path-based

  • Ensure all API calls and internal links pass the tenant ID appropriately.


🔷 6. Tenant-Specific Localization & Content

  • Allow tenants to define custom terms (e.g., “Employee” → “Crew Member”).

  • Store in i18n files or tenant-specific content DB.

  • Load language packs or content overrides at runtime.


🔷 7. Onboarding and Config Wizards

  • Provide UI wizards for:

    • Admin onboarding (company info, users, roles)

    • Tenant-specific configurations

    • Billing or subscription setup


🔷 8. Error Handling & SLA Awareness

  • Display tenant-specific support links or escalation options.

  • Track SLA breaches and errors using telemetry and show user-friendly fallback UIs.


🔷 9. Scalable UI Architecture

  • Follow component-based design, e.g., React with Storybook for reusable components.

  • Each component should accept tenantSettings or theme as a prop/context.

  • Consider Micro Frontends if large teams work on isolated modules.

🔷 Example Folder Structure (React)

bash

/src /themes tenant-default.js tenant-acme.js /contexts TenantContext.js AuthContext.js /components NavBar.js Sidebar.js Dashboard.js /services tenant-config.service.js auth.service.js /routes tenantRoutes.js

💡 Pro Tip:

Build a Tenant Switcher Admin Panel if you need to impersonate or troubleshoot tenants quickly (used in enterprise SaaS support).




how does macrofrot end support multi tenant UI architecture


When designing micro frontend (MFE) architecture for multi-tenant SaaS UI, you’re combining the power of independent feature teams with tenant-aware rendering, theming, and routing.

Here’s how micro frontends (MFEs) support multi-tenant UI in an enterprise SaaS setup:

🧱 1. Each MFE Is Tenant-Aware

Every micro frontend (e.g., Billing, Dashboard, Admin Console) is built to:

  • Accept tenant context as props or via a shared context/provider

  • Dynamically apply tenant-specific themes, features, or UI elements

This ensures each MFE can:

  • Render different logos, colors, or components

  • Respect per-tenant permissions (RBAC)

  • Enable/disable modules or views (via feature flags)


🧩 2. Composition Layer (Shell App / Host App)

A Shell App (a.k.a. "layout" or "host" app) handles:

  • Parsing the tenant ID (from subdomain, JWT, or URL path)

  • Fetching tenant config (e.g., branding, features)

  • Setting up global context (TenantContext, AuthContext, ThemeProvider)

  • Loading MFEs via Module Federation or other strategies (like Webpack 5, Single-SPA)

bash

CopyEdit

https://<tenant-id>.yourapp.com/dashboard → Shell → passes tenant info to MFEs

🖼️ 3. Shared Context and Theming

Tenant-aware theming and branding are achieved through:

  • Global ThemeProvider (e.g., styled-components, MUI Theme)

  • Shared TenantContext, made available to all MFEs

  • Shared libraries for layout, authentication, and config utilities

MFEs consume shared context and adapt:

tsx

CopyEdit

const { theme, features } = useContext(TenantContext); return <Header style={{ backgroundColor: theme.primary }} />;

🚥 4. Routing & Access Control per Tenant

Tenant-based routing options:

Inside each MFE or the shell:

  • Conditionally render routes or views based on tenant permissions and feature flags

  • Use centralized RBAC config (role, tenant, module, action)

🛠️ 5. Deployment Strategies

Approach

Description

Module Federation

Each MFE independently deployable, loaded dynamically by Shell via remote entry

iFrame (legacy)

Each MFE runs in isolation (less preferred due to performance & UX gaps)

Single-SPA

Compose MFEs from different frameworks with lifecycle management

🔐 6. Multi-Tenant Security

  • Token includes tenant ID, scoped permissions

  • MFEs validate the token and tenant context

  • Prevent cross-tenant access by validating tenant-bound API access

✅ Example: MFE Stack for SaaS

Component

Tech Stack Example

Shell App

React + Webpack 5 + Module Federation

Auth MFE

React + Keycloak/OIDC

Billing MFE

Angular + Stripe SDK

Reports MFE

Vue + D3

Shared Libraries

TypeScript + Tailwind/MUI + i18n

API Gateway

Multi-tenant JWT, Tenant-aware routing

💡 Summary – Key Benefits of MFE for Multi-Tenant SaaS

✅ Decoupled team ownership✅ Per-tenant customization✅ Faster release cycle✅ Scalable theming and RBAC✅ Isolated deployment of features


how do you design API for multi tenant architecture


Designing APIs for a multi-tenant SaaS architecture involves careful consideration of security, data isolation, scalability, and tenant context propagation. Here's a structured and enterprise-ready guide to help you design robust, scalable multi-tenant APIs.

🔧 1. Tenant Context Passing

📌 Common Patterns

Best practice: Use JWT-based authentication with tenant_id in claims to ensure secure and validated tenant identity on every request.

🛡️ 2. Tenant Isolation & Security

  • Every API must validate the tenant_id against the authenticated user’s token

  • Never trust input directly from URL/path/header — validate it server-side

  • Enforce row-level security in DB using tenant ID

  • Apply role-based access control (RBAC) within the tenant scope

java

CopyEdit

// Sample Java Spring Security @PreAuthorize("hasRole('ADMIN') and #tenantId == authentication.token.tenantId") public ResponseEntity<?> getTenantUsers(@PathVariable String tenantId) { ... }

🧩 3. API Design Patterns

Concern

Pattern

Example

URL Structure

Scoped Path or Header

/api/v1/tenantA/users

Auth

JWT with tenant ID claim

{"sub": "user1", "tenant": "tenantA"}

RBAC

Tenant-scoped roles

tenant_admin, tenant_user

DB Access

Row-level isolation

WHERE tenant_id = ?

Service Layer

Tenant Context Injection

@Inject TenantContext

API Gateway

Multi-tenant routing / auth

Validate tenant before request hits microservice

🗄️ 4. Data Partitioning Strategies

🏢 1. Shared DB, Shared Schema

  • All tenants in same DB and same tables

  • Use tenant_id column for partitioning

  • ✅ Cost-effective, good for early stage

  • ⚠️ Must enforce row-level access control

🏢 2. Shared DB, Separate Schema

  • Each tenant has its own schema (tenantA.users)

  • Better isolation, more complexity

🏢 3. Separate DB per tenant

  • Full isolation

  • Costlier, ideal for large/regulated customers

  • Useful for data residency laws (e.g., GDPR)

🔁 5. Multi-Tenant Middleware

Use a middleware/interceptor to:

  • Extract and validate tenant_id

  • Load tenant config, theme, limits

  • Inject TenantContext into request/thread

java

public class TenantContextInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String tenantId = extractTenantId(request); validate(tenantId); TenantContext.set(tenantId); return true; } }

📈 6. Monitoring, Throttling, and Limits

  • Track usage per tenant (API metrics, limits, abuse detection)

  • Use API Gateway (e.g., Kong, Apigee, Azure API Management) to:

    • Enforce rate limits per tenant

    • Log tenant-specific metrics

🧪 7. Testing and Observability

  • Write multi-tenant test cases (simulate multiple tenants with different data sets)

  • Include tenant ID in all logs and metrics

  • Ensure data visibility is scoped properly in logs, APM, alerts

🧠 Best Practices

Practice

Why it Matters

Validate tenant identity

Prevent data leaks or tenant spoofing

Isolate data access

Enforce via app logic + DB level if needed

Use consistent tenant key

In APIs, services, logs, metrics

Make tenant context injectable

For logging, auditing, personalization

Plan for scalability

Especially if using shared DB/infra

🔚 Summary

A multi-tenant API should: ✅ Accept and validate tenant context✅ Enforce strict tenant-bound data access✅ Propagate tenant info across services✅ Be observable and rate-limited per tenant✅ Scale flexibly as tenants grow


Great! Let's walk through a live example of a multi-tenant Spring Boot app with:

  • Tenant-aware API

  • Multi-DB isolation

  • OAuth2 security

  • Rate limiting

  • UI routing via subdomain

🎯 Use Case

Scenario: A SaaS HR platform with different tenants like acme.hrplatform.com, globex.hrplatform.com.

Each tenant should:

  • Have isolated DB

  • Be authenticated via OAuth2

  • Be rate limited

  • Get custom UI theme and routes

1️⃣ Tenant Identification (Subdomain-Based)

Create a filter:

java

CopyEdit

@Component public class TenantFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws ServletException, IOException { String host = req.getServerName(); // acme.hrplatform.com String tenantId = host.split("\\.")[0]; // acme TenantContext.setCurrentTenant(tenantId); chain.doFilter(req, res); } }

2️⃣ Multi-DB Routing

Use AbstractRoutingDataSource:

java

CopyEdit

public class MultiTenantDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return TenantContext.getCurrentTenant(); // e.g., acme } }

In your config:

java

CopyEdit

@Bean public DataSource dataSource() { Map<Object, Object> resolvedDataSources = new HashMap<>(); tenants.forEach((id, props) -> { resolvedDataSources.put(id, DataSourceBuilder.create().url(props.getUrl()).build()); }); MultiTenantDataSource ds = new MultiTenantDataSource(); ds.setTargetDataSources(resolvedDataSources); return ds; }

3️⃣ Tenant-Aware API

java

CopyEdit

@RestController @RequestMapping("/employees") public class EmployeeController { @Autowired private EmployeeService service; @GetMapping public List<Employee> getAll() { String tenantId = TenantContext.getCurrentTenant(); return service.getAllForTenant(tenantId); } }

4️⃣ OAuth2 with Spring Security

In application.yml:

yaml

CopyEdit

spring: security: oauth2: resourceserver: jwt: issuer-uri: https://auth.hrplatform.com

In your security config:

java

CopyEdit

@EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests() .anyRequest().authenticated() .and() .oauth2ResourceServer().jwt(); return http.build(); } }

5️⃣ Rate Limiting (Per Tenant with Bucket4j)

java

CopyEdit

@Component public class RateLimitInterceptor implements HandlerInterceptor { private final Map<String, Bucket> cache = new ConcurrentHashMap<>(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String tenantId = TenantContext.getCurrentTenant(); Bucket bucket = cache.computeIfAbsent(tenantId, k -> Bucket4j.builder() .addLimit(Bandwidth.simple(100, Duration.ofMinutes(1))).build()); if (!bucket.tryConsume(1)) { response.setStatus(429); return false; } return true; } }

6️⃣ Frontend Routing (React Example)

js

CopyEdit

const hostname = window.location.hostname; const tenantId = hostname.split('.')[0]; // 'acme' const theme = getThemeForTenant(tenantId); return ( <ThemeProvider theme={theme}> <Routes> <Route path="/" element={<Dashboard tenant={tenantId} />} /> </Routes> </ThemeProvider> );

Would you like me to spin this up as a GitHub repo, create a Docker Compose, or simulate a request lifecycle (with headers and trace)?





 
 
 

Recent Posts

See All
Ops Efficiency 30 % improvement

how did you achieve 30 % operational efficiency Achieving 30% operational efficiency in a BFSI-grade, microservices-based personal...

 
 
 

Comments

Rated 0 out of 5 stars.
No ratings yet

Add a rating
  • Facebook
  • Twitter
  • LinkedIn

©2024 by AeeroTech. Proudly created with Wix.com

bottom of page