perf: improve lingo performance — personalization, geo, lingo, XLG#6210
Open
mokimo wants to merge 13 commits into
Open
perf: improve lingo performance — personalization, geo, lingo, XLG#6210mokimo wants to merge 13 commits into
mokimo wants to merge 13 commits into
Conversation
personalization.js, sanitizeHtml.js (static import), and promo-utils.js were loaded serially — each triggered only after the previous finished. On adobe.com/fr/creativecloud this created a ~130ms serial chain starting at t=499ms (personalization → +41ms sanitizeHtml → +44ms promo-utils). Adding modulepreload hints for all three in checkForPageMods(), before the getCountry() await, overlaps their fetches with country detection so the browser has them in the module cache by the time await import() fires. Verified locally: all three now start in parallel at the same timestamp. Also deduplicates the getConfig().base call in the same block. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…request sanitizeHtml.js had a single consumer (personalization.js) and was always loaded as a static import — meaning every page with personalization paid for an extra serial module fetch. Moving the logic directly into personalization.js eliminates that request entirely. - Inline stringToHTML, isPossiblyDangerous, sanitizeHtmlNode, and sanitizeHtmlBody into personalization.js as private functions (sanitizeHtmlBody exported for testability) - Delete libs/utils/sanitizeHtml.js - Remove the now-unnecessary modulepreload hint for sanitizeHtml.js - Update sanitizeHtml.test.js to import from personalization.js and drop the sanitizeHtml (firstChild) describe block — that export is gone Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… call geo2.adobe.com/json/ was fetched with no timeout (browser TCP timeout ~30s) and no deduplication — concurrent callers each started an independent fetch. resolveDetectedMarketCountry() compounded this by making a second serial geo2 attempt on failure, meaning a blocked geo2 could cost 60s total. Changes: - geo.js: wrap fetch in AbortController with 5s timeout; cache the in-flight promise so concurrent calls share one fetch instead of each issuing a duplicate request - utils.js: remove the redundant getAkamaiCode() fallback block from resolveDetectedMarketCountry() — getCountry() already tried geo2 and returned null, an immediate retry will have the same result lana.js loading late is a symptom, not a cause: it lazy-loads on the first window.lana.log() call, which fires when geo2 times out. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…dexes earlier - Parallelize getManifestConfig() calls with Promise.all (was serial for-loop) — 300-800ms gain on pages with multiple manifests - Parallelize categorizeActions() calls with Promise.all (synchronous but eliminates microtask overhead) - Add in-flight promise dedup to getEntitlementMap() so concurrent callers share one XLG tags fetch instead of each issuing their own - Move section setup and loadLingoIndexes() before decorateDocumentExtras() so query-index fetches start while header/meta decoration runs (parallel instead of serial) - Deduplicate processQueryIndexMap calls when primary and base locale URLs resolve to the same path (avoids double-fetch on prod with root locale) - Add market.js modulepreload hint before the preview.js dynamic import so both modules load in parallel Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On Lingo-active pages (e.g. /fr/creativecloud), lingo-site-mapping.json
resolves quickly (~70ms) and immediately triggers 6 cross-site query index
fetches (Wave 2) while Wave 1's 45KB + 26KB files are still downloading.
On 4G this bandwidth race delays Wave 1 completion by ~220ms, pushing back
the LCP image fetch start.
Three changes:
- Add fetchOptions param to processQueryIndexMap so callers can control
fetch priority independently.
- Fetch lingo-site-mapping.json with { priority: 'low' } — it has no LCP
dependency and should not compete with hero CSS/JS.
- Await Wave 1 (primary + base pathsRequests) inside the lingoSiteMapping
IIFE before firing the Wave 2 forEach, so cross-site indexes only start
after the bandwidth-heavy Wave 1 files have landed. Wave 2 calls also
pass { priority: 'low' }.
Wave 1 is unchanged — fires immediately at default priority for LCP section
link localization.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…request getXLGListURL() returns https://www.adobe.com/federal/assets/data/mep-xlg-tags.json. fetchData() passes it through normalizePath() which resolves it via getFederatedUrl() to the environment-specific federated root (e.g. main--federal--adobecom.aem.live on preview). The rel=preload loadLink call was using the raw www.adobe.com URL, so the browser preloaded one URL and fetched a different resolved URL — cache miss, two round trips. Wrapping the preload argument in normalizePath() makes both calls use the same resolved URL in every environment, letting the browser reuse the preloaded response. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Contributor
|
Hello, I'm the AEM Code Sync Bot and I will run some actions to deploy your branch.
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…arlier saveToMmm() posts analytics to a Lambda; its result is unused, but the await was blocking checkForPageMods() for the full Lambda round-trip (~500ms visible in waterfall). Dropping the await unblocks section processing and shifts fragment.js loading ~500ms earlier. Add a modulepreload for fragment.js right after sections are built (post-personalization DOM is final at that point), so the browser fetches it in parallel with section decoration instead of cold-loading it when the first fragment selector match triggers a dynamic import. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This was referenced Jun 22, 2026
preview.js has heavy static deps (merch.js, caas/utils.js) that were loading right after personalization completes — still inside the LCP window. By deferring the import to the milo:deferred event (fired after all sections are processed), the entire module graph stays out of the LCP window with no user-visible impact. Also removes the market.js modulepreload from the LCP window since market.js is only needed as a static dep of preview.js. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The milo:deferred approach caused a performance regression: preview.js has market.js, mas-mep-utils.js, merch.js, and caas/utils.js as static imports. Those modules were previously loading right after personalization (in parallel with section processing), warming the module cache for the merch/MAS blocks. Deferring to milo:deferred pushed the whole graph out past all sections, making those modules cold and sequential when the merch block actually needs them. Fire-and-forget is the right approach: removecing the await unblocks checkForPageMods() from the Lambda round-trip, while still starting preview.js + its static deps early enough to benefit the merch blocks. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… dep chain
hero-marquee.js awaits iconography.css on its critical path, blocking section 1
completion and delaying everything after it (including section 2 blocks like
commerce). Adding rel=preload for iconography.css and breakpoint-theme.css in
preloadBlockResources when hero-marquee is detected lets the browser fetch them
in parallel with the block JS, so the await resolves from cache.
preview.js -> caas/utils.js -> {lingo-active.js, getUuid.js} is a 2-level-deep
sequential discovery chain. Adding modulepreload hints for both alongside the
existing market.js hint lets the browser fetch them in parallel with the rest of
the preview.js module graph rather than waiting for caas/utils.js to parse first.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
markpadbe
requested changes
Jun 22, 2026
markpadbe
reviewed
Jun 22, 2026
…ana logging - Restore libs/utils/sanitizeHtml.js (both sanitizeHtml and sanitizeHtmlBody exports) so cc dynamic imports don't 404 - Revert inline copy in personalization.js; use static import instead - Add modulepreload for sanitizeHtml.js in utils.js alongside personalization.js preload so both load in parallel before getCountry() await - Add window.lana?.log to preview.js .catch so MEP save errors surface in monitoring - Add comment on Promise.all/categorizeActions ordering invariant Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eHtml tests back Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Contributor
|
This PR has not been updated recently and will be closed in 7 days if no action is taken. Please ensure all checks are passing, https://github.com/orgs/adobecom/discussions/997 provides instructions. If the PR is ready to be merged, please mark it with the "Ready for Stage" label. |
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.
Before
Multiple sequential render blocking calls

