Organization slugs, brands, synchronous page shells and Suspense islands.
Tenant routes live under app/[locale]/[brand]/(app)/[orgSlug]/. There is no current-organization cookie and no organization header. Pass orgSlug from route params into Suspense islands with params.then(...), and resolve access with requireAccess({ tenant: orgSlug, permission }) from apps/saas/lib/organizations/require-access.ts.
The proxy maps the request host to a brand and inserts it after the locale in the next-intl rewrite, so [brand] never appears in public URLs. Read it with getBrand() from apps/saas/lib/brand/current-brand.ts. It uses the brand root param during rendering and falls back to the host header in Server Actions and route handlers, where root params are unavailable. The CentraKit host serves every organization, and a product brand host only its own; brandServesOrganization() from @workspace/foundation/brands decides, and requireOrgSlug and requireAccess return notFound() when it refuses.
Pages and layouts do not await data or import feature queries. Put <Suspense> around async feature components and export each skeleton beside the component it stands in for. Do not register orgSlug in next/root-params; only locale and brand use root params.
Typed routes are on. The generated patterns include the hidden [locale]/[brand] segments, so inferring the route type from a value already typed Route takes minutes per call site in next/link, useRouter() and redirect from next/navigation.
redirect(`/${slug}/roles`). Next infers and checks them quickly.Route (or a runtime string cast to Route once, with a SAFETY comment), pass the type argument: <Link<string> href={cancelHref}>, router.push<string>(href), redirect<string>(href).ReturnType<typeof useRouter>, not as a hand-written { push(href: Route) } shape.The next-intl Link and router from @/lib/i18n/navigation take plain strings and are not affected. tests/integration includes the generated link.d.ts so it checks SaaS sources with the same route types; cast test hrefs passed to Route props with as Route. Time pnpm --filter saas typecheck after adding a call site; it should finish in seconds.
Next.js 16 differs from older examples. Read the relevant guide in node_modules/next/dist/docs/ before changing caching, prefetching, or routing behavior.
The proxy is the authentication boundary. Adding an in-app redirect to /login when a request context has no session can create a refresh loop.
Last updated on