Suspense works for lazy-loaded components and there is no supported way to suspend on a data fetch in application code.
// supported: code splitting
const Settings = lazy(() => import('./Settings'))
<Suspense fallback={<Spinner />}><Settings /></Suspense>
// not supported in application code: throwing a promise
// to suspend on data. it works, and the documentation is
// explicit that it is not a public API.
// what to use: a library that implements it — React Query,
// SWR, Relay — which is the officially recommended answer.
Throwing a promise is what the libraries do internally and it has genuinely difficult cache and invalidation semantics, which is why the recommendation is to use one rather than to write one. In 18 Suspense also became consistent between server and client rendering, which is what makes the libraries workable rather than experimental.