Public marketing pages load only the layouts they use

The public preview used to import 54 layout classes and ship every one on home, marketing, comparison, pro, creators-home, and a white-label cover. It now loads only the layout ids the page actually shows. The server waits for those imports before it writes HTML, so title, description, and the app-*-layout tags stay the same. The editor still imports the full catalog. That is the right call. The three files are walked in how the marketing preview loads a layout. A follow-up stops the appearance-tab leak on /edit-preference/whitelabel. Notes are in stop the cover preview from reloading layouts every cycle.

branch t3code/lazy-marketing-layouts on top of 33fcd8d508, PR 10847 merged the leftover from after the SharedModule work 1 commit, PR 10849

First-visit JavaScript

What a first visit fetches on top of the initial bundle, from the QA build. Sizes are minified bytes before compression. Before means after PR 10847. MB means a million bytes. The walk is the static import graph of each entry. It does not count the layout chunks a page then fetches with import().

EntryBeforeAfter
The promotional routes file, which is main /2.79 MB1.76 MB
The marketing page component1.23 MB0.03 MB
The comparison page component1.23 MB0.03 MB
The pro page component2.05 MB0.86 MB
Creators-home1.30 MB0.11 MB
Sub-page view1.36 MB0.21 MB

Marketing and comparison are the clean case. Those pages were almost all the 54-layout preview. Pro stays heavier because the pro page still statically owns pro-contact-form-layout. That id never had a preview class in the 54.

Home at 1.76 MB is not a failed split. promotional.routes.ts still statically imports landing, promotional, promotional-detail, and search-result. Those four sit in one graph. Some unused layout source files still appear in that graph because they share a chunk with header or chrome. The browser on / still did not fetch newsletter, most portfolio details, pro-hero, read-more, or feature-creator. Those staying off the wire is the proof the split works.

Commit 3645b6d5ee: load only the layouts a public page uses

3 files, +385 −345

What changed

  • marketing-page-layout-loaders.ts maps 53 layout ids to literal import() calls. A variable path would not split. Header is not in the map. pro-contact-form-layout is not either.
  • MarketingPageFullPreviewComponent no longer lists 54 layout classes in imports. After it resolves sections it loads the unique visible ids in parallel, caches the classes, and renders them with ngComponentOutlet.
  • A PendingTasks handle stays open until those imports finish, then done() runs in finally. A generation token drops a stale load when sections change or the component is destroyed.
  • Header stays a static template. It projects the welcome-video modal and binds [post]="headerLayout.welcomeVideoPost". ngComponentOutlet cannot do that without moving the modal into HeaderLayout.
  • The extra inputs each layout already had, like disablePinnedScroll, isEditingMode, footer branding, and forceMobileView, move into getMarketingPageLayoutInputs.
  • add-marketing-page-section still statically imports every layout. The picker needs the full catalog.

SEO tags come from setMarketingPageSeo on the page, from the published payload. They never depended on the layout imports. Title and description on /, /athletes, /music-artists, /topfan-vs-patreon, and /creators-home matched the 10847 SSR. So did every app-*-layout count. Skip PendingTasks and the first HTML loses those tags, the same way white-label / lost <app-profile> before 10847. @defer would do the same. It is client-only.

How I checked it

CheckResult
QA buildPasses. Output in /tmp/tf-sm/layouts.
Route-weight against the 10847 QA buildThe table above. Same script as the leftovers numbers, walking stats.json.
SSR HTML against the 10847 serverTitle match, description match, layout tag counts match on home, athletes, music-artists, comparison, and creators-home. /apps/music-artists-fan-club 404s on both. That is old.
Browser on the new serverHome, athletes, and comparison render. Athletes "See Example" still goes to /. No hydration errors. CDN 404s are old image URLs.
Network on /Newsletter, most portfolio details, pro-hero, read-more, and feature-creator stay off the wire.

What this does not do

  • It does not lazy-load the four promotional route components. That leftover 1.76 MB on home is a different split.
  • It does not skip ProfileComponent on a published white-label cover. That import still starts in the landing constructor.
  • It does not split comparison-layout.utils off the editor field constants. That 29 KB leak is still there.
  • It does not touch bootstrapApplication.

Verification checklist

Keep the console and the network panel open. Do not buy, subscribe, or publish. Checks are saved in this browser.

0 of 0 checked

High The page still looks like itself

High The unused layouts stay off the wire

Medium Clicks still work

Low The editor is unchanged