When scaling B2B SaaS platforms to serve thousands of corporate customers, the typical pitfalls aren't network load—they are codebase maintainability and team friction. A monolithic frontend structure leads to massive merge conflicts, sluggish build times, and high cognitive load.
Here are the structural standards we established to scale our Vue 3 codebases:
1. The 3-App Domain Routing Pattern
Rather than shipping distinct codebases or building separate web apps for Super Admins, Client Dashboards, and Public Subdomain portals, we unified them into a
3-in-1 codebase model. Using
vite-plugin-pages and standard layout wrappers, the app parses the subdomains dynamically at boot time:
// src/router/guards.ts
router.beforeEach((to, from, next) => {
const subdomain = getActiveSubdomain(window.location.hostname);
if (subdomain === 'admin' && !to.path.startsWith('/admin')) {
return next('/admin');
}
if (isCustomerDashboard(subdomain) && to.path.startsWith('/admin')) {
return next('/dashboard/unauthorized');
}
next();
});
This unified code pattern reduced shared utility duplication and ensured that common components (like our custom forms engines and alert banners) were instantly accessible to all modules.
2. Standardizing the Setup API
We strictly enforced the use of Vue 3
composition paradigms. This eliminated the boilerplate of the Options API and enabled robust composable extraction. For example, rather than writing custom form handling inline on every template, we abstracted SurveyJS inputs into reusable composables:
// src/composables/useFormEngine.ts
import { ref } from 'vue';
import { Model } from 'survey-core';
export function useFormEngine(surveySchema: object) {
const surveyModel = ref(new Model(surveySchema));
const isSubmitting = ref(false);
const handleComplete = async (sender: any) => {
isSubmitting.value = true;
try {
await submitFormResults(sender.data);
} finally {
isSubmitting.value = false;
}
};
return { surveyModel, isSubmitting, handleComplete };
}
3. Strict Pinia State Encapsulation
We prohibited component direct state modification outside of dedicated actions. All shared state mutations must flow through Pinia stores. This keeps debugging predictable and allows for centralized hooks (such as cookie session sync and user permission validation).