Marketing Site
Architecture deep dive for the `apps/www` Next.js marketing site and its root-site deployment model.
apps/www is the standalone marketing application for SterlingIndex. It is a Next.js App Router app deployed at the root www origin and authored as a normal route tree with internal links like /product, /pricing, and /solutions/legal-recruiting.
The app is intentionally content-heavy and data-light. Most routes render static JSX or import structured page content from local modules instead of fetching CMS content or calling backend APIs at request time.
System Role
- Primary purpose: explain SterlingIndex to new buyers, route evaluation intent toward demos, and keep existing-customer login separate from new-buyer conversion.
- Runtime boundary: the app owns the marketing shell and public evaluation journey, but defers authenticated product access to the hosted sign-in contract exported by
@sterling/auth. - Deployment assumption: the app owns the root path on
https://www.<domain>/and links authenticated users over to the dashboard host.
www.<domain>/
-> src/app/layout.tsx shell
-> NavbarRefined
-> route page content
-> footer + instrumentationRoute Structure
Core shell and entry points
| Route | Purpose | Primary file |
|---|---|---|
/ | Homepage / category overview | apps/www/src/app/page.tsx |
/product | Product mechanism and workflow story | apps/www/src/app/product/page.tsx |
/pricing | Sales-assisted pricing router, not self-serve checkout | apps/www/src/app/pricing/page.tsx |
/request-demo | Demo-intent landing page and evaluation checklist | apps/www/src/app/request-demo/page.tsx |
/company | Company and trust narrative | apps/www/src/app/company/page.tsx |
/data-coverage | Methodology and trust surface | apps/www/src/app/data-coverage/page.tsx |
/compare/leopard-solutions | Competitor comparison page | apps/www/src/app/compare/leopard-solutions/page.tsx |
Solution family
| Route | Purpose | Primary file |
|---|---|---|
/solutions | Hub page for audience selection | apps/www/src/app/solutions/page.tsx |
/solutions/legal-recruiting | Recruiting workflow variant | apps/www/src/app/solutions/legal-recruiting/page.tsx |
/solutions/law-firms | Law-firm strategy variant | apps/www/src/app/solutions/law-firms/page.tsx |
/solutions/in-house | Legal ops / outside-counsel variant | apps/www/src/app/solutions/in-house/page.tsx |
Resource and editorial family
| Route | Purpose | Primary file |
|---|---|---|
/resources | Resource hub that routes into evaluation content | apps/www/src/app/resources/page.tsx |
/blog | Blog index sourced from local post metadata | apps/www/src/app/blog/page.tsx |
/blog/[slug] | Static article pages from local content arrays | apps/www/src/app/blog/[slug]/page.tsx |
Guardrails and error routes
| Route | Purpose | Primary file |
|---|---|---|
/privacy-policy | Intentionally unpublished placeholder that returns notFound() and noIndex metadata | apps/www/src/app/privacy-policy/page.tsx |
/404 | Explicit public error page that renders a standalone "Page not found" hero when the route is visited directly | apps/www/src/app/404/page.tsx |
Application Shell
The global shell lives in apps/www/src/app/layout.tsx.
- It publishes shared
SITE_METADATAfromapps/www/src/lib/marketing.ts. - It injects Google-hosted
Mona SansandInter. - It applies a skip link,
NavbarRefined, the sharedMainwrapper, a shared footer, and aSuspense-wrapped observability client island. - It keeps the footer login path as a plain anchor handoff to the dashboard host by rendering
FooterUtilityLinkwithrootBoundary.
The active navigation implementation is apps/www/src/components/sections/navbar-refined.tsx.
- Desktop navigation is a small route map plus hover/focus dropdowns.
- Mobile navigation is a Radix
Sheetdriven menu with staggered entrance animation. - Both desktop and mobile paths attach analytics attributes through
getMarketingLinkAnalyticsAttributes. - Existing-customer login and demo CTA are always exposed as separate actions.
The footer implementation is apps/www/src/components/sections/footer-with-newsletter-form-categories-and-social-icons.tsx.
- Footer categories are configured centrally in
FOOTER_CATEGORIES. - The newsletter UI is present as a preview component, but the footer copy explicitly says signup is inactive in this build.
- The only utility link wired in the layout is the root-boundary Sterling login link.
Content Composition Model
The marketing app uses three composition styles.
1. Direct JSX pages for flagship routes
The homepage, product, pricing, and request-demo pages are authored directly in route files:
apps/www/src/app/page.tsxapps/www/src/app/product/page.tsxapps/www/src/app/pricing/page.tsxapps/www/src/app/request-demo/page.tsx
These routes compose hero, FAQ, stats, card-grid, and CTA sections directly in JSX because they carry the highest-fidelity conversion story and bespoke sequencing.
2. Data-backed page families
Two library modules hold structured content that multiple pages reuse:
apps/www/src/lib/solutions-trust-page-content.tsapps/www/src/lib/evaluation-demand-page-content.ts
Those modules provide:
- metadata payloads for route-level SEO,
- section arrays for card-grid pages,
- solution-specific content for the three audience pages,
- blog-post metadata and article HTML,
- compare-page proof points and pricing-table content.
This keeps many routes thin. For example:
apps/www/src/app/solutions/page.tsxmostly reads fromsolutionsHubPage,- the three solution detail routes all render the shared
SolutionDetailPagepage family, apps/www/src/app/resources/page.tsxandapps/www/src/app/blog/page.tsxproject arrays fromevaluation-demand-page-content.ts,apps/www/src/app/blog/[slug]/page.tsxstatically enumeratesgenerateStaticParams()from the localblogPostsarray.
3. Shared section primitives
The section library under apps/www/src/components/sections gives the app a constrained set of reusable storytelling blocks:
- heroes such as
HeroSimpleCenteredandHeroLeftAlignedWithDemo, - structural sections such as
CardGridSection,FeaturesThreeColumn,PricingHeroMultiTier, andFAQsAccordion, - shell components such as
NavbarRefinedand the shared footer.
Low-level layout primitives under apps/www/src/components/elements keep these sections visually consistent:
Containersets page width,SectionIntrostandardizes heading blocks,SurfaceCardprovides the default card language,Link,ButtonLink, andPlainButtonLinkcentralize CTA behavior and analytics,RevealanduseRevealprovide intersection-triggered motion.
Styling and Design System
The site styling is defined in apps/www/src/app/globals.css.
- Tailwind CSS v4 theme tokens define the
mistcolor ramp, background/foreground tokens, and font variables. - The app uses a light radial-gradient background with dark-mode counterparts instead of flat fills.
- Global accessibility rules cover focus rings, skip-link behavior, and
scroll-margin-topfor section anchors. - Animation tokens and keyframes support reveal motion, mobile menu transitions, and Radix collapsible panels.
The app also carries apps/www/components.json, which points to a shadcn-style component setup. In practice, the active shell currently uses a narrow slice of local UI wrappers:
apps/www/src/components/ui/sheet.tsxapps/www/src/components/ui/collapsible.tsx
Those wrappers sit on top of Radix primitives and mainly support the mobile navigation experience.
Metadata, Canonicals, and Deployment Behavior
Shared metadata behavior is centralized in apps/www/src/lib/marketing.ts.
buildPageMetadata()normalizes canonical paths with trailing slashes.- Every major public route exports metadata through that helper.
- The privacy-policy route opts into
noIndex. - The root layout publishes
SITE_METADATAfor the base shell.
Delivery behavior is configured in apps/www/next.config.ts.
- The app deploys at the root
wwworigin with no gateway base path. trailingSlash: truekeeps canonical marketing URLs normalized with a trailing slash.- When
SENTRY_AUTH_TOKEN,SENTRY_ORG, and the project name env are present,withSentryConfig(...)uploads client source maps keyed toNEXT_PUBLIC_APP_RELEASE. - The Next config also publishes origin-side security headers such as HSTS,
X-Frame-Options, andX-Content-Type-Optionsso the marketing host is ready to sit behind a Cloudflare proxy without pushing all hardening into edge-only config. - There is no active middleware layer in
apps/www; routing is handled by App Router plus the Next.js route tree. - The repo includes
apps/www/src/app/404/page.tsxas a normal route file; this documentation does not assume a separatenot-found.tsxApp Router handler exists unless one is added later.
Integrations and Cross-App Boundaries
Dashboard login contract
The most important shared-package dependency that shapes the marketing experience is @sterling/auth.
apps/www/src/lib/marketing.tsimportsnormalizeBaseUrl()andbuildPlatformLoginHref().packages/auth/src/index.tsstill exposes dashboard handoff helpers for cross-app redirects, while marketing links directly to the platform auth entrypoint.NEXT_PUBLIC_DASHBOARD_URLis the environment input; local fallback ishttp://localhost:8080.
That contract is why login links on the marketing site sometimes render as plain <a> tags instead of next/link: they need to leave the marketing app cleanly when sending a user into the dashboard host. The docs app now uses the same contract from its Fumadocs layout, so the public-site auth story is consistent across www and docs.
Analytics instrumentation
The app keeps analytics out of page-level JSX, but it now does initialize shared observability SDKs at the shell edge.
Observed files:
apps/www/src/components/analytics/marketing-instrumentation.tsxapps/www/src/lib/observability-config.tsapps/www/instrumentation-client.tsapps/www/sentry.server.config.tsapps/www/sentry.edge.config.tsapps/www/src/app/global-error.tsx
Behavior:
Link,ButtonLink, footer links, and navbar items still attachdata-analytics-*attributes viagetMarketingLinkAnalyticsAttributes().- Forms still attach submit metadata via
getMarketingFormAnalyticsAttributes(). MarketingInstrumentationstill listens for document-level click and submit events, preserving the existingwindow.dataLayerpush andmarketing:interactioncustom-event bridge.- The same client island now initializes PostHog for anonymous marketing analytics and emits automatic pageviews plus CTA/form events with attribution metadata.
- Attribution is standardized in
src/lib/observability-config.tsaslanding_path,referrer, and both first-touch and latest-touch UTM properties. - Sentry is initialized separately for browser, server, and edge contexts so route transitions, server exceptions, and App Router global errors all report against the same
wwwproject.
Two boundary rules are intentional:
wwwstays anonymous from an observability perspective. It does not attach an authenticated user identity to Sentry or PostHog.- PostHog is the marketing analytics system of record, while Sentry is reserved for error and trace triage.
This pattern keeps CTA instrumentation uniform without coupling every section component to analytics code.
No live backend reads on public routes
The public marketing routes are currently static from the app's point of view.
- No route in
src/appfetches backend data. - Blog posts are stored in local arrays, not MDX or CMS records.
- The request-demo route is an evaluation checklist, not a live submission flow.
- Newsletter inputs are preview-only markers and intentionally not wired into production intake.
Runtime Environment Inputs
The checked-in public runtime contract for apps/www now includes:
NEXT_PUBLIC_DASHBOARD_URLfor dashboard destination linksNEXT_PUBLIC_APP_ENVandNEXT_PUBLIC_APP_RELEASEfor release correlation across Sentry and PostHogNEXT_PUBLIC_SENTRY_DSNandNEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATEfor lean frontend monitoringNEXT_PUBLIC_POSTHOG_KEYandNEXT_PUBLIC_POSTHOG_HOSTfor anonymous marketing analytics
These values are documented in apps/www/.env.example and mirrored into the shared local/prod examples under infra/.
The repo now also documents Infisical as the source of truth for hosted and local secrets. For the marketing app, that means NEXT_PUBLIC_* values are expected to originate from the Infisical /apps/www path and sync into the Vercel project for apps/www.
Notable Flows
New-buyer evaluation path
Homepage / Product / Resources
-> Request a Demo CTA
-> /request-demo
-> guided evaluation framing
-> sales-assisted follow-up outside the marketing appExisting-customer login path
Header or footer login CTA
-> STERLING_LOGIN_HREF
-> @sterling/auth preserves cross-app return-to handoffs
-> browser leaves marketing originBlog publishing flow
blogPosts[] in evaluation-demand-page-content.ts
-> /blog index card grid
-> generateStaticParams()
-> /blog/[slug]
-> dangerouslySetInnerHTML article renderVerification Surfaces
The marketing app includes lightweight guardrail tests in apps/www/tests.
auth-intent-links.test.mjsverifies cross-origin login handoff behavior and login CTA wiring.marketing-guardrails.test.mjsverifies metadata helper usage, accessibility shell hooks, analytics instrumentation, and the unpublished privacy-policy route.
Build and local scripts are declared in apps/www/package.json.
pnpm --dir apps/www devpnpm --dir apps/www buildpnpm --dir apps/www testpnpm --dir apps/www lint
Because most content is static and local, the app's build behavior is predictable: route generation is mostly a function of source files and local content modules rather than remote dependencies.
Key Files
apps/www/next.config.tsapps/www/src/app/layout.tsxapps/www/src/app/page.tsxapps/www/src/app/pricing/page.tsxapps/www/src/app/request-demo/page.tsxapps/www/src/app/blog/[slug]/page.tsxapps/www/src/components/sections/navbar-refined.tsxapps/www/src/components/sections/footer-with-newsletter-form-categories-and-social-icons.tsxapps/www/src/components/page-family/solution-detail-page.tsxapps/www/src/components/analytics/marketing-instrumentation.tsxapps/www/src/lib/observability-config.tsapps/www/instrumentation-client.tsapps/www/sentry.server.config.tsapps/www/sentry.edge.config.tsapps/www/src/app/global-error.tsxapps/www/src/lib/marketing.tsapps/www/src/lib/evaluation-demand-page-content.tsapps/www/src/lib/solutions-trust-page-content.tspackages/auth/src/index.ts