For AI agents: the complete documentation index is available at /next/llms.txt, the full documentation bundle is available at /next/llms-full.txt, and this page is available as Markdown at /next/react/code-splitting.md.
  • English
  • Code Splitting

    Rspack supports code splitting, which allows splitting the code into other chunks. You have the full control about size and number of generated assets, which allow you to gain performance improvements in loading time.

    Rspack - Code Splitting

    Lazy-loading components

    Usually, you import components with the static import declaration:

    import LazyComponent from './LazyComponent.jsx';
    
    export function App() {
      return (
        <view>
          <LazyComponent />
        </view>
      );
    }

    To defer loading this component’s code until it’s rendered for the first time, replace this import with:

    - import LazyComponent from './LazyComponent.jsx'
    + import { lazy } from '@lynx-js/react'
    + const LazyComponent = lazy(() => import('./LazyComponent.jsx'))

    This code relies on dynamic import(), which is supported by Rspack. Using this pattern requires that the lazy component you are importing was exported as the default export.

    Now that your component’s code loads on demand, you also need to specify what should be displayed while it is loading. You can do this by wrapping the lazy component or any of its parents into a <Suspense> boundary:

    Info

    The split components will only start downloading when they are rendered.

    src/App.tsx
    import { Suspense, lazy } from '@lynx-js/react';
    
    const LazyComponent = lazy(() => import('./LazyComponent.jsx'));
    
    export function App() {
      return (
        <view>
          <Suspense fallback={<text>Loading...</text>}>
            <LazyComponent />
          </Suspense>
        </view>
      );
    }

    Load lazy component when needed

    In this example, the code for LazyComponent won’t be loaded until you attempt to render it. If LazyComponent hasn’t loaded yet, a "Loading..." will be shown in its place. For example:

    src/App.tsx
    import { Suspense, lazy, useState } from '@lynx-js/react';
    
    const LazyComponent = lazy(() => import('./LazyComponent.jsx'));
    
    export function App() {
      const [shouldDisplay, setShouldDisplay] = useState(false);
      const handleClick = () => {
        setShouldDisplay(true);
      };
      return (
        <view>
          <view bindtap={handleClick}>Load Component</view>
          {shouldDisplay && (
            <Suspense fallback={<text>Loading...</text>}>
              <LazyComponent />
            </Suspense>
          )}
        </view>
      );
    }
    Differences from the Web

    In Lynx, CSS is scoped to each Bundle. Since lazy-loaded components generate their own Bundles (see output structure at Output Files), you shouldn’t expect them to behave like on the Web. Global CSS defined in the main Bundle will not affect lazy-loaded components, and vice versa.

    Error handling

    Use ErrorBoundary

    If loading is completed, lazy-loaded components are essentially also a React component, so the error handling practices in React are still applicable.

    Checkout React - Catching rendering errors with an error boundary for details.

    Lazy-loading standalone project

    You may also lazy-load modules that being built in a standalone Rspeedy project.

    Glossary of Terms

    • Producer (Remote): An application that exposes modules to be consumed by other Lynx applications.
    • Consumer (Host): An application that consumes modules from other Producers.

    Create a standalone Producer project

    Create a standalone project using create-rspeedy:

    pnpm create rspeedy@latest

    Then add experimental_isLazyBundle to the options of pluginReactLynx in the lynx.config.js:

    import { pluginReactLynx } from '@lynx-js/react-rsbuild-plugin';
    import { defineConfig } from '@lynx-js/rspeedy';
    
    export default defineConfig({
      source: {
        entry: './src/index.tsx',
      },
      plugins: [
        pluginReactLynx({
          experimental_isLazyBundle: true,
        }),
      ],
    });

    Finally, change the index.tsx to export the App.

    src/index.tsx
    import { App } from './App.jsx';
    
    export default App;

    Modify the Consumer project

    To load the Producer project, add an import to @lynx-js/react/experimental/lazy/import at the beginning of the entry.

    src/index.tsx
    import '@lynx-js/react/experimental/lazy/import';
    import { root } from '@lynx-js/react';
    
    import { App } from './App.jsx';
    
    root.render(<App />);

    This would provide essential APIs that the Producer needs.

    Then, the Producer could be loaded using dynamic import().

    src/App.tsx
    import { Suspense, lazy } from '@lynx-js/react';
    
    const LazyComponent = lazy(
      () =>
        import('https://<host>:<port>/path/to/lynx.bundle', {
          with: { type: 'component' },
        }),
    );
    
    export function App() {
      return (
        <view>
          <Suspense fallback={<text>Loading...</text>}>
            <LazyComponent />
          </Suspense>
        </view>
      );
    }

    Developing Producer project

    It is recommended to create a separated Consumer in the Producer project.

    src/Consumer.tsx
    import { Suspense, lazy, root } from '@lynx-js/react';
    
    // You may use static import if you want
    const App = lazy(() => import('./App.jsx'));
    
    root.render(
      <Suspense>
        <App />
      </Suspense>,
    );

    Then, create a separated lynx.config.consumer.js:

    lynx.config.consumer.js
    import { pluginReactLynx } from '@lynx-js/react-rsbuild-plugin';
    import { defineConfig } from '@lynx-js/rspeedy';
    
    export default defineConfig({
      source: {
        entry: './src/Consumer.tsx',
      },
      plugins: [pluginReactLynx()],
    });

    Use npx rspeedy dev --config lynx.config.consumer.js to start developing the producer project.

    Lazy bundle loaders

    Lazy bundles are fetched through one of two loaders. Which one a build uses is decided at build time and exposed to your code as the __LAZY_BUNDLE_FETCHER__ macro.

    LoaderHow bundles are fetched
    FetchBundlelynx.fetchBundle, and the import(..., { with: { mode } }) mode hints
    QueryComponentThe legacy lynx.QueryComponent path

    By default the loader follows engineVersion: FetchBundle when it is 3.9 or higher, QueryComponent otherwise. engineVersion defaults to 3.2, so a build that does not raise it to 3.9 gets QueryComponent.

    The REACT_LAZY_BUNDLE_FETCHER environment variable overrides that choice:

    REACT_LAZY_BUNDLE_FETCHER=QueryComponent rspeedy build
    • QueryComponent forces the legacy loader even when engineVersion is 3.9 or higher.
    • FetchBundle is only accepted when engineVersion is 3.9 or higher. On a lower declared version the build fails, because older hosts do not expose lynx.fetchBundle / lynx.loadScript.
    • Any other value is ignored, and the loader falls back to the engineVersion-derived choice.
    Version scope

    The FetchBundle loader and this selection logic arrived in @lynx-js/react@0.123.0 / @lynx-js/react-rsbuild-plugin@0.18.0, which release together. Earlier versions always use QueryComponent.

    Except as otherwise noted, this work is licensed under a Creative Commons Attribution 4.0 International License, and code samples are licensed under the Apache License 2.0.