Core no longer imports next/* at runtime. Four things a framework owns — rendering a link, rendering an image, reading the current path, and navigating — are read through adapters instead, and the defaults are plain DOM. That is what lets Once UI install and run in a Vite app, a Remix app, or a plain React app with no Next.js present.
Everything that routes or loads an image goes through this seam: SmartLink, ElementType — and so any Button, Card or ToggleButton with an href — Media, Logo, MegaMenu and Kbar.
Import LayoutProvider from @once-ui-system/core/next instead of the root. That is the entire migration: same props, same tree, and the Next adapters (next/link, next/image, next/navigation) are installed for you.
Miss this and nothing crashes — which is the trap. The DOM defaults take over, so internal links become full page loads and images stop being optimized. The app still works, just worse. If routing suddenly feels heavy after upgrading, this import is the first thing to check.
Apps that compose their own provider tree can keep using NextAdapterProvider directly; LayoutProvider from /next only removes the boilerplate.
Do nothing. The defaults render <a> and <img>, read window.location.pathname (popstate-aware, "" on the server) and navigate with window.location.assign.
Pass adapters to AdapterProvider. It is a partial override merged onto whatever the nearest provider already supplies, so you can replace only the piece your framework does differently.
Because providers merge rather than replace, nesting one inside another is a legitimate way to override a single adapter for one subtree.
The obvious wish is for core to notice Next.js and wire itself up. It cannot, and the reason is worth knowing before you try it in your own app.
Two of the four adapters are hooks, and the two implementations call different numbers of hooks: the DOM useNavigate returns a closure and calls none, the Next one calls useRouter and useCallback. Swapping them after mount raises "Rendered more hooks than during the previous render." So resolution has to settle before the first render — and a browser bundle has no synchronous way to conditionally resolve an optional module. An explicit import is the bundler-independent version of the same thing.
useAdapters() returns the resolved set. Use it when you are building a component of your own that should route the same way core's do.
The image adapter's contract follows next/image semantics so the Next implementation is a pass-through rather than a translation layer.