perf(metadata): cache scanMetadataFiles to skip the redundant 2nd per-build app-tree walk#2394
Merged
Merged
Conversation
This was referenced Jun 28, 2026
commit: |
Contributor
Performance benchmarksCompared 0 improved · 0 regressed · 6 within ±1.5%
View detailed results and traces 🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head |
scanMetadataFiles folds `*.alt.txt` sidecars into a static social image route's altFilePath via an existsSync probe, but isMetadataRouteFile did not treat them as route files. With the new per-appDir scan cache, adding or removing an alt sidecar in dev no longer triggered invalidation, so the cached scan kept a stale altFilePath. Match the sidecars in the dev invalidation predicate so the metadata cache clears and re-scans.
50c18c8 to
cea2b29
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Memoizes
scanMetadataFiles(appDir)perappDir(mirroring app-router'scachedGraph), and invalidates it alongside the route graph when app files change in dev.Why
scanMetadataFileswalks the entire app tree with recursivefs.readdirSyncto find file-based metadata routes (favicon,icon,opengraph-image,sitemap,robots,manifest, …). It is called fromload(RESOLVED_RSC_ENTRY)(index.ts), which fires once per build pass that roots at the RSC entry — measured at exactly 2× pervinext buildon the 5000-route fixture — and it had no cache, so the whole tree was walked twice. The sibling call right next to it,appRouter(...), is already memoized viacachedGraph; this just gives the metadata scan the same treatment.Measured
Direct timing of
scanMetadataFileson the 5000-route fixture (5,037 dirs):So it removes one full app-tree walk per build: ~100 ms at 5,000 routes, scaling ~linearly with directory count (~200 ms at 10,000). Modest, but it's provably-redundant work and the change is tiny.
Correctness
invalidateAppRoutingModules()— the same path the route-graph cache uses. Metadata-file add/remove already routes there viashouldInvalidateAppRouteFile→isMetadataRouteFile, so a newfavicon/sitemap/opengraph-imagein dev clears the cache and re-scans (no staleness).push/sort/splice), so returning the cached array is safe — same ascachedGraph.metadata-routes,file-based-metadata,app-router-metadata-routes,metadata-route-build-data,metadata-route-response,streaming-metadata: 174 tests.vp lintclean.