Implement Code Splitting on UI Library of Turbo Repo Kitchen Sink

Wednesday, August 6, 2025
code-splitting-ui-lib

Code splitting in the UI library of a Turbo Repo Kitchen Sink project involves strategically dividing your JavaScript bundles so that only the necessary code is loaded when required, improving performance and user experience. Implementing code splitting in a monorepo like Turborepo, especially for shared UI libraries, requires specific techniques suitable for React, Next.js, and similar frameworks commonly used in Turborepo setups.

Why Code Splitting Matters in a Turbo Repo UI Library

  • Performance: Reduces initial load times by serving only essential code on first load, then loading additional features or components as users interact with the app.[2][4]
  • Scalability: Makes it easier to manage and scale large codebases by isolating features into modular chunks.
  • Maintainability: Simplifies debugging and enhances code organization, particularly beneficial in a monorepo where code is shared across multiple applications.

Approaches to Code Splitting in Turborepo UI Libraries

1. Dynamic Imports with React.lazy and Suspense

The simplest way to split code at the component level is to use React’s built-in React.lazy coupled with Suspense:

import React, { Suspense } from 'react';
const HeavyComponent = React.lazy(() => import('@acme/ui/HeavyComponent')); // Path points to UI library in monorepo

function MyFeature() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <HeavyComponent />
    </Suspense>
  );
}

This ensures that HeavyComponent is only loaded when MyFeature is rendered.

  • Best for: Basic code splitting in React-based UI libraries.
  • Limitations: Limited control over preloading and error handling.

2. Third-party Libraries: Loadable Components

For more advanced use—especially in SSR contexts—third-party libraries like [Loadable Components][2] are preferable:

import loadable from '@loadable/component';

const Modal = loadable(() => import('@acme/ui/Modal'));

function Page() {
  return <Modal />;
}

Advantages:

  • SSR support (important for Next.js apps in Turborepo)
  • Customizable loading and error states
  • More granular control over chunk naming and preloading

3. Webpack and Next.js Native Code Splitting

  • Webpack: By default, dynamic import() statements instruct Webpack to create separate chunks, even within libraries. In Turborepo, ensure your UI package build is configured to output ES Modules so that consuming apps can leverage code splitting.
  • Next.js: Automatically code splits at the page level and with dynamic imports (next/dynamic). For shared components, use dynamic import syntax:
import dynamic from 'next/dynamic';

const Button = dynamic(() => import('@acme/ui/Button'));
  • Note: Next.js will generate separate bundles for these dynamically loaded components.

4. Practical Steps in a Turbo Repo

  1. Enable ES Modules in UI Package:
    Ensure your UI library’s package.json includes "type": "module", and the build outputs are ESM.

  2. Dynamic Import Usage:
    Use dynamic imports inside the consuming app or even within the UI library itself where heavyweight dependencies/components are involved.[4]

  3. Optimize Library Structure:

    • Refactor large, rarely-used components/features into separate entry points in the UI library.
    • Export individual components/modules instead of monolithic bundles.
  4. Test Chunk Generation:
    Use Webpack/Next.js analysis tools (like webpack-bundle-analyzer) to verify that components from your UI library are split into separate chunks when dynamically imported.


Considerations for Monorepo Code Splitting

  • Shared Dependencies: Code splitting is most effective when shared dependencies are excluded from vendor (common) chunks. Organize dependencies such that only what’s needed is loaded per component.[4]
  • SSR Compatibility: If your apps use server-side rendering, choose code splitting strategies compatible with SSR (e.g., loadable-components or Next.js’s built-in features).
  • Developer Experience: Ensure local development and builds remain fast by leveraging Turborepo’s caching—splitting your UI package into smaller modules can improve incremental rebuild times.[5]

Example: Code Splitting in Turborepo Kitchen Sink UI

Suppose you have a Turbo Repo with:

  • apps/web (Next.js app)
  • packages/ui (shared React components)

Step 1: Export each heavy component individually in packages/ui.

Step 2: In apps/web, use dynamic imports for these components:

// apps/web/pages/example.js
import dynamic from 'next/dynamic';

const Chart = dynamic(() => import('@acme/ui/Chart'), {
  loading: () => <span>Loading chart…</span>,
  ssr: false, // Adjust as needed
});

export default function ExamplePage() {
  return (
    <div>
      <Chart />
    </div>
  );
}

Step 3: Analyze and confirm, using bundle analyzer, that Chart is loaded as a separate chunk.


In summary, implement code splitting in a Turbo Repo UI library by structuring your package for modular imports, using dynamic imports in consuming apps, and supporting SSR with chosen libraries. This aligns with best practices for code splitting in modern monorepos and improves the maintainability and performance of your shared UI layer[2][4][5].

No comments: