From harness-claude
Implements code splitting strategies—route-based, component-based, dynamic imports, vendor optimization—in React/Next.js/Webpack/Vite to reduce bundle size and improve TTI in SPAs.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Master code splitting strategies — route-based splitting, component-based splitting, vendor chunk optimization, and dynamic imports to reduce initial bundle size and improve Time to Interactive across single-page applications and server-rendered frameworks.
Provides step-by-step guidance, configurations, and best practices for code splitting in React, Vue, CSS frontend projects, focusing on performance optimization and accessibility.
Guides React dynamic imports with React.lazy, Suspense, and import() for code splitting to reduce bundle size and improve startup. Use for modals, routes, below-fold content, large libraries.
Optimizes web apps via bundle analysis, code splitting, lazy loading, image optimization, caching headers, and virtual lists for Core Web Vitals.
Share bugs, ideas, or general feedback.
Master code splitting strategies — route-based splitting, component-based splitting, vendor chunk optimization, and dynamic imports to reduce initial bundle size and improve Time to Interactive across single-page applications and server-rendered frameworks.
Measure the current bundle. Before splitting, establish a baseline. Use the Coverage tab in Chrome DevTools (Ctrl+Shift+P, "Coverage") to identify unused bytes on page load. Run npx webpack-bundle-analyzer or npx vite-bundle-visualizer to see the composition of each chunk.
Implement route-based splitting. This is the highest-impact, lowest-risk form of code splitting. Each route becomes its own chunk, loaded only when the user navigates there:
// React with React.lazy
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const Analytics = lazy(() => import('./pages/Analytics'));
function App() {
return (
<Suspense fallback={<PageSkeleton />}>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
<Route path="/analytics" element={<Analytics />} />
</Routes>
</Suspense>
);
}
// Next.js — automatic route-based splitting via file-system routing
// pages/dashboard.tsx → separate chunk automatically
// pages/settings.tsx → separate chunk automatically
// For dynamic routes with heavy components:
import dynamic from 'next/dynamic';
const HeavyChart = dynamic(() => import('../components/HeavyChart'), {
loading: () => <ChartSkeleton />,
ssr: false, // skip server rendering for client-only components
});
Split component-level heavy dependencies. When a component imports a large library, split it at the component boundary:
// Before: monaco-editor (5MB) loaded on every page
import MonacoEditor from '@monaco-editor/react';
// After: loaded only when CodeEditor mounts
const CodeEditor = lazy(() => import('./CodeEditor'));
// CodeEditor.tsx imports monaco-editor internally
Configure vendor chunk splitting. Separate rarely-changing vendor code from frequently-changing application code so deploys only invalidate app chunks:
// webpack.config.js
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
framework: {
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
name: 'framework',
priority: 40,
enforce: true,
},
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
priority: 20,
minSize: 20000,
},
common: {
minChunks: 2,
priority: 10,
reuseExistingChunk: true,
},
},
},
}
Use named chunk comments for debugging. Webpack magic comments give chunks readable names in the Network panel:
const AdminPanel = lazy(() => import(/* webpackChunkName: "admin" */ './pages/AdminPanel'));
const PDFViewer = lazy(
() => import(/* webpackChunkName: "pdf-viewer" */ './components/PDFViewer')
);
Prefetch predictable navigation targets. After initial load completes, prefetch chunks the user is likely to visit next:
// Webpack magic comment for prefetch
const Settings = lazy(() => import(/* webpackPrefetch: true */ './pages/Settings'));
// Emits <link rel="prefetch" href="settings.chunk.js"> in <head>
// Manual prefetch on hover/focus for fine control
function NavLink({ to, component, children }) {
const prefetch = () => component.preload?.();
return (
<Link to={to} onMouseEnter={prefetch} onFocus={prefetch}>
{children}
</Link>
);
}
Set chunk size budgets. Configure warnings and errors when chunks exceed size limits:
// webpack.config.js
performance: {
maxEntrypointSize: 250000, // 250KB
maxAssetSize: 200000, // 200KB
hints: 'error', // fail the build if exceeded
}
Webpack, Rollup, and esbuild all recognize dynamic import() expressions as split points. When the bundler encounters import('./Module'), it creates a separate chunk containing that module and its unique dependencies. Shared dependencies are either duplicated or extracted into a common chunk depending on configuration. At runtime, the framework's chunk loading mechanism (webpack's __webpack_require__.e, Vite's native ESM imports) fetches the chunk via a network request and resolves the promise.
Splitting too aggressively creates many small chunks, increasing HTTP request overhead (especially on HTTP/1.1) and reducing compression efficiency. Splitting too conservatively leaves large monolithic bundles. The optimal strategy depends on the application: content sites benefit from aggressive route splitting (users visit 1-2 pages), while dashboards with frequent navigation benefit from larger shared chunks that amortize loading across views.
Shopify's checkout splits into three tiers: a critical shell chunk (React + routing, ~40KB gzipped) that loads instantly, route chunks for each checkout step (shipping, payment, confirmation at ~15-25KB each), and deferred chunks for optional features (address autocomplete, payment method animations). This achieves a 1.2s TTI on 3G for the first step, with subsequent steps loading in <200ms from prefetched chunks. The vendor chunk (React, Polaris) changes only on framework upgrades, maintaining >95% cache hit rate across deploys.
Splitting every component individually. Wrapping every component in lazy() creates dozens of tiny chunks that generate waterfall requests. Split at meaningful boundaries — routes, heavy feature modules, conditional features — not individual UI components.
Dynamic imports inside hot loops or render functions. Calling import() inside a component body without memoization triggers a new chunk load on every render. Always hoist lazy components to module scope or memoize them.
Ignoring the loading state. A Suspense fallback of null or a tiny spinner causes layout shift when the chunk loads. Use skeleton screens that match the loaded component's dimensions.
Not accounting for chunk load failures. Network failures during chunk loading crash the application. Wrap lazy components in an Error Boundary that offers a retry mechanism.