Frontend Scalability Implementation Patterns
1. Code Splitting Bundle Optimization
| Strategy | Implementation | Description | Impact |
|---|---|---|---|
| Entry Point Splitting | entry: { app, vendor } |
Separate application code from third-party libraries | Better caching, parallel loading, reduced main bundle size |
| Dynamic Splitting | import() |
Runtime code splitting based on user interaction or route | 50-70% reduction in initial bundle, faster TTI |
| Vendor Chunking | splitChunks.cacheGroups |
Extract node_modules into separate vendor chunk | Long-term caching, CDN optimization |
| Common Chunks | splitChunks.chunks: 'all' |
Shared code between multiple entry points extracted automatically | Reduce duplication, optimize cache usage |
| Granular Chunks | maxSize, minSize |
Control chunk size for optimal HTTP/2 multiplexing | Parallel downloads, better compression ratios |
| CSS Splitting | MiniCssExtractPlugin |
Extract CSS into separate files for parallel loading | Non-blocking CSS, better caching, critical CSS extraction |
Example: Webpack Bundle Optimization
// webpack.config.js
module.exports = {
entry: {
app: './src/index.js',
},
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// Vendor chunk: node_modules
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
priority: 10,
reuseExistingChunk: true
},
// React/ReactDOM separate chunk
react: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: 'react',
priority: 20
},
// Common chunks shared by 2+ modules
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true,
enforce: true
}
},
// Chunk size limits
maxSize: 244000, // 244KB
minSize: 20000, // 20KB
},
// Runtime chunk for webpack module loading logic
runtimeChunk: {
name: 'runtime'
},
// Minimize bundles
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
}
})
]
},
plugins: [
// Extract CSS
new MiniCssExtractPlugin({
filename: '[name].[contenthash:8].css',
chunkFilename: '[name].[contenthash:8].chunk.css'
}),
// Bundle analyzer
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false
})
]
};
2. Tree Shaking Dead Code Elimination
| Technique | Configuration | Description | Requirement |
|---|---|---|---|
| ES Modules | type: "module" |
Use ES6 import/export for static analysis, avoid CommonJS require | Required for tree shaking, module.exports prevents elimination |
| sideEffects Flag | package.json |
Mark files without side effects for safe removal | Enables aggressive tree shaking, CSS imports need marking |
| Production Mode | mode: 'production' |
Webpack/Vite automatically enables tree shaking optimizations | Development mode keeps all code for debugging |
| Named Exports | export { fn } |
Granular imports allow elimination of unused exports | Prefer named over default exports for better tree shaking |
| Babel Configuration | modules: false |
Preserve ES modules in Babel, don't transpile to CommonJS | Critical for tree shaking, preset-env default breaks it |
| Unused Code Detection | usedExports: true |
Mark unused exports for removal by minimizer | Works with TerserPlugin to eliminate dead code |
Example: Tree Shaking Configuration
// package.json - Mark side effects
{
"name": "my-library",
"version": "1.0.0",
"sideEffects": [
"*.css",
"*.scss",
"./src/polyfills.js"
]
// Or false if no side effects
// "sideEffects": false
}
// babel.config.js - Preserve ES modules
module.exports = {
presets: [
['@babel/preset-env', {
modules: false, // Don't transpile modules
targets: { esmodules: true }
}]
]
};
// Library: Use named exports
// ❌ Bad: Default export
export default {
add,
subtract,
multiply,
divide
};
// ✅ Good: Named exports
export { add };
export { subtract };
export { multiply };
export { divide };
// Consumer: Import only what's needed
// ❌ Bad: Entire library imported
import math from 'math-library';
math.add(1, 2);
// ✅ Good: Tree-shakeable
import { add } from 'math-library';
add(1, 2);
// webpack.config.js
module.exports = {
mode: 'production',
optimization: {
usedExports: true, // Mark unused exports
minimize: true,
sideEffects: true, // Read package.json sideEffects
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
unused: true,
dead_code: true
}
}
})
]
}
};
// Lodash tree shaking
// ❌ Bad: Imports entire library
import _ from 'lodash';
_.debounce(fn, 300);
// ✅ Good: Import specific module
import debounce from 'lodash/debounce';
// Or use lodash-es (ES module version)
import { debounce } from 'lodash-es';
Impact: Proper tree shaking can reduce bundle size by 30-50% by
eliminating unused code. Lodash alone can save 60KB+ when properly tree-shaken.
3. CDN CloudFront Asset Distribution
| Strategy | Implementation | Description | Benefit |
|---|---|---|---|
| CDN Distribution | CloudFront, Cloudflare |
Global edge network serving assets from nearest location to users | 50-90% latency reduction, improved TTFB, global reach |
| Cache Headers | Cache-Control: max-age |
Configure browser and CDN caching policies for optimal freshness | Reduced bandwidth, faster repeat visits, lower origin load |
| Content Hashing | [contenthash] |
Add hash to filenames for cache busting and long-term caching | Immutable assets, aggressive caching, instant updates |
| Gzip/Brotli Compression | Accept-Encoding |
Compress text assets (JS, CSS, HTML) at edge or origin | 60-80% size reduction, faster transfers, less bandwidth |
| HTTP/2 Push | Link: <url>; rel=preload |
Proactively push critical assets before browser requests them | Reduced round trips, faster critical resource loading |
| Edge Functions | CloudFront Functions |
Run lightweight logic at CDN edge for personalization, A/B testing | Low latency, no origin hit, dynamic edge content |
Example: CDN Configuration
// webpack.config.js - Content hashing
module.exports = {
output: {
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].chunk.js',
assetModuleFilename: 'assets/[name].[contenthash:8][ext]',
publicPath: 'https://cdn.example.com/'
}
};
// CloudFront Cache Behaviors
{
"CacheBehaviors": [
{
"PathPattern": "*.js",
"ViewerProtocolPolicy": "redirect-to-https",
"CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
"Compress": true,
"AllowedMethods": ["GET", "HEAD", "OPTIONS"],
"CachedMethods": ["GET", "HEAD"]
},
{
"PathPattern": "*.css",
"CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
"Compress": true
}
]
}
// Cache-Control headers
// Long-term cache for hashed assets
Cache-Control: public, max-age=31536000, immutable
// Short cache for HTML
Cache-Control: public, max-age=0, must-revalidate
// Nginx configuration
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header X-Content-Type-Options "nosniff";
# Gzip compression
gzip on;
gzip_types text/css application/javascript image/svg+xml;
gzip_min_length 1000;
# Brotli (if module enabled)
brotli on;
brotli_types text/css application/javascript;
}
// S3 + CloudFront deployment
aws s3 sync ./dist s3://my-bucket \
--cache-control "public,max-age=31536000,immutable" \
--exclude "*.html"
aws s3 sync ./dist s3://my-bucket \
--cache-control "public,max-age=0,must-revalidate" \
--exclude "*" \
--include "*.html"
// Invalidate CloudFront cache
aws cloudfront create-invalidation \
--distribution-id E1234567890 \
--paths "/index.html" "/*.html"
CDN Best Practices
- Content Hashing: Use [contenthash] for immutable assets with long cache
- Separate HTML: Never cache HTML with long TTL, use cache busting
- Compression: Enable Brotli at CDN, 20% better than Gzip
- Multi-Region: Deploy to multiple regions for global redundancy
4. Service Workers PWA Caching
| Strategy | Pattern | Description | Use Case |
|---|---|---|---|
| Cache First | cache → network |
Serve from cache if available, fetch from network as fallback | Static assets (JS, CSS, images), offline-first apps |
| Network First | network → cache |
Fetch from network first, use cache if network fails | Dynamic content, API responses, fresh data priority |
| Stale While Revalidate | cache + background update |
Serve cached version instantly, update cache in background | Balance freshness and speed, user avatars, news feeds |
| Network Only | network only |
Always fetch from network, never cache | Sensitive data, real-time updates, analytics |
| Cache Only | cache only |
Only serve from cache, fail if not cached | Pre-cached critical resources, offline mode |
| Workbox | workbox-webpack-plugin |
Google's service worker library with routing, strategies, precaching | Production-ready PWA, automated caching, background sync |
Example: Service Worker Caching Strategies
// service-worker.js - Manual implementation
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1-static').then((cache) => {
return cache.addAll([
'/',
'/index.html',
'/styles.css',
'/app.js',
'/logo.png'
]);
})
);
});
self.addEventListener('fetch', (event) => {
const { request } = event;
// Cache-first for static assets
if (request.url.includes('/static/')) {
event.respondWith(
caches.match(request).then((cached) => {
return cached || fetch(request).then((response) => {
return caches.open('v1-static').then((cache) => {
cache.put(request, response.clone());
return response;
});
});
})
);
}
// Network-first for API
else if (request.url.includes('/api/')) {
event.respondWith(
fetch(request)
.then((response) => {
const clone = response.clone();
caches.open('v1-api').then((cache) => {
cache.put(request, clone);
});
return response;
})
.catch(() => caches.match(request))
);
}
});
// Workbox implementation
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
// Precache build assets
precacheAndRoute(self.__WB_MANIFEST);
// Cache images - Cache First
registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({
cacheName: 'images',
plugins: [
new ExpirationPlugin({
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 days
})
]
})
);
// Cache API - Network First
registerRoute(
({ url }) => url.pathname.startsWith('/api/'),
new NetworkFirst({
cacheName: 'api-cache',
networkTimeoutSeconds: 3,
plugins: [
new CacheableResponsePlugin({
statuses: [0, 200]
}),
new ExpirationPlugin({
maxEntries: 50,
maxAgeSeconds: 5 * 60 // 5 minutes
})
]
})
);
// Stale While Revalidate for Google Fonts
registerRoute(
({ url }) => url.origin === 'https://fonts.googleapis.com',
new StaleWhileRevalidate({
cacheName: 'google-fonts-stylesheets'
})
);
// webpack.config.js - Generate service worker
const { GenerateSW } = require('workbox-webpack-plugin');
module.exports = {
plugins: [
new GenerateSW({
clientsClaim: true,
skipWaiting: true,
runtimeCaching: [
{
urlPattern: /^https:\/\/api\.example\.com/,
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
expiration: { maxEntries: 50, maxAgeSeconds: 300 }
}
}
]
})
]
};
Caching Decision Tree
| Content Type | Strategy |
|---|---|
| App Shell | Cache First + Precache |
| Static Assets | Cache First |
| User Content | Stale While Revalidate |
| API Data | Network First |
| Real-time Data | Network Only |
| Offline Fallback | Cache Only |
Service Worker Lifecycle
// Register service worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/service-worker.js')
.then((registration) => {
console.log('SW registered:', registration);
})
.catch((error) => {
console.log('SW registration failed:', error);
});
});
}
// Skip waiting for updates
self.addEventListener('message', (event) => {
if (event.data === 'SKIP_WAITING') {
self.skipWaiting();
}
});
Offline Capability: Service Workers enable offline-first
experiences with 50-90% faster repeat visits by serving assets from cache instead of network.
5. Webpack Vite Build Optimization
| Tool | Feature | Description | Performance |
|---|---|---|---|
| Vite NEW | esbuild, Rollup |
Lightning-fast dev server with native ESM, instant HMR | 10-100x faster cold start, sub-second HMR |
| Webpack 5 | Persistent Cache |
Filesystem caching for faster rebuilds, long-term caching | 90% faster rebuilds, module federation |
| SWC/esbuild | Rust/Go compilers |
Ultra-fast JavaScript/TypeScript transpilation vs Babel | 20-70x faster than Babel, drop-in replacement |
| Parallel Processing | thread-loader |
Run expensive loaders in worker pool for parallel processing | 30-50% faster builds with multi-core utilization |
| Module Federation | ModuleFederationPlugin |
Share code between independently deployed applications | Micro-frontend architecture, reduced duplication |
| Build Caching | cache: { type: 'filesystem' } |
Cache build artifacts to disk for faster subsequent builds | 5-10x faster rebuilds in CI/CD pipelines |
Webpack 5 Optimization
// webpack.config.js
module.exports = {
mode: 'production',
// Persistent filesystem cache
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
},
optimization: {
// Tree shaking
usedExports: true,
sideEffects: true,
// Code splitting
splitChunks: {
chunks: 'all',
maxInitialRequests: 25,
minSize: 20000
},
// Minimize
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {
compress: { drop_console: true }
}
}),
new CssMinimizerPlugin()
]
},
module: {
rules: [
{
test: /\.js$/,
use: [
// Parallel processing
'thread-loader',
{
loader: 'swc-loader',
options: {
jsc: {
parser: { syntax: 'ecmascript' },
transform: { react: { runtime: 'automatic' } }
}
}
}
]
}
]
},
// Performance hints
performance: {
maxEntrypointSize: 512000,
maxAssetSize: 512000
}
};
Vite Configuration
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: {
target: 'esnext',
minify: 'esbuild',
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
ui: ['@mui/material']
}
}
},
chunkSizeWarningLimit: 600,
// Source maps for production
sourcemap: false,
// CSS code splitting
cssCodeSplit: true
},
server: {
// HMR configuration
hmr: {
overlay: true
}
},
optimizeDeps: {
include: ['react', 'react-dom'],
esbuildOptions: {
target: 'esnext'
}
}
});
When to Use Each Tool
- Vite: New projects, fast dev experience, modern browsers only
- Webpack 5: Complex builds, module federation, legacy browser support
- Rollup: Libraries, tree-shaking priority, smaller bundles
- esbuild: Ultra-fast builds, minimal config, bundler or loader
6. Micro-Frontend Container Orchestration
| Pattern | Implementation | Description | Trade-off |
|---|---|---|---|
| Container App | Shell Application |
Host application that loads and orchestrates remote micro-frontends | Centralized routing, shared layout, version coordination |
| Module Federation | Webpack 5 |
Runtime module sharing, independent deployments, dynamic loading | Best integration, version conflicts possible, Webpack-specific |
| iframe Integration | <iframe> |
Complete isolation, technology agnostic, simple implementation | Poor UX (scrolling, routing), SEO issues, performance overhead |
| Web Components | Custom Elements |
Native browser standard for encapsulated components | Framework agnostic, Shadow DOM isolation, limited React support |
| Single-SPA | single-spa framework |
Meta-framework for orchestrating multiple SPA frameworks | Framework agnostic, routing lifecycle, learning curve |
| Shared Dependencies | Singleton sharing |
Share React, libraries across micro-frontends to reduce duplication | Version coordination critical, potential conflicts |
Example: Micro-Frontend Architecture
// Container/Host Application
// webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'container',
remotes: {
header: 'header@http://localhost:3001/remoteEntry.js',
products: 'products@http://localhost:3002/remoteEntry.js',
checkout: 'checkout@http://localhost:3003/remoteEntry.js'
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
'react-router-dom': { singleton: true }
}
})
]
};
// Container App.js
import React, { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const Header = lazy(() => import('header/Header'));
const Products = lazy(() => import('products/ProductList'));
const Checkout = lazy(() => import('checkout/CheckoutFlow'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>Loading...</div>}>
<Header />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/products/*" element={<Products />} />
<Route path="/checkout/*" element={<Checkout />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
// Remote Micro-Frontend (Header)
// webpack.config.js
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'header',
filename: 'remoteEntry.js',
exposes: {
'./Header': './src/components/Header'
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true }
}
})
]
};
// Single-SPA approach
import { registerApplication, start } from 'single-spa';
registerApplication({
name: '@org/header',
app: () => System.import('@org/header'),
activeWhen: '/' // Always active
});
registerApplication({
name: '@org/products',
app: () => System.import('@org/products'),
activeWhen: '/products'
});
start();
// Web Components approach
class ProductCard extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<div class="product-card">
<h3>${this.getAttribute('name')}</h3>
<p>${this.getAttribute('price')}</p>
</div>
`;
}
}
customElements.define('product-card', ProductCard);
// Usage in any framework
<product-card name="Widget" price="29.99"></product-card>
Example: Error Handling & Fallbacks
// Error Boundary for Micro-Frontends
class MicroFrontendBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('MFE Error:', error, errorInfo);
// Log to error tracking
logError(this.props.mfeName, error);
}
render() {
if (this.state.hasError) {
return (
<div className="mfe-fallback">
<h3>{this.props.mfeName} unavailable</h3>
<button onClick={() => this.setState({ hasError: false })}>
Retry
</button>
</div>
);
}
return this.props.children;
}
}
// Usage
<MicroFrontendBoundary mfeName="Header">
<Suspense fallback={<HeaderSkeleton />}>
<Header />
</Suspense>
</MicroFrontendBoundary>
// Version fallback
const loadMicroFrontend = async (name, version = 'latest') => {
try {
return await import(`${name}@${version}/remoteEntry.js`);
} catch (error) {
console.warn(`Failed to load ${name}@${version}, trying v1`);
return await import(`${name}@v1/remoteEntry.js`);
}
};
Micro-Frontend Best Practices
- Team Autonomy: Each team owns full stack for their domain
- Shared Nothing: Minimize shared dependencies, avoid tight coupling
- Error Isolation: Failures in one MFE shouldn't break entire app
- Performance: Monitor bundle sizes, avoid duplication, lazy load
- Versioning: Semantic versioning, backward compatibility, gradual rollout
Complexity Warning: Micro-frontends add significant complexity. Only adopt for large teams (50+ developers) where team autonomy and independent deployments outweigh
coordination overhead.