After
Parallelize and prefetch needed code

Description
Combined quick-win performance improvements targeting LCP on Lingo-active pages (e.g.
/fr/creativecloud). Rolls up and supersedes the four individual PRs listed below — they can be closed once this merges.Supersedes:
1. Personalization module
modulepreload+ inlinesanitizeHtml(#6206)personalization.js → sanitizeHtml.js → promo-utils.jsloaded serially (~130ms). Addedmodulepreloadhints before thegetCountry()await so all three start in parallel. InlinedsanitizeHtml.js(single-consumer static import) to eliminate that extra request entirely.Gain: ~80ms off personalization apply time.
2.
geo25s timeout + in-flight dedup (#6207)geo2.adobe.comhad no timeout (browser TCP timeout ~30s) and no dedup — concurrent callers each issued a separate fetch.resolveDetectedMarketCountry()compounded this with a serial retry on failure. AddedAbortControllerwith 5s timeout, cached the in-flight promise, and removed the redundant fallback call.Gain: eliminates pathological 30–60s stall; no impact on typical fast-path.
3. Parallel personalization manifest fetches +
getEntitlementMapdedup (#6208)getManifestConfigiterated manifests serially;categorizeActionsused a serialfor-awaitloop. Both converted toPromise.all. When manifests run concurrently, each was callinggetEntitlementMapindependently — fixed with an in-flight promise cache onconfig.mep.entitlementMapFetch. Also movedloadLingoIndexesbeforedecorateDocumentExtrasto overlap lingo index fetches with header decoration, and deduped identical primary/base query-index URLs.Gain: 300–800ms on pages with 3+ manifests.
4. Lingo Wave 2 defer +
priority: 'low'(#6209)lingo-site-mapping.json(0.9 KB) resolves in ~70ms and immediately fired 6 cross-site query-index fetches (Wave 2) while Wave 1's 45 KB + 26 KB primary/base files were still downloading. On 4G this bandwidth race delayed Wave 1 completion by ~220ms.Three changes:
fetchOptionsparam onprocessQueryIndexMap;priority: 'low'on the site-mapping fetch; await Wave 1 (Promise.allof primary + base) before firing Wave 2'sforEach. Wave 2 calls also passpriority: 'low'.Wave 1 unchanged — fires immediately at default priority for LCP-section link localization.
Gain: ~220ms bandwidth relief on 4G; Wave 2 no longer races Wave 1.
5. XLG preload URL alignment (new)
getXLGListURLreturnshttps://www.adobe.com/federal/assets/data/mep-xlg-tags.json. Therel=preloadlink used that raw URL;fetchDatapasses it throughnormalizePathwhich resolves it viagetFederatedUrlto the environment-specific federated root (e.g.main--federal--adobecom.aem.liveon preview). Two different resolved URLs → browser cache miss → two round trips. Wrapping the preload argument innormalizePath()aligns both calls to the same URL in every environment.Gain: eliminates one redundant XLG JSON fetch on non-prod environments.
Resolves: MWPW-TBD
Test URLs (da-cc fr/creativecloud):
🤖 Generated with Claude Code