Turn /partners into the partner lead page (#24702)
## What
`/partners` becomes a buyer lead page; the partner-recruitment content
moves to `/partners/become`; `/partners/list` is retired behind a
permanent redirect.
The menu entry has always promised "Find a Twenty partner" but landed on
a become-a-partner pitch. This PR makes the root URL serve the audience
the menu promises (buyers looking to hire a partner) and gives the
recruitment pitch its own page.
### `/partners` (new lead page)
- **Concierge hero**: "Tell us what you need, we match you in *48
hours*", filled "Get matched" CTA to `/partners/brief`, outlined "Browse
partners" smooth-scrolling to the directory, a one-line 3-step rail
(Tell us what you need / Get matched / Book an intro call), and the
existing `PartnerVisual` halftone stage — the production hero
composition with lead copy.
- **Directory zone**: the stock marketplace grid and filters, recomposed
with a section header and anchor (`#directory`). The unfiltered
filter-bar line now reads "N certified partners ready to take on your
project" (neutral wording at zero; filtered state unchanged).
- **Services explainer**: what a partner does — migration, customization
& apps, self-hosted operations, training & RevOps.
- Stock brief band, a lead-facing partner FAQ (dark closer, `FAQPage`
JSON-LD), and a demoted become-a-partner strip (modal + link to
`/partners/become`).
### `/partners/become`
The previous `/partners` composition verbatim (hero, TrustedBy +
CaseStudyPromo, testimonials, signoff, product FAQ), with its own
metadata, breadcrumbs, and CTAs retargeted to `/partners`.
### `/partners/list`
308 redirect to `/partners` (locale-prefixed URLs included). The route,
`MarketplaceClient`, `MarketplaceHeader`, and `MarketplaceBriefLink` are
deleted; every internal link, breadcrumb, sitemap entry, and script
target is updated. `/partners/profile/[slug]` stays.
## Design validation
The page was prototyped behind a `?variant=` switch and iterated against
live partner data before implementation (variants: marketplace-first,
split-path, concierge-first, and composites). The winning composition is
archived on the branch `prototype/partners-landing`.
## Verification
- `jest`: 478/478 passing
- `nx lint twenty-website` (conventions gate + oxlint + oxfmt): clean
- `tsgo --noEmit`: clean
- `nx build twenty-website`: passes — `/[locale]/partners` dynamic,
`/[locale]/partners/become` static, no `/partners/list` entry
- End-to-end marker check against live partner data (23 partners): all
zones render, count matches grid, redirects return 308
- No locale catalogs committed (per repo rule); the i18n pipeline owns
them
## Follow-ups (out of scope)
- The new strings render in English on non-English locales until the
i18n pipeline runs — worth confirming before release.
- `export const dynamic = 'force-dynamic'` on `/partners` is inherited
from the old list route; the fetch already revalidates at 300s, so edge
caching could likely be restored.
- Two pre-existing `partners-parity.mjs` failures (testimonials quote
font, application-modal transition) predate this branch.
- The concierge action carries two labels on one page ("Get matched" in
the hero and match card, "Submit a brief" in the stock band).
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/24702?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. --> R
Rashad Karanouh committed
aa5c45f588e5cad9d5b6d43dfa47dee975e518fd
Parent: e5178f1
Committed by GitHub <noreply@github.com>
on 8/25/2026, 10:02:43 AM