Next.js storefront monorepo. @repo/shop compositions sit above Centra commerce and Sanity CMS. Product cards, basket drawers, checkout steps live in packages/shop/src/components. They used to import @repo/centra views and @repo/sanity/view directly.
That felt fast until a Centra field rename broke a product card. Or a Sanity block type leaked into a checkout molecule. Vendor shapes in the UI layer mean every API drift becomes a JSX chase. Basket and CheckoutDraft were especially painful: half the props were Centra selection types with names I did not want on a storefront contract.
The fix is two layers, not one big refactor. Thin shop mappers expose Basket, CheckoutDraft, and settings as StorefrontDto types via ReturnType inference. No hand-rolled field bags. No re-exporting vendor types from shop. Compositions only see what the storefront needs.
Biome noRestrictedImports bans @repo/centra/** and @repo/sanity/view/** under packages/shop/src/components. Mappers and services still import vendors. Live preview and image adapters stay on an allowlist because they are adapters, not product UI.
export function mapBasket(...args: Parameters<typeof mapCartSelection>) {
return mapCartSelection(...args);
}
export type BasketDto = ReturnType<typeof mapBasket>;
// biome.jsonc on packages/shop/src/components:
// noRestrictedImports: @repo/centra/**, @repo/sanity/view/**When Centra changes a cart field, I touch the mapper once. The linter stops the next developer from importing SelectionLine into a card component because it was convenient for five minutes. Convenience is how vendor APIs become your public API.
Happy coding! Sander