`n

Code Splitting Strategies - Complete Guide

Published: September 25, 2024 | Reading time: 21 minutes

Code Splitting Overview

Code splitting improves application performance by loading only necessary code:

Code Splitting Benefits
# Code Splitting Benefits
- Faster initial page load
- Reduced bundle size
- Better caching strategies
- Improved user experience
- Lower bandwidth usage
- Better Core Web Vitals
- Enhanced mobile performance

Dynamic Imports

ES6 Dynamic Imports

Dynamic Import Implementation
# Dynamic Import Strategies

# 1. Basic Dynamic Import
// Load module on demand
async function loadModule() {
    try {
        const module = await import('./heavy-module.js');
        return module.default;
    } catch (error) {
        console.error('Failed to load module:', error);
    }
}

// Usage
const heavyModule = await loadModule();
heavyModule.doSomething();

# 2. Conditional Loading
class ConditionalLoader {
    constructor() {
        this.loadedModules = new Map();
    }
    
    async loadIfNeeded(modulePath, condition) {
        if (condition && !this.loadedModules.has(modulePath)) {
            try {
                const module = await import(modulePath);
                this.loadedModules.set(modulePath, module);
                return module;
            } catch (error) {
                console.error(`Failed to load ${modulePath}:`, error);
                return null;
            }
        }
        
        return this.loadedModules.get(modulePath);
    }
    
    async loadAdminModule() {
        return await this.loadIfNeeded('./admin.js', this.isAdmin());
    }
    
    async loadAnalyticsModule() {
        return await this.loadIfNeeded('./analytics.js', this.hasAnalyticsConsent());
    }
    
    isAdmin() {
        return localStorage.getItem('userRole') === 'admin';
    }
    
    hasAnalyticsConsent() {
        return localStorage.getItem('analyticsConsent') === 'true';
    }
}

# 3. Route-Based Code Splitting
// React Router example
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';

// Lazy load components
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));
const Admin = lazy(() => import('./pages/Admin'));

function App() {
    return (
        
            Loading...
}> } /> } /> } /> } /> ); } # 4. Feature-Based Code Splitting class FeatureLoader { constructor() { this.features = new Map(); this.loadingPromises = new Map(); } async loadFeature(featureName) { // Prevent duplicate loading if (this.loadingPromises.has(featureName)) { return await this.loadingPromises.get(featureName); } if (this.features.has(featureName)) { return this.features.get(featureName); } const loadingPromise = this.loadFeatureModule(featureName); this.loadingPromises.set(featureName, loadingPromise); try { const feature = await loadingPromise; this.features.set(featureName, feature); this.loadingPromises.delete(featureName); return feature; } catch (error) { this.loadingPromises.delete(featureName); throw error; } } async loadFeatureModule(featureName) { const modulePath = `./features/${featureName}.js`; const module = await import(modulePath); return module.default; } async loadChatFeature() { return await this.loadFeature('chat'); } async loadVideoFeature() { return await this.loadFeature('video'); } } # 5. Error Boundary for Code Splitting class CodeSplitErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error) { return { hasError: true, error }; } componentDidCatch(error, errorInfo) { console.error('Code split error:', error, errorInfo); // Report error to monitoring service this.reportError(error, errorInfo); } reportError(error, errorInfo) { // Send to error tracking service if (window.gtag) { gtag('event', 'exception', { description: error.toString(), fatal: false }); } } render() { if (this.state.hasError) { return (

Something went wrong loading this component.

); } return this.props.children; } } # 6. Preloading Strategies class ModulePreloader { constructor() { this.preloadedModules = new Set(); } preloadModule(modulePath) { if (this.preloadedModules.has(modulePath)) { return; } // Preload module without executing const link = document.createElement('link'); link.rel = 'modulepreload'; link.href = modulePath; document.head.appendChild(link); this.preloadedModules.add(modulePath); } preloadOnHover(element, modulePath) { element.addEventListener('mouseenter', () => { this.preloadModule(modulePath); }); } preloadOnIntersection(element, modulePath) { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { this.preloadModule(modulePath); observer.unobserve(entry.target); } }); }); observer.observe(element); } }

Webpack Code Splitting

Webpack Configuration

Webpack Code Splitting Setup
# Webpack Code Splitting Configuration

# 1. Basic Webpack Configuration
const path = require('path');

module.exports = {
    entry: {
        main: './src/index.js',
        vendor: './src/vendor.js'
    },
    output: {
        filename: '[name].[contenthash].js',
        path: path.resolve(__dirname, 'dist'),
        clean: true
    },
    optimization: {
        splitChunks: {
            chunks: 'all',
            cacheGroups: {
                vendor: {
                    test: /[\\/]node_modules[\\/]/,
                    name: 'vendors',
                    chunks: 'all'
                }
            }
        }
    }
};

# 2. Advanced Split Chunks Configuration
module.exports = {
    optimization: {
        splitChunks: {
            chunks: 'all',
            minSize: 20000,
            maxSize: 244000,
            cacheGroups: {
                default: {
                    minChunks: 2,
                    priority: -20,
                    reuseExistingChunk: true
                },
                vendor: {
                    test: /[\\/]node_modules[\\/]/,
                    name: 'vendors',
                    priority: -10,
                    chunks: 'all'
                },
                common: {
                    name: 'common',
                    minChunks: 2,
                    priority: -5,
                    reuseExistingChunk: true
                },
                styles: {
                    name: 'styles',
                    test: /\.(css|scss|sass)$/,
                    chunks: 'all',
                    enforce: true
                }
            }
        }
    }
};

# 3. Dynamic Import with Webpack
// webpack.config.js
module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env'],
                        plugins: ['@babel/plugin-syntax-dynamic-import']
                    }
                }
            }
        ]
    }
};

# 4. Bundle Analysis
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
    plugins: [
        new BundleAnalyzerPlugin({
            analyzerMode: 'static',
            openAnalyzer: false,
            reportFilename: 'bundle-report.html'
        })
    ]
};

# 5. Custom Split Chunks Strategy
module.exports = {
    optimization: {
        splitChunks: {
            chunks: 'all',
            cacheGroups: {
                // React and related libraries
                react: {
                    test: /[\\/]node_modules[\\/](react|react-dom|react-router)[\\/]/,
                    name: 'react',
                    chunks: 'all',
                    priority: 20
                },
                // Utility libraries
                utils: {
                    test: /[\\/]node_modules[\\/](lodash|moment|date-fns)[\\/]/,
                    name: 'utils',
                    chunks: 'all',
                    priority: 15
                },
                // UI libraries
                ui: {
                    test: /[\\/]node_modules[\\/](@material-ui|antd|bootstrap)[\\/]/,
                    name: 'ui',
                    chunks: 'all',
                    priority: 10
                },
                // Default vendor chunk
                vendor: {
                    test: /[\\/]node_modules[\\/]/,
                    name: 'vendor',
                    chunks: 'all',
                    priority: 5
                }
            }
        }
    }
};

# 6. Webpack Performance Budget
module.exports = {
    performance: {
        maxAssetSize: 250000,
        maxEntrypointSize: 250000,
        hints: 'warning',
        assetFilter: function(assetFilename) {
            return assetFilename.endsWith('.js') || assetFilename.endsWith('.css');
        }
    }
};

# 7. Webpack Plugin for Code Splitting
class CodeSplittingPlugin {
    constructor(options = {}) {
        this.options = {
            chunkSizeLimit: 244000,
            maxChunks: 10,
            ...options
        };
    }
    
    apply(compiler) {
        compiler.hooks.emit.tapAsync('CodeSplittingPlugin', (compilation, callback) => {
            const chunks = compilation.chunks;
            const oversizedChunks = chunks.filter(chunk => 
                chunk.size() > this.options.chunkSizeLimit
            );
            
            if (oversizedChunks.length > 0) {
                console.warn('Oversized chunks detected:', oversizedChunks.map(chunk => ({
                    name: chunk.name,
                    size: chunk.size()
                })));
            }
            
            callback();
        });
    }
}

React Code Splitting

React-Specific Strategies

React Code Splitting Implementation
# React Code Splitting Strategies

# 1. React.lazy() Implementation
import React, { Suspense, lazy } from 'react';

// Lazy load components
const Dashboard = lazy(() => import('./Dashboard'));
const Profile = lazy(() => import('./Profile'));
const Settings = lazy(() => import('./Settings'));

function App() {
    const [currentView, setCurrentView] = React.useState('dashboard');
    
    const renderCurrentView = () => {
        switch (currentView) {
            case 'dashboard':
                return ;
            case 'profile':
                return ;
            case 'settings':
                return ;
            default:
                return ;
        }
    };
    
    return (
        
Loading...
}> {renderCurrentView()}
); } # 2. Higher-Order Component for Code Splitting function withCodeSplitting(importFunc, fallback = null) { return function WrappedComponent(props) { const [Component, setComponent] = React.useState(null); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); React.useEffect(() => { importFunc() .then(module => { setComponent(() => module.default); setLoading(false); }) .catch(err => { setError(err); setLoading(false); }); }, []); if (loading) { return fallback ||
Loading...
; } if (error) { return
Error loading component: {error.message}
; } return ; }; } // Usage const LazyChart = withCodeSplitting( () => import('./Chart'),
Loading chart...
); # 3. Custom Hook for Code Splitting function useCodeSplit(importFunc, deps = []) { const [component, setComponent] = React.useState(null); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); React.useEffect(() => { let cancelled = false; importFunc() .then(module => { if (!cancelled) { setComponent(() => module.default); setLoading(false); } }) .catch(err => { if (!cancelled) { setError(err); setLoading(false); } }); return () => { cancelled = true; }; }, deps); return { component: component, loading, error }; } // Usage function MyComponent() { const { component: Chart, loading, error } = useCodeSplit( () => import('./Chart') ); if (loading) return
Loading chart...
; if (error) return
Error: {error.message}
; return ; } # 4. Route-Based Code Splitting with React Router import React, { Suspense, lazy } from 'react'; import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; // Lazy load route components const Home = lazy(() => import('./pages/Home')); const About = lazy(() => import('./pages/About')); const Contact = lazy(() => import('./pages/Contact')); const Blog = lazy(() => import('./pages/Blog')); const Admin = lazy(() => import('./pages/Admin')); // Loading component const LoadingSpinner = () => (

Loading...

); function App() { return (
}> } /> } /> } /> } /> } />
); } # 5. Conditional Code Splitting class ConditionalCodeSplit extends React.Component { constructor(props) { super(props); this.state = { component: null, loading: false, error: null }; } async loadComponent() { if (this.state.component || this.state.loading) { return; } this.setState({ loading: true, error: null }); try { const module = await import('./HeavyComponent'); this.setState({ component: module.default, loading: false }); } catch (error) { this.setState({ error: error.message, loading: false }); } } render() { const { component: Component, loading, error } = this.state; if (error) { return
Error loading component: {error}
; } if (loading) { return
Loading...
; } if (Component) { return ; } return ( ); } } # 6. Preloading with React class PreloadableComponent extends React.Component { constructor(props) { super(props); this.state = { preloaded: false, component: null }; } preloadComponent = async () => { if (this.state.preloaded) return; try { const module = await import(this.props.importPath); this.setState({ preloaded: true, component: module.default }); } catch (error) { console.error('Preload failed:', error); } }; render() { const { component: Component } = this.state; return (
{Component ? ( ) : ( this.props.children )}
); } } // Usage

Vue.js Code Splitting

Vue-Specific Strategies

Vue.js Code Splitting Implementation
# Vue.js Code Splitting Strategies

# 1. Vue Router Code Splitting
import { createRouter, createWebHistory } from 'vue-router';

const routes = [
    {
        path: '/',
        name: 'Home',
        component: () => import('../views/Home.vue')
    },
    {
        path: '/about',
        name: 'About',
        component: () => import('../views/About.vue')
    },
    {
        path: '/contact',
        name: 'Contact',
        component: () => import('../views/Contact.vue')
    },
    {
        path: '/admin',
        name: 'Admin',
        component: () => import('../views/Admin.vue'),
        meta: { requiresAuth: true }
    }
];

const router = createRouter({
    history: createWebHistory(),
    routes
});

export default router;

# 2. Vue Component Lazy Loading
// App.vue




# 3. Vue Composition API Code Splitting
import { defineAsyncComponent, ref, onMounted } from 'vue';

export default {
    setup() {
        const showChart = ref(false);
        const ChartComponent = ref(null);
        
        const loadChart = async () => {
            if (!ChartComponent.value) {
                ChartComponent.value = defineAsyncComponent(() => 
                    import('./Chart.vue')
                );
            }
            showChart.value = true;
        };
        
        return {
            showChart,
            ChartComponent,
            loadChart
        };
    }
};

# 4. Vue Plugin for Code Splitting
// plugins/code-splitting.js
export default {
    install(app, options = {}) {
        const { preloadOnHover = true } = options;
        
        app.directive('lazy-load', {
            mounted(el, binding) {
                const importFunc = binding.value;
                
                if (preloadOnHover) {
                    el.addEventListener('mouseenter', async () => {
                        if (!el._component) {
                            try {
                                const module = await importFunc();
                                el._component = module.default;
                            } catch (error) {
                                console.error('Failed to load component:', error);
                            }
                        }
                    });
                }
            }
        });
    }
};

# 5. Vue Mixin for Code Splitting
export const codeSplittingMixin = {
    data() {
        return {
            loadedComponents: new Map()
        };
    },
    
    methods: {
        async loadComponent(importFunc, componentName) {
            if (this.loadedComponents.has(componentName)) {
                return this.loadedComponents.get(componentName);
            }
            
            try {
                const module = await importFunc();
                this.loadedComponents.set(componentName, module.default);
                return module.default;
            } catch (error) {
                console.error(`Failed to load ${componentName}:`, error);
                throw error;
            }
        }
    }
};

# 6. Vue 3 Composition API Hook
import { ref, onMounted } from 'vue';

export function useCodeSplit(importFunc) {
    const component = ref(null);
    const loading = ref(true);
    const error = ref(null);
    
    onMounted(async () => {
        try {
            const module = await importFunc();
            component.value = module.default;
            loading.value = false;
        } catch (err) {
            error.value = err;
            loading.value = false;
        }
    });
    
    return {
        component,
        loading,
        error
    };
}

// Usage in component
export default {
    setup() {
        const { component: Chart, loading, error } = useCodeSplit(
            () => import('./Chart.vue')
        );
        
        return {
            Chart,
            loading,
            error
        };
    }
};

Performance Monitoring

Code Splitting Performance Tracking

Performance Monitoring Setup
# Code Splitting Performance Monitoring

# 1. Bundle Size Monitoring
class BundleSizeMonitor {
    constructor() {
        this.metrics = {
            initialLoad: 0,
            chunksLoaded: 0,
            totalSize: 0,
            loadTimes: []
        };
    }
    
    trackInitialLoad() {
        const startTime = performance.now();
        
        window.addEventListener('load', () => {
            const loadTime = performance.now() - startTime;
            this.metrics.initialLoad = loadTime;
            this.recordLoadTime('initial', loadTime);
        });
    }
    
    trackChunkLoad(chunkName) {
        const startTime = performance.now();
        
        return () => {
            const loadTime = performance.now() - startTime;
            this.metrics.chunksLoaded++;
            this.recordLoadTime(chunkName, loadTime);
        };
    }
    
    recordLoadTime(chunkName, loadTime) {
        this.metrics.loadTimes.push({
            chunk: chunkName,
            time: loadTime,
            timestamp: Date.now()
        });
    }
    
    getAverageLoadTime() {
        const totalTime = this.metrics.loadTimes.reduce((sum, entry) => sum + entry.time, 0);
        return totalTime / this.metrics.loadTimes.length;
    }
    
    getReport() {
        return {
            ...this.metrics,
            averageLoadTime: this.getAverageLoadTime(),
            chunksPerSecond: this.metrics.chunksLoaded / (this.metrics.initialLoad / 1000)
        };
    }
}

# 2. Code Splitting Analytics
class CodeSplittingAnalytics {
    constructor() {
        this.events = [];
        this.performanceObserver = new PerformanceObserver(this.handlePerformanceEntry.bind(this));
        this.performanceObserver.observe({ entryTypes: ['navigation', 'resource'] });
    }
    
    handlePerformanceEntry(list) {
        for (const entry of list.getEntries()) {
            if (entry.entryType === 'resource' && entry.name.includes('.js')) {
                this.trackChunkLoad(entry);
            }
        }
    }
    
    trackChunkLoad(entry) {
        const chunkInfo = {
            name: this.extractChunkName(entry.name),
            size: entry.transferSize,
            loadTime: entry.duration,
            timestamp: entry.startTime
        };
        
        this.events.push(chunkInfo);
        
        // Send to analytics
        this.sendToAnalytics('chunk_loaded', chunkInfo);
    }
    
    extractChunkName(url) {
        const match = url.match(/\/([^\/]+)\.js$/);
        return match ? match[1] : 'unknown';
    }
    
    sendToAnalytics(eventName, data) {
        if (window.gtag) {
            gtag('event', eventName, {
                chunk_name: data.name,
                chunk_size: data.size,
                load_time: data.loadTime
            });
        }
    }
    
    trackUserInteraction(interaction) {
        this.events.push({
            type: 'interaction',
            action: interaction,
            timestamp: Date.now()
        });
    }
}

# 3. Webpack Bundle Analyzer Integration
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
    plugins: [
        new BundleAnalyzerPlugin({
            analyzerMode: 'static',
            openAnalyzer: false,
            reportFilename: 'bundle-report.html',
            generateStatsFile: true,
            statsFilename: 'bundle-stats.json'
        })
    ]
};

# 4. Real-time Performance Monitoring
class RealTimePerformanceMonitor {
    constructor() {
        this.metrics = new Map();
        this.observers = [];
    }
    
    startMonitoring() {
        // Monitor chunk loading
        this.observeChunkLoading();
        
        // Monitor user interactions
        this.observeUserInteractions();
        
        // Monitor memory usage
        this.observeMemoryUsage();
    }
    
    observeChunkLoading() {
        const observer = new PerformanceObserver((list) => {
            for (const entry of list.getEntries()) {
                if (entry.entryType === 'resource' && entry.name.includes('.js')) {
                    this.recordChunkMetric(entry);
                }
            }
        });
        
        observer.observe({ entryTypes: ['resource'] });
        this.observers.push(observer);
    }
    
    observeUserInteractions() {
        ['click', 'scroll', 'keydown'].forEach(eventType => {
            document.addEventListener(eventType, (event) => {
                this.recordInteractionMetric(eventType, event);
            });
        });
    }
    
    observeMemoryUsage() {
        if ('memory' in performance) {
            setInterval(() => {
                this.recordMemoryMetric();
            }, 5000);
        }
    }
    
    recordChunkMetric(entry) {
        const metric = {
            type: 'chunk',
            name: this.extractChunkName(entry.name),
            size: entry.transferSize,
            loadTime: entry.duration,
            timestamp: Date.now()
        };
        
        this.metrics.set(`chunk_${metric.name}`, metric);
    }
    
    recordInteractionMetric(eventType, event) {
        const metric = {
            type: 'interaction',
            eventType,
            timestamp: Date.now(),
            target: event.target.tagName
        };
        
        this.metrics.set(`interaction_${Date.now()}`, metric);
    }
    
    recordMemoryMetric() {
        if ('memory' in performance) {
            const metric = {
                type: 'memory',
                used: performance.memory.usedJSHeapSize,
                total: performance.memory.totalJSHeapSize,
                limit: performance.memory.jsHeapSizeLimit,
                timestamp: Date.now()
            };
            
            this.metrics.set('memory', metric);
        }
    }
    
    getMetrics() {
        return Array.from(this.metrics.values());
    }
    
    cleanup() {
        this.observers.forEach(observer => observer.disconnect());
    }
}

# 5. Performance Budget Monitoring
class PerformanceBudgetMonitor {
    constructor(budget) {
        this.budget = {
            maxInitialLoad: 3000, // 3 seconds
            maxChunkSize: 244000, // 244KB
            maxChunks: 10,
            ...budget
        };
        this.violations = [];
    }
    
    checkBudget(metrics) {
        const violations = [];
        
        if (metrics.initialLoad > this.budget.maxInitialLoad) {
            violations.push({
                type: 'initial_load',
                value: metrics.initialLoad,
                limit: this.budget.maxInitialLoad,
                severity: 'error'
            });
        }
        
        if (metrics.chunksLoaded > this.budget.maxChunks) {
            violations.push({
                type: 'chunk_count',
                value: metrics.chunksLoaded,
                limit: this.budget.maxChunks,
                severity: 'warning'
            });
        }
        
        this.violations.push(...violations);
        return violations;
    }
    
    getViolations() {
        return this.violations;
    }
    
    generateReport() {
        return {
            budget: this.budget,
            violations: this.violations,
            summary: {
                totalViolations: this.violations.length,
                errors: this.violations.filter(v => v.severity === 'error').length,
                warnings: this.violations.filter(v => v.severity === 'warning').length
            }
        };
    }
}

Best Practices

Code Splitting Guidelines

Code Splitting Best Practices

  • Split by route boundaries
  • Split vendor libraries
  • Use dynamic imports
  • Implement error boundaries
  • Preload critical chunks
  • Monitor bundle sizes
  • Use performance budgets

Common Mistakes

  • Over-splitting code
  • No error handling
  • Ignoring loading states
  • Not preloading chunks
  • Poor chunk naming
  • No performance monitoring
  • Inconsistent splitting strategy

Summary

Code splitting strategies involve several key components:

Need More Help?

Struggling with code splitting implementation or need help optimizing your JavaScript bundles? Our performance experts can help you implement effective code splitting strategies.

Get Code Splitting Help