Performance Best Practices
Optimize bundle size and loading performance with the UMD Design System
Getting Started
Where This Package Fits: Components sits at the top of the Application layer. It depends on all lower packages and provides ready-to-use web components. See full architecture.
Introduction
The UMD Design System provides multiple strategies for optimizing bundle size and loading performance. By leveraging ES modules, tree-shaking, and strategic code splitting, you can significantly reduce initial load times and improve the user experience of your applications.
This guide covers the design system's export strategy, bundling configuration with Vite, and production-ready patterns for dynamic component loading. Whether you're building a simple static site or a complex web application, these patterns will help you achieve optimal performance while maintaining code maintainability.
The design system is built with performance in mind, providing granular control over what gets loaded and when. Components are organized into logical groups that align with common usage patterns, allowing you to load only what you need, when you need it. Note: Grouped exports (structural, content, interactive, feed) were introduced in Release 1.14.0.
- CDN Usage - Quick setup via unpkg for prototypes (see Getting Started)
- Bundle Import - Single import with all packages included via
@universityofmaryland/web-components-library/bundle
Export Strategy
The component library provides two complementary export strategies to optimize bundle size and loading performance. Choose the approach that best fits your application's needs.
Grouped Exports
The design system provides pre-configured component groups based on common usage patterns. This approach balances convenience with performance.
// Structural Components - Page layout and navigation
// Typically loaded first for above-the-fold content
import { LoadStructuralComponents } from '@universityofmaryland/web-components-library/structural';
LoadStructuralComponents(); // Loads: actions, hero, navigation
// Content Components - Display components for information
// Load after structural components for main content
import { LoadContentComponents } from '@universityofmaryland/web-components-library/content';
LoadContentComponents(); // Loads: card, text, media, stats, etc.
// Interactive Components - User interaction components
// Can be deferred until user interaction
import { LoadInteractiveComponents } from '@universityofmaryland/web-components-library/interactive';
LoadInteractiveComponents(); // Loads: accordion, carousel, footer, social, tab
// Feed Components - Dynamic content feeds
// Load on-demand for pages with feeds
import { LoadFeedComponents } from '@universityofmaryland/web-components-library/feed';
LoadFeedComponents(); // Loads: events, news, people feeds
Individual Component Exports
Import specific component types individually using dynamic imports for maximum tree-shaking efficiency and granular control over what gets loaded.
// Dynamically import individual component types on demand
const loadSpecificComponents = async () => {
// Load only card components and their variants
const cardComponents = await import(
'@universityofmaryland/web-components-library/components/card'
);
cardComponents.standard(); // Register standard card
cardComponents.overlay(); // Register overlay card
cardComponents.event(); // Register event card
// Load only hero components and their variants
const heroComponents = await import(
'@universityofmaryland/web-components-library/components/hero'
);
heroComponents.base(); // Register base hero
heroComponents.expand(); // Register expandable hero
// Load only navigation components
const navComponents = await import(
'@universityofmaryland/web-components-library/components/navigation'
);
navComponents.primary(); // Register primary navigation
navComponents.drawer(); // Register navigation drawer
};
// Conditionally load based on DOM presence
if (document.querySelector('[class*="umd-element-card"]')) {
import('@universityofmaryland/web-components-library/components/card')
.then(module => module.standard());
}
if (document.querySelector('[class*="umd-element-hero"]')) {
import('@universityofmaryland/web-components-library/components/hero')
.then(module => module.base());
}
Bundle Import with Tree-Shaking
Even when using the bundle import, you can still benefit from tree-shaking by importing specific components:
// Import specific components from the bundle for tree-shaking
import { Components, Styles } from '@universityofmaryland/web-components-library/bundle';
// Register only the components you need
Components.card.standard();
Components.hero.base();
Components.navigation.primary();
// The bundler will tree-shake unused components from Elements, Feeds, etc.
// This gives you the convenience of a single import path with optimized output
Export Strategy Comparison
| Approach | Bundle Size | Tree-shaking | Development Speed | Best For |
|---|---|---|---|---|
| Grouped Imports | Moderate (group-level) | Good (per group) | Faster setup | Standard sites with typical patterns |
| Individual Imports | Smallest possible | Maximum efficiency | More verbose | Production apps with specific needs |
| Bundle + Tree-shaking | Small (with proper imports) | Good (when destructured) | Simple imports | Apps wanting single import path |
| Full Bundle Init | Largest | None | Quickest | Prototypes and development only |
Pro Tip: Hybrid Approach
Combine both strategies for optimal results. Use grouped imports for common component sets and individual imports for specific components used sparingly:
// Load structural components as a group
import { LoadStructuralComponents } from '@universityofmaryland/web-components-library/structural';
LoadStructuralComponents();
// Import specific components individually
import { Components } from '@universityofmaryland/web-components-library';
Components.specialFeature.custom(); // Only this specific component
Bundling Configuration
The design system includes an optimized Vite configuration that implements intelligent chunk splitting for optimal caching and loading performance.
Complete Vite Configuration
// vite.config.js
import { defineConfig } from 'vite';
import path from 'path';
export default defineConfig(({ mode }) => {
const isProduction = mode === 'production';
return {
build: {
// Enable/disable source maps based on environment
sourcemap: !isProduction,
// Use terser for production minification
minify: isProduction ? 'terser' : false,
rollupOptions: {
input: {
main: path.resolve(__dirname, 'src/main.ts'),
},
output: {
// Named entry chunks without hash for stable names
entryFileNames: '[name].js',
// Smart chunk naming strategy
chunkFileNames: (chunkInfo) => {
// Group vendor chunks by library for better caching
if (chunkInfo.name.includes('styles-library'))
return 'vendor-styles.js';
if (chunkInfo.name.includes('elements-library'))
return 'vendor-elements.js';
// Default naming with hash for cache busting
return '[name]-[hash].js';
},
// CSS extraction with predictable names
assetFileNames: (assetInfo) => {
if (assetInfo.name?.endsWith('.css')) {
if (assetInfo.name.includes('main')) return 'main.css';
if (assetInfo.name.includes('template')) return 'template.css';
return '[name].css';
}
return '[name].[ext]';
},
},
},
// Terser options for aggressive production optimization
terserOptions: isProduction
? {
compress: {
drop_console: true, // Remove console.log in production
drop_debugger: true, // Remove debugger statements
},
}
: undefined,
// Warn about large chunks (500kb)
chunkSizeWarningLimit: 500,
// Enable module preload polyfill for older browsers
modulePreload: {
polyfill: true,
},
},
resolve: {
// Path aliases for cleaner imports and grouped components
alias: {
'@': path.resolve(__dirname, './source'),
// Grouped component exports
'@universityofmaryland/web-components-library/structural':
path.resolve(__dirname, '../packages/components/dist/structural.js'),
'@universityofmaryland/web-components-library/content':
path.resolve(__dirname, '../packages/components/dist/content.js'),
'@universityofmaryland/web-components-library/interactive':
path.resolve(__dirname, '../packages/components/dist/interactive.js'),
'@universityofmaryland/web-components-library/feed':
path.resolve(__dirname, '../packages/components/dist/feed.js'),
// Main library exports
'@universityofmaryland/web-components-library':
path.resolve(__dirname, '../packages/components/dist/index.js'),
'@universityofmaryland/web-elements-library':
path.resolve(__dirname, '../packages/elements'),
'@universityofmaryland/web-styles-library':
path.resolve(__dirname, '../packages/styles'),
},
},
optimizeDeps: {
// Pre-bundle critical dependencies for faster dev server startup
include: [
'@universityofmaryland/web-styles-library',
'@universityofmaryland/web-elements-library',
],
// Scan entry points for dependency discovery
entries: [
'src/main.ts',
'src/styles.ts',
],
},
};
});
-
chunkFileNames- Creates predictable vendor chunks for better long-term caching -
modulePreload- Ensures critical resources are preloaded for faster initial render -
optimizeDeps.include- Pre-bundles frequently used dependencies to avoid waterfall loading -
alias- Maps grouped exports to their distribution files for cleaner imports -
terserOptions- Removes development code in production builds
Dynamic Loading Patterns
The design system implements sophisticated loading strategies to optimize initial page load and progressively enhance the user experience.
Progressive Component Loading
// main.ts - Production-ready loading strategy
import './styles.ts';
// Component loaders with dynamic imports for code splitting
const loadStructuralComponents = async () => {
const { LoadStructuralComponents } = await import(
'@universityofmaryland/web-components-library/structural'
);
return LoadStructuralComponents();
};
const loadContentComponents = async () => {
const { LoadContentComponents } = await import(
'@universityofmaryland/web-components-library/content'
);
return LoadContentComponents();
};
const loadInteractiveComponents = async () => {
const { LoadInteractiveComponents } = await import(
'@universityofmaryland/web-components-library/interactive'
);
return LoadInteractiveComponents();
};
const loadFeedComponents = async () => {
const { LoadFeedComponents } = await import(
'@universityofmaryland/web-components-library/feed'
);
return LoadFeedComponents();
};
const loadAnimations = async () => {
const { Utilties } = await import(
'@universityofmaryland/web-components-library'
);
return Utilties.Animations.loadIntersectionObserver();
};
const initializeApp = async () => {
// Critical: Load structural components immediately
await loadStructuralComponents();
// Visible content: Load when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', async () => {
await loadContentComponents();
});
} else {
await loadContentComponents();
}
// Below-the-fold: Defer interactive components
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
let componentsLoaded = false;
const loadDeferredComponents = () => {
if (!componentsLoaded) {
componentsLoaded = true;
loadInteractiveComponents();
loadFeedComponents();
loadAnimations();
}
};
// Load on any user interaction
const interactionEvents = [
'mousedown',
'touchstart',
'keydown',
'scroll',
];
interactionEvents.forEach((event) => {
document.addEventListener(event, loadDeferredComponents, {
once: true,
passive: true,
});
});
// Fallback: Ensure loading after 2 seconds
setTimeout(loadDeferredComponents, 2000);
});
} else {
// Fallback for browsers without requestIdleCallback
setTimeout(() => {
loadInteractiveComponents();
loadFeedComponents();
loadAnimations();
}, 100);
}
};
initializeApp();
Intersection Observer Pattern
Load component groups only when they're about to enter the viewport. This leverages the pre-built chunks for optimal performance:
// Lazy load component chunks based on viewport visibility
const observeAndLoadComponents = () => {
// Map components to their optimized chunk groups
const componentGroups = {
interactive: ['umd-element-carousel', 'umd-element-accordion', 'umd-element-tab'],
feed: ['umd-element-feed-events', 'umd-element-feed-news', 'umd-element-feed-people'],
content: ['umd-element-stats', 'umd-element-card', 'umd-element-text-image'],
};
// Track which groups have been loaded
const loadedGroups = new Set();
const loadComponentGroup = async (groupName) => {
if (loadedGroups.has(groupName)) return;
loadedGroups.add(groupName);
// Load the optimized chunk for this component group
// Named exports: LoadStructuralComponents, LoadContentComponents, etc.
const loaderName = `Load${groupName.charAt(0).toUpperCase() + groupName.slice(1)}Components`;
const module = await import(
`@universityofmaryland/web-components-library/${groupName}`
);
if (module[loaderName]) module[loaderName]();
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(async (entry) => {
if (entry.isIntersecting) {
const tagName = entry.target.tagName.toLowerCase();
// Find which group this component belongs to
for (const [groupName, components] of Object.entries(componentGroups)) {
if (components.includes(tagName)) {
await loadComponentGroup(groupName);
observer.unobserve(entry.target);
break;
}
}
}
});
}, {
// Start loading when component is 200px away from viewport
rootMargin: '200px',
});
// Observe all uninitialized components
Object.values(componentGroups).flat().forEach(tagName => {
document.querySelectorAll(tagName).forEach(element => {
observer.observe(element);
});
});
};
Critical CSS Strategy
The design system provides multiple approaches for CSS loading. For most projects, static CSS files are the simplest and most performant option. For advanced use cases, programmatic CSS loading is also available.
Static CSS Files (Recommended)
Pre-compiled CSS files are available in the styles package (v1.8.0+). This is the simplest approach for most projects.
<!-- Critical CSS (render-blocking) -->
<link rel="stylesheet" href="https://unpkg.com/@universityofmaryland/web-styles-library/dist/css/font-faces.min.css">
<link rel="stylesheet" href="https://unpkg.com/@universityofmaryland/web-styles-library/dist/css/tokens.min.css">
<link rel="stylesheet" href="https://unpkg.com/@universityofmaryland/web-styles-library/dist/css/base.min.css">
<!-- Non-critical CSS (load async) -->
<link rel="stylesheet" href="https://unpkg.com/@universityofmaryland/web-styles-library/dist/css/layout.min.css" media="print" onload="this.media='all'">
<link rel="stylesheet" href="https://unpkg.com/@universityofmaryland/web-styles-library/dist/css/element.min.css" media="print" onload="this.media='all'">
<link rel="stylesheet" href="https://unpkg.com/@universityofmaryland/web-styles-library/dist/css/web-components.min.css" media="print" onload="this.media='all'">
Bundler CSS Import
// Import in your entry file
import '@universityofmaryland/web-styles-library/css/styles.min.css';
// Or selective imports
import '@universityofmaryland/web-styles-library/css/tokens.min.css';
import '@universityofmaryland/web-styles-library/css/font-faces.min.css';
import '@universityofmaryland/web-styles-library/css/base.min.css';
import '@universityofmaryland/web-styles-library/css/typography.min.css';
import '@universityofmaryland/web-styles-library/css/layout.min.css';
For a complete guide to the static CSS files, including all available files and usage patterns, see the Static CSS documentation.
Programmatic CSS Loading (Advanced)
For cases where static CSS files are not sufficient — such as when you need dynamic theme switching, conditional style loading, or fine-grained control over CSS injection order — the styles package provides a programmatic API.
// styles.ts - Critical CSS loading implementation
import * as Styles from '@universityofmaryland/web-styles-library';
const inlineCriticalStyles = async () => {
try {
// Load critical styles in parallel
const [preRenderCss, fonts] = await Promise.all([
Styles.preRenderCss,
Promise.resolve(Styles.typography.fontFace.base64fonts),
]);
// Create inline style element for critical CSS
const criticalStyle = document.createElement('style');
criticalStyle.id = 'critical-css';
criticalStyle.innerHTML = `
${fonts}
${preRenderCss}
`;
// Insert critical styles before any other styles
const firstStyle = document.head.querySelector('style');
if (firstStyle) {
document.head.insertBefore(criticalStyle, firstStyle);
} else {
document.head.appendChild(criticalStyle);
}
// Load non-critical styles when browser is idle
requestIdleCallback(() => loadNonCriticalStyles());
} catch (error) {
console.error('Failed to load critical styles:', error);
// Ensure content is visible even if styles fail
document.body.style.opacity = '1';
}
};
const loadNonCriticalStyles = async () => {
try {
// Load decorative and below-the-fold styles
const postRenderCss = await Styles.postRenderCss;
const style = document.createElement('style');
style.id = 'non-critical-css';
style.innerHTML = postRenderCss;
document.head.appendChild(style);
} catch (error) {
console.error('Failed to load non-critical styles:', error);
document.body.style.opacity = '1';
}
};
// Start loading immediately or when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', inlineCriticalStyles, {
once: true,
});
} else {
inlineCriticalStyles();
}
export { inlineCriticalStyles, loadNonCriticalStyles };
- Phase 1: Critical CSS - Inline base64 fonts and above-the-fold styles directly in the document head
- Phase 2: Non-Critical CSS - Load decorative styles and animations during idle time
- Fallback Strategy - Always reveal content if styles fail to load
Performance Metrics
Expected performance improvements when implementing these optimization strategies:
Initial Bundle Size
Reduction with grouped exports vs full import
Time to Interactive
Faster with progressive loading strategy
First Contentful Paint
Improvement with critical CSS inlining
Cache Hit Rate
Better with vendor chunk splitting
Measuring Performance
// Add performance monitoring to track improvements
const measureComponentLoad = (componentName) => {
const startMark = `${componentName}-start`;
const endMark = `${componentName}-end`;
const measureName = `${componentName}-load`;
performance.mark(startMark);
return {
complete: () => {
performance.mark(endMark);
performance.measure(measureName, startMark, endMark);
const measure = performance.getEntriesByName(measureName)[0];
console.log(`${componentName} loaded in ${measure.duration.toFixed(2)}ms`);
// Send to analytics
if (window.gtag) {
window.gtag('event', 'timing_complete', {
name: componentName,
value: Math.round(measure.duration),
event_category: 'Component Loading',
});
}
}
};
};
// Usage example
const heroTimer = measureComponentLoad('hero-components');
await loadStructuralComponents();
heroTimer.complete();
Best Practices Checklist
Follow these recommendations to achieve optimal performance with the UMD Design System:
Loading Strategy
- Load structural components (hero, navigation) immediately for above-the-fold content
- Defer interactive components until user interaction or idle time
- Use Intersection Observer for below-the-fold components
- Implement fallback timers to ensure components eventually load
- Leverage
requestIdleCallbackfor non-critical resources
Bundle Optimization
- Use grouped exports for logical component sets instead of importing everything
- Configure vendor chunk splitting for better caching across deployments
- Enable production minification with console.log removal
- Set appropriate chunk size warning limits (500KB recommended)
- Use path aliases to simplify imports and improve maintainability
CSS Performance
- Use static CSS files for the simplest and fastest loading approach
- Split critical and non-critical CSS using async loading patterns
- Inline critical CSS including base64 fonts for immediate rendering
- Defer non-critical styles using
requestIdleCallbackor async link tags - Always provide fallbacks for CSS loading failures
Development Workflow
- Use the bundle analyzer (
ANALYZE=true npm run build) to identify optimization opportunities - Monitor performance metrics in development to catch regressions early
- Test with network throttling to simulate real-world conditions
- Validate that tree-shaking is working correctly for unused components
- Keep component dependencies minimal to reduce bundle size
Quick Start Template
Copy this minimal setup for optimal performance out of the box:
// main.js - Minimal performance-optimized setup
import './styles/critical.css';
// Load structural components immediately
import('@universityofmaryland/web-components-library/structural')
.then(m => m.LoadStructuralComponents());
// Load content when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
import('@universityofmaryland/web-components-library/content')
.then(m => m.LoadContentComponents());
});
}
// Defer interactive components
requestIdleCallback(() => {
import('@universityofmaryland/web-components-library/interactive')
.then(m => m.LoadInteractiveComponents());
}, { timeout: 2000 });
Related Resources
Components API Reference
Complete API documentation for all component exports.
Static CSS Guide
Pre-compiled CSS files for zero-JavaScript styling.
Advanced Usage
TypeScript integration and custom components.