| name | wsh-code-splitting |
| description | Code splitting strategies for WSH 2026 — React.lazy, Suspense, splitChunks, and route-based splitting for the CaX app. |
WSH: Code Splitting
Splitting the monolithic 108MB bundle into route-based chunks reduces initial load time dramatically. Only the code needed for the current page is loaded upfront.
Use this skill when implementing React.lazy, Suspense, webpack splitChunks, or route-based code splitting.
Techniques
1. Route-Based Lazy Loading
import { TimelineContainer } from "./TimelineContainer";
import { PostContainer } from "./PostContainer";
const TimelineContainer = React.lazy(() => import("./TimelineContainer"));
const PostContainer = React.lazy(() => import("./PostContainer"));
const DirectMessageListContainer = React.lazy(() => import("./DirectMessageListContainer"));
const DirectMessageContainer = React.lazy(() => import("./DirectMessageContainer"));
const SearchContainer = React.lazy(() => import("./SearchContainer"));
const UserProfileContainer = React.lazy(() => import("./UserProfileContainer"));
const TermContainer = React.lazy(() => import("./TermContainer"));
const CrokContainer = React.lazy(() => import("./CrokContainer"));
const NotFoundContainer = React.lazy(() => import("./NotFoundContainer"));
<Suspense fallback={<div />}>
<Routes>
<Route element={<TimelineContainer />} path="/" />
...
</Routes>
</Suspense>
Impact: Initial bundle loads only shared code + current route. TBT -40-60%.
2. Enable splitChunks
optimization: {
splitChunks: {
chunks: "all",
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendor",
chunks: "all",
},
},
},
}
3. Dynamic Import for Heavy Libraries
Libraries like katex, react-markdown, react-syntax-highlighter should be dynamically imported only when needed.
const ReactMarkdown = React.lazy(() => import("react-markdown"));
Pitfalls
| Pitfall | Symptom | Fix |
|---|
| Missing Suspense boundary | React error: "A component suspended..." | Wrap lazy components in Suspense |
| Suspense fallback too visible | CLS increase from layout shift | Use minimal/invisible fallback |
| Named exports with React.lazy | Build error — lazy requires default export | Use import(...).then(m => ({ default: m.NamedExport })) |
VRT Risks
| Change | Visual Impact | Mitigation |
|---|
| Suspense fallback | Brief flash before content | Use empty div as fallback |
| Chunk loading delay | Content appears later | Prefetch critical chunks |
Project-Specific Notes
- All route containers are in
application/client/src/containers/
- AppContainer.tsx has 9 routes — all should be lazy loaded
- CrokContainer imports @mlc-ai/web-llm — perfect candidate for separate chunk
- Heavy libraries per route:
- Timeline: basic (Redux, React Router)
- Post detail: CoveredImage (image-size, piexifjs, Buffer), PausableMovie (gifler, omggif)
- Crok: @mlc-ai/web-llm, react-markdown, katex, react-syntax-highlighter
- Search: bayesian-bm25, kuromoji