PR 10826 and 10827: standalone routes review

Two pull requests, opened about a minute apart from the same base commit, each convert the five single-component feature modules (comparison-page, marketing-page, sub-page-view, creators-home, link-in-bio-fan-preview) to standalone components loaded with loadComponent, delete their ten module and routing files, and teach the prefetch script to write loadComponent routes. They touch the same 20 files and are alternatives, not a stack. This review compares them line by line and builds and serves both.

Verdict

Merge PR 10826 as it stands. Close PR 10827 as superseded. Both compile, both produce the same 12.30 MB initial bundle, and both render all five surfaces over SSR. Fourteen of the twenty files are byte-identical. The six that differ all favour 10826: it removes CUSTOM_ELEMENTS_SCHEMA from every converted component, where 10827 keeps it on creators-home without needing it; and it keeps the link-in-bio component in the same chunk as its routes, where 10827 splits off a 319-byte routes chunk so every /bio/:userName visit needs two sequential loads before it can hydrate, in exchange for saving 59 KB on the not-found path. A parallel review preferred 10827 for that split; the trade is a worse common case for a better 404. 10826's description also records the behaviour change and what was tested; 10827's test plan is unchecked.

reviewed 2026-09-23 base 5345fee1fc 10826 head c320b59a9f (+142 / -290) 10827 head e5389aa596 (+147 / -288) Angular 21.2.23

What both PRs change

Each PR is a single commit. The four leaf components (ComparisonPageComponent, MarketingPageComponent, SubPageViewComponent, CreatorsHomeComponent) lose standalone: false and gain an imports array holding what their templates use. app-routing.module.ts points the /creator, /creators, white-label root sub-page matcher, and :id/:subPageUrl routes at those components with loadComponent. LinkInBioFanPreviewComponent keeps its :userName and wildcard children, now in a plain Routes array exported from a new link-in-bio-fan-preview.routes.ts and loaded with loadChildren. The ten *.module.ts and *-routing.module.ts files are deleted.

scripts/prefetch-marketing-pages.mjs now emits loadComponent for the marketing and comparison route generators, and the two generated route files (marketing-page-routes.generated.ts, 14 routes; comparison-page-routes.generated.ts, 7 routes) are regenerated to match, so the next prefetch run does not resurrect the deleted modules.

One behaviour change, shared by both: the old CreatorsHomeModule imported AuthModule and SetupProfileModule, which mounted their RouterModule.forChild routes under /creators and /creator. Both imports are gone, so /creators/login no longer renders the login page. See the finding below.

Findings that apply to both PRs

correct

Every template dependency is covered by the new imports arrays

The deleted CreatorsHomeModule imported FormsModule, NgSelectModule, AccordionModule, ModalModule, and NgMultiSelectDropDownModule.forRoot(). None of those survive in either PR, and none are needed: the creators-home template contains no ngModel, ng-select, accordion, or ng-multiselect-dropdown. Every element it does use resolves: vjs-ivs-player is the component declared and exported by NgIvsModule (inputs options and appChanEndpoint, confirmed in the @cotufa82/ng-ivs bundle); app-book-demo, app-tick-icon, and app-creator-requirements-modal are exported by SharedModule; app-faq, app-button, and app-book-demo-form are imported directly; app-marketing-page-full-preview and app-whitelabel-cover-page-preview come from MarketingPageSharedModule. The link-in-bio template's app-share-details-preview and its product and profileImageUrl pipes are in SharedModule.

BsModalService, injected by CreatorsHomeComponent, is providedIn: 'root' in ngx-bootstrap 21.2, so dropping ModalModule from the component does not break it. The AOT build of 10826, which has no CUSTOM_ELEMENTS_SCHEMA anywhere in these files, passing with no NG8001 or NG8002 is the proof.

assessed

Dropping AuthModule and SetupProfileModule is safe, and a small win

AuthModule has no providers; it only declares components and exports ResendVerification. SetupProfileModule likewise. Nothing CreatorsHomeComponent injects came from either module. No code, prerender route, or sitemap entry links to /creators/login, /creator/signup, or any other auth or setup path under those prefixes. As a side effect, the creators-home lazy chunk no longer drags the whole auth and setup-profile feature code along with it.

What happens to /creators/login now: it matches :id/:subPageUrl, so SSR returns 200 with SubPageViewComponent in its loading state, and in the browser the component asks the API for a user called creators, gets nothing, and navigates to not-found. Before, the same URL rendered the login page. If analytics or an old email campaign show traffic to it, add a redirectTo: 'auth/login' entry; otherwise leave it.

correct

Route data still reaches the components

Before, data: { marketingSlug } sat on the lazy parent route and the component sat on the child '' route; paramsInheritanceStrategy: 'always' in the router config carried the data down. With loadComponent the component's ActivatedRoute is the route that owns the data, so currentRoute.snapshot.data['marketingSlug'] and ['comparisonSlug'] read the same value with one less hop. None of the five components reads route.parent, so nothing depended on the old nesting.

note

The marketing-pro generator still writes loadChildren

prefetch-marketing-pages.mjs has three generators. The marketing and comparison ones now emit loadComponent; the marketing-pro one still emits loadChildren to MarketingProPageModule, which both PRs leave in place. That is consistent (the module still exists) and out of scope for "single-component modules", but marketing-pro-page is also a single component with its own routing module, so it is the obvious next candidate.

note

SharedModule is still imported wholesale

creators-home, sub-page-view, and link-in-bio-fan-preview each import SharedModule, which exports about 160 declarations. That is exactly what their deleted modules did, so it is not a regression and bundle size is unchanged. Standalone components make it possible to import only TickIconComponent, BookDemoComponent, CreatorRequirementsModalComponent, and the pipes each template uses. Worth a follow-up once more of the app is standalone; not a reason to hold either PR.

