Repository navigation
Replies: 1 comment 5 replies
Can you explain your setup a bit more so we can figure out what was causing this? Links rendered together should batch into the same manifest fetch. We have looked into and POC'd shrinking/compressing/deduping the full manifest before but those gains will always be a fraction of just not shipping the whole manifest. |
5 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Problem
When using
routeDiscovery: { mode: "initial" }, the browser manifest (manifest-*.js) grows dramatically with route count because each route'simportsarray contains the full asset path strings. With ~1,000 routes sharing ~500unique import paths, the same strings are repeated hundreds of times.
Our app (1,002 routes) produces a 5.9 MB raw manifest. The
dedupe()ingetReactRouterManifestBuildAssetsdeduplicates within a single route, but not across routes — so/assets/chunk-ABC-xyz123.jsappears verbatim in everyroute that depends on it.
Why not Fog of War?
We switched from
mode: "lazy"tomode: "initial"because/__manifestrequests were causing severe production issues:/__manifestrequests<Link>elements triggered 40–58 concurrent manifest fetches, saturating browser connections and freezing the UImode: "initial"eliminated this entirely — one static, cacheable file instead of thousands of dynamic requests. But the file is large.Proposed Solution
Deduplicate the repeated import path strings using a lookup table at manifest generation time. Instead of:
Emit:
The IIFE expands indices back to full paths before assigning to
window.__reactRouterManifest, so the runtime contract is unchanged — React Router sees the exact same object it always would.Why
JSON.parsewrapping?V8 parses
JSON.parse('...')significantly faster than equivalent JS object literals for large data payloads (no AST construction needed). This is a well-known optimization fordata-heavy scripts.
Measured Results (1,002 routes, ~510 unique import paths)
The raw size reduction is the most impactful metric — it directly reduces JS parse time on the client, which matters for low-end devices:
Implementation Complexity
As a userland Vite plugin (post-processing the built file), this requires ~300 lines including JSON extraction, safety guards, and error handling. If done natively in
@react-router/dev, it would be ~30 lines insidegenerateReactRouterManifestsForBuild— just build the lookup table beforeJSON.stringifyand emit the IIFE format. No file parsing, no key-ordering concerns, no SRI complications (hashes would be computed after optimization).This would also benefit
/__manifestresponses inmode: "lazy"when many routes are returned in a single patch.Workaround
We're currently using a Vite
closeBundleplugin that post-processes the manifest file. Happy to share the full implementation if helpful — but a native solution would be simpler, safer, and benefit the entire ecosystem.(cc @binchik)
All reactions