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/guide/performance/analysis-performance/analysis-bundle-size.md.
Lynx
  • English
  • Bundle Size

    Bundle size directly shapes download, parse, and first-screen time, yet it is the easiest metric to overlook. A dependency upgrade, an uncompressed image, or background-only code that leaks into the first-screen render path can each inflate your output.

    Unlike a regular web app, a Lynx app has a distinctive output structure because of its dual-thread architecture, and understanding that structure is the prerequisite for analyzing and optimizing size. Rspeedy is built on top of Rsbuild and Rspack, so most of their size-optimization techniques apply. This page explains the Lynx-specific output structure, then the analysis workflow and optimization levers, linking out to the deeper docs.

    Understand the Lynx output structure

    Dual-thread architecture: two JS layers

    ReactLynx splits React across two threads (see Thinking in ReactLynx):

    • Main thread: a lightweight UI driver responsible for Instant First-Frame Rendering (IFR), UI updates, and touch-following animation and gestures. It sits on the rendering critical path and must stay lean.
    • Background thread: the logic and state center that handles full render, lifecycle, setState, data requests, telemetry, and so on.

    Accordingly, the build emits two JS compilation intermediates per entry, then encodes them into the final binary artifact:

    LayerCompilation intermediateNotes
    Main-thread outputmain-thread.js (react:main-thread)First-screen render-path code; its size has the biggest impact on the first screen
    Background-thread outputbackground.js (react:background)App logic code; also produces the CSS. Does not block the first frame, but counts toward download and parse cost
    Static assetsimages / fonts / mediaOften the largest single contributor to a bundle

    The two JS layers are encoded together into a single binary .lynx.bundle, located under dist/ after a production build (see Output Files). That binary is what the Lynx engine loads, and it is the final metric to watch when measuring size.

    Main-thread output ≠ Main Thread Script

    "Main-thread output / main-thread.js" here means the compiled output of the main-thread layer. It is not the Main Thread Script, the programming model written with the 'main thread' directive for touch-following animation and gestures. Keep the two distinct.

    Inspect the readable intermediates

    .lynx.bundle is binary and can't be read directly. To inspect the readable main-thread.js / background.js, build with DEBUG, which keeps the intermediates under dist/.rspeedy/<entry>/:

    DEBUG=rspeedy rspeedy build

    A development build also emits these intermediates under dist/.rspeedy/; see Output Files.

    Code elimination: why the main-thread output should be small

    Because the main thread sits on the rendering critical path, any code only the background needs (network requests, NativeModule calls, loggers, telemetry, lifecycle, etc.) bloats both the bundle and the first-frame cost once it is bundled into the main-thread output.

    ReactLynx automatically eliminates code that shouldn't run on the main thread: by default, the callbacks of useEffect / useLayoutEffect and some event handlers are removed from main-thread.js (see Thinking in ReactLynx). When the compiler can't statically decide which thread a piece of code belongs to, you mark it explicitly:

    This elimination mechanism is exactly what the "slim down the main-thread output" lever below relies on.

    Analyze bundle size

    The guiding principle is measure first, optimize second: use data to confirm which layer the bytes are in before deciding what to touch, never guess and edit. Break the size down across three layers: static assets, background-thread output, and main-thread output.

    Use Rsdoctor

    Rsdoctor is a build-analysis tool that visualizes the asset list, module dependency graph, duplicate modules, and more. Enable it in a Rspeedy-based project:

    RSDOCTOR=true rspeedy build

    It opens an analysis page once the build finishes. Its module data carries a layer field, so you can attribute bytes to the react:main-thread and react:background layers, locate the largest modules, and trace "why was this module bundled in" through the real module graph rather than intuition. See Use Rsdoctor for the full workflow.

    Use the rspeedy-bundle-size Skill

    If you work with an AI coding tool that supports Agent Skills, load the rspeedy-bundle-size skill. It follows a measure-first workflow: it reads how your project builds, generates a size breakdown, attributes bytes layer by layer, and proposes optimizations in priority order. It is a good way to get an evidence-based report when you are not deeply familiar with the build internals.

    Optimize bundle size

    Once you have located where the size comes from, pick the lever that matches the layer.

    Slim down the main-thread output (strip background-only code)

    This is the Lynx-specific layer, and the first one to check. Move code only the background needs (requests / NativeModule calls / loggers / monitoring SDKs) off the render path with 'background only' or __BACKGROUND__, keeping it out of main-thread.js. If such code comes from a third-party library you can't edit, it becomes a library-side ask to add the directive at the definition site.

    Trim the background-thread output

    • Reduce duplicate dependencies: use Rsdoctor's duplicate-package detection to find dependencies bundled more than once, then converge them with resolve.dedupe or pnpm dedupe.
    • Use lighter libraries: use Rsdoctor to find the largest dependencies and evaluate lighter alternatives (for example dayjs instead of moment), or switch to importing only what you use.

    Compress static assets

    Images, fonts, and media are usually the largest part of a bundle. Compress them at the source, use appropriately sized assets, and split large, non-first-screen assets out of the main bundle.

    Split and lazy-load

    Distinguish first-screen size from total size: the first screen only needs the main-thread output and first-screen assets, so splitting out the rest noticeably improves startup.

    Compile-layer options

    • extractStr: merges duplicate string literals to reduce size, especially effective for strings that are duplicated across the main-thread and background-thread layers, such as i18n text and class names.

    Next Steps

    For a more systematic checklist and lower-level control, continue from Rsbuild's Bundle size optimization and Rspack's optimization guide.

    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.