note

No specs, no browser session

None of the five components has a test, before or after. Verification for this review was a QA-configuration build of each head and an SSR smoke test of seven routes (below). Neither PR was clicked through in a browser, and no white-label project was built.

Where the two PRs differ

Only six files differ between the heads, for a net 24 insertions and 17 deletions. app-routing.module.ts, the prefetch script, both generated route files, and all ten deletions are byte-identical.

Aspect PR 10826 PR 10827
CUSTOM_ELEMENTS_SCHEMA on CreatorsHomeComponent Removed, along with the other four. The description says so explicitly. Kept as schemas: [CUSTOM_ELEMENTS_SCHEMA]. With it, the compiler stops reporting unknown elements and unknown property bindings in that template. 10826's clean build shows nothing in the template needs it, so it only hides future mistakes.
How link-in-bio-fan-preview.routes.ts loads the component component: LinkInBioFanPreviewComponent with a static import. Routes and component land in one 59.6 KB chunk. loadComponent: () => import('./link-in-bio-fan-preview.component'). The build emits a 319-byte routes chunk plus a 59.5 KB component chunk. The split only pays off when the wildcard matches (/bio, /bio/a/b), which is the not-found page nobody links to. Every real /bio/:userName visit makes one more sequential request before it can hydrate, and NoPreloading means nothing warms it up.
Import paths in the routes file @modules/... alias for both the component and not-found.module. Keeps the relative '../../modules/public/not-found/not-found.module' from the deleted routing module, next to an @constants alias import.
Import placement in the components New imports sit with the Angular imports at the top of each file. New imports are appended at the end of the import block; comparison-page and marketing-page gain a stray blank line after CommonModule. Cosmetic.
Pull request description States the behaviour change, the build result (12.30 MB initial, same as base), which SSR routes were checked, and what was not done. Summary plus an unchecked test plan. No evidence anything was run.
Commit message 67-character subject; body explains the auth-module removal. 134-character subject, which GitHub cuts with an ellipsis; the body then begins mid-sentence with "…they match".
Net size +142 / -290 +147 / -288

Requested changes before merge

None on PR 10826. Two optional follow-ups, neither blocking: convert marketing-pro-page the same way and update the third generator; and, if traffic to /creators/login turns up, add a redirect.

If the team prefers to land PR 10827 instead, it needs two edits to reach parity:

  1. Drop the schema. In src/app/modules/creators-home/creators-home.component.ts:

    -import { CUSTOM_ELEMENTS_SCHEMA, Component, ElementRef, ... } from '@angular/core';
    +import { Component, ElementRef, ... } from '@angular/core';
           NgIvsModule
    -    ],
    -    schemas: [CUSTOM_ELEMENTS_SCHEMA]
    +    ]
  2. Load the component eagerly inside the routes chunk. In src/app/modules/link-in-bio-fan-preview/link-in-bio-fan-preview.routes.ts:

    +import { LinkInBioFanPreviewComponent } from '@modules/link-in-bio-fan-preview/link-in-bio-fan-preview.component';
       {
         path: ':userName',
    -    loadComponent: () =>
    -      import('./link-in-bio-fan-preview.component').then(
    -        (m) => m.LinkInBioFanPreviewComponent
    -      )
    +    component: LinkInBioFanPreviewComponent
       },

What was verified

  • Both heads were checked out into temporary worktrees sharing the repository's node_modules and built with ng build --configuration=qa --prerender=false. Both pass with no errors. Initial bundle 12.30 MB raw, 2.00 MB estimated transfer, in both. 10826 emits 109 browser chunks, 10827 emits 111.
  • Each dist/TopFans/server/server.mjs was started and seven routes fetched. Results were identical for the two heads:
  • /creators200app-creators-home
  • /creator200app-creators-home
  • /athletes200app-marketing-page
  • /topfan-vs-patreon200app-comparison-page
  • /bio/testuser200app-link-in-bio-fan-preview
  • /bio404app-not-found, as before
  • /creators/login200app-sub-page-view, was the login page
  • The five templates were scanned for every dashed element, structural directive, and pipe, and each was traced to NgIvsModule, SharedModule, MarketingPageSharedModule, or a directly imported standalone component.
  • AuthModule and SetupProfileModule were read for providers; there are none. BsModalService was confirmed providedIn: 'root' in the installed ngx-bootstrap.
  • The whole tree at each head was searched for references to the five deleted module classes and file paths; none remain.
  • Chunk ownership of LINK_IN_BIO_FAN_PREVIEW_ROUTES and app-link-in-bio-fan-preview was checked in each browser build to confirm the one-chunk versus two-chunk difference.

Not run here: npm run build-dev (fails on the base branch too, because environment.dev.ts does not exist), a browser session, prerendering, and white-label project builds.

QA after merge

  1. Open /creators and /creator. Confirm the hero video plays (vjs-ivs-player), the FAQ accordion opens, and the book-a-demo modal opens and closes.
  2. Open a marketing slug such as /athletes and a comparison slug such as /topfan-vs-patreon. Confirm the page renders and the console shows no NG0500 to NG0507 hydration errors.
  3. Open /bio/<enabled-user> and confirm the carousel, welcome video, feed preview, and portfolio preview render. Open /bio and confirm not-found.
  4. On a white-label site, open the root sub-page route and a /:id/:subPageUrl sub-page. Confirm both render and a missing sub-page lands on not-found.
  5. Open /creators/login and /creator/setup-profile. Confirm they end on not-found rather than an error, and that /auth/login still works.
  6. Run npm run prefetch-pages-qa and confirm the two generated route files come back unchanged.
  7. Prerender one white-label project and confirm the cover page and sub-page HTML are written.