What changed
Each of these modules declared one component and had one inline route with an empty path. The component now lists its own imports, and the app route points at it with loadComponent. The 15 *.module.ts files are gone.
| Page | URLs | Component imports |
|---|---|---|
about | /about | CommonModule, SharedModule, RouterLink |
pricing | /pricing | CommonModule, SharedModule, RouterLink |
privacy-policy | /privacy-policy | SharedModule, RouterLink |
terms-condition | /terms-conditions, /terms-of-service | SharedModule, RouterLink |
contact-us | /contact-us | CommonModule, SharedModule, NgSelectModule |
contact-v1 | /request-demo, /schedule-a-demo | CommonModule, SharedModule, DynamicFormBuilderModule |
help-faq | /help-faq | CommonModule, SharedModule, FaqComponent, RouterLink |
calendar-connect | /calendar-connect | CommonModule, SharedModule |
shopify-connect | /shopify-connect | CommonModule, SharedModule |
imported-opt-out | /imported/opt-out | CommonModule, SharedModule |
unsubscribe-newsletter | /unsubscribe/newsletter | CommonModule, SharedModule |
unsubscribe-instruction | /unsubscribe-instruction | CommonModule, SharedModule |
not-found | /not-found, the wildcard, /bio/** | CommonModule, SharedModule |
shortend-url | /share/:shortend-url | none |
access-user-account | /access-user | none |
The guards on each app route didn't move. /about and /pricing keep the white-label and PWA guards, and the contact, demo, and FAQ routes keep the PWA guard. link-in-bio-fan-preview.routes.ts now loads the standalone not-found component for its wildcard.
The breadcrumbs needed RouterLink
Five templates use routerLink for the "Home" breadcrumb or a "contact us" link. They worked because each module imported RouterModule.forChild(routes), and that re-exports the router directives. SharedModule doesn't export RouterModule.
So with the module gone, the component has to import RouterLink itself. Without it, the build still passes. Angular treats routerLink="/" on an <a> as a plain HTML attribute, and the link renders with no href. about, pricing, privacy-policy, terms-condition, and help-faq import it now. The server output has href="/" on the breadcrumb for all five, same as the base build.
Route titles and descriptions moved to a constants file
Six modules put a title and description in their child route's data. That data now sits on the app route. The strings live in src/app/constants/public-page-route-data.ts, so the routing file has no inline text.
{
path: ROUTE_PATHS.ABOUT,
loadComponent: () =>
import('./modules/public/about/about.component').then((m) => m.AboutComponent),
canActivate: marketingSurfaceGuards,
data: PUBLIC_PAGE_ROUTE_DATA.data.ABOUT
}
I only noticed this while moving it, but nothing reads that data. CommonService.getTitle and getDescription walk the route tree for it, but neither has a caller. Each page sets its real title and meta description through SeoService. I kept the data anyway because the plan said to. It costs 752 bytes in the initial bundle, since the strings moved out of lazy chunks into main. Dropping the data and those two methods would be a small follow-up.
terms-condition is different. It used the route's title property, and Angular's default title strategy does apply that to document.title. Both terms routes keep it.
Imports I dropped
CarouselModulefrom help-faq. The template has no carousel.NgSelectModulefrom contact-v1. The form comes from the dynamic form builder, which imports its own select.AlertModalComponentfrom imported-opt-out. The class uses it as the type of a@ViewChild, but the template never renders one.CUSTOM_ELEMENTS_SCHEMAfrom pricing, calendar-connect, and shopify-connect. Without the schema, an unknown tag fails the build, and the build passed.SharedModulefrom shortend-url and access-user-account. Both have an empty template, and every service they inject isprovidedIn: 'root'.
Two dead modules deleted
modules/public/advertise and modules/public/comparison had no route pointing at them and no importer. They're gone, 9 files and 2,564 lines. The live /advertise page is advertisement-home, and the live comparison pages are comparison-page. Neither shares code with the deleted folders.
How it was tested
I built the base commit in a separate worktree and this branch in the main checkout, both with ng build --configuration=qa --prerender=false. Both builds passed. ESLint on the 18 changed TypeScript files reports no errors. The 4 warnings it prints are on lines this change doesn't touch.
| Initial bundle | Base | This branch |
|---|---|---|
| Reported total | 12.30 MB | 12.30 MB |
| Exact raw bytes | 12,295,379 | 12,296,131, +752 |
Then I started the SSR server from each build and requested the same 20 URLs. I stripped out the hashed chunk names and Angular's per-component style IDs, then diffed the full HTML. The rendered markup was identical on every URL. The only other difference was the same style ID inside the hydration data.
| URL | Base | This branch |
|---|---|---|
/about, /pricing, /privacy-policy | 200 | same HTML |
/terms-conditions, /terms-of-service | 200 | same HTML |
/contact-us, /request-demo, /schedule-a-demo | 200 | same HTML |
/help-faq | 200 | same HTML |
/calendar-connect, /shopify-connect | 200 | same HTML |
/imported/opt-out, /unsubscribe/newsletter, /unsubscribe-instruction | 200 | same HTML |
/share/abc123, /access-user | 200 | same HTML |
/not-found, /this-page-does-not-exist-xyz/a/b | 404 | same HTML |
/bio/someuser/extra/segment | 404 | same HTML |
/about/extra-segment | 200, falls through to the sub-page view | same HTML |
Last, I opened the branch build in a browser. /pricing hydrated, and clicking its "Home" breadcrumb navigated to / without a reload. From there I navigated in the app to each converted URL, which loads the new lazy chunks in the browser. Every page rendered, and the console logged no errors.
Two things looked wrong at first and turned out to be the same on the base build:
- Going from
/calendar-connectto/shopify-connectbounces back. shopify-connect callslocation.back()when the URL has nocodeorshopparams. document.titlegoes stale after in-app navigation. The terms title "TopFan - Terms & Conditions" stays after you leave the terms page. The routetitlesets it, and the next page never replaces it on the client.
Affected areas
Every page here is public, so you can test them without logging in. The callback pages need real query params from an email, an OAuth flow, or an admin link.
| Area | Risk | Why |
|---|---|---|
| Not-found and the wildcard | Medium | Every unknown URL on the site lands here, and the SSR response has to stay a 404. |
| Contact and demo forms | Medium | They have reCAPTCHA, a phone input, and a select. contact-v1 lost an import. I didn't submit either form. |
| OAuth and email callbacks | Medium | calendar-connect, shopify-connect, imported-opt-out, unsubscribe-newsletter, shortend-url, and access-user all act on query params. I only loaded them without params. |
| About, pricing, privacy, terms, help | Low | Static content. The breadcrumb links were the one real risk, and they render with href. |
Verification checklist
Test each page two ways. Load the URL directly, which renders on the server and then hydrates. Then reach it from another page, so the browser fetches the lazy chunk. Checks are saved in this browser.
Medium Not-found
Medium Contact and demo forms
Medium Callback pages
Low Static pages
On every page
Not tested
- No form submissions and no callback with real params. Those need a QA account and real emails or OAuth flows.
- No white-label build.
angular.jsononly has theTopFansproject right now, since white-label projects are generated. - No prerender run. The pages are the same, but
npm run prerender-qawould confirm it.
What's next
PR 2 in the plan covers fulfillment-order, creators, setup-profile, and forum. Nothing imports those four, and they don't import any routed module, so no extra URLs disappear. Forum is the one to click through, because it calls NgCircleProgressModule.forRoot and provides BsModalService at the module level.