Spun out of #7601 review feedback from @SamTV12345.
Problem
The admin UI currently hand-writes fetch calls in useEffect throughout (UpdateBanner, UpdatePage, plus the existing settings/plugins/users pages). Two issues with this:
- Pattern is officially discouraged — see the React docs: https://react.dev/reference/react/useEffect#fetching-data-with-effects (no caching, no dedup, no race-condition handling, manual loading states).
- Endpoint shape is duplicated — every fetch site re-types the request and response by hand. We already publish a Swagger/OpenAPI spec that describes these endpoints authoritatively, so any drift between the spec and the hand-written types is a silent bug.
Proposal
Generate the admin's API client directly from the OpenAPI spec, and use TanStack Query as the runtime.
Toolchain:
openapi-typescript — generates a TS types file from src/node/hooks/express/swagger.ts output. Run as a build step; the result is checked in or generated pre-build.
openapi-fetch — typed fetch wrapper that consumes the generated types. Zero runtime overhead, ~3 KB gzipped.
openapi-react-query — thin TanStack Query bindings that derive query/mutation hooks from the same generated types. Caching, dedup, refetch-on-focus, devtools all come along for free.
@tanstack/react-query — the runtime.
Net result: every endpoint becomes one line — useQuery({ ...client.GET("/admin/...") }) — with full request/response inference and no hand-written types.
Scope
- Wire the OpenAPI generator into the admin build pipeline (or the root
pnpm build).
- Add
@tanstack/react-query + provider at the admin root, plus React Query Devtools in dev.
- Migrate every
useEffect+fetch site in admin/src/ to the generated hooks.
- Replace
if (!data) return null patterns with Suspense boundaries (useSuspenseQuery).
- Document the codegen step in
admin/README so contributors know how to regen after API changes.
Out of scope
- Migrating the pad-side frontend (different bundle, different concerns).
- Backend changes — the OpenAPI spec is the contract; if anything moves, that moves first.
Why now
Tracking out of #7601 specifically — that PR adds two new admin fetch sites, and bundling this migration into it would balloon the diff. Cleaner as a dedicated PR.
Spun out of #7601 review feedback from @SamTV12345.
Problem
The admin UI currently hand-writes
fetchcalls inuseEffectthroughout (UpdateBanner,UpdatePage, plus the existing settings/plugins/users pages). Two issues with this:Proposal
Generate the admin's API client directly from the OpenAPI spec, and use TanStack Query as the runtime.
Toolchain:
openapi-typescript— generates a TS types file fromsrc/node/hooks/express/swagger.tsoutput. Run as a build step; the result is checked in or generated pre-build.openapi-fetch— typedfetchwrapper that consumes the generated types. Zero runtime overhead, ~3 KB gzipped.openapi-react-query— thin TanStack Query bindings that derive query/mutation hooks from the same generated types. Caching, dedup, refetch-on-focus, devtools all come along for free.@tanstack/react-query— the runtime.Net result: every endpoint becomes one line —
useQuery({ ...client.GET("/admin/...") })— with full request/response inference and no hand-written types.Scope
pnpm build).@tanstack/react-query+ provider at the admin root, plus React Query Devtools in dev.useEffect+fetchsite inadmin/src/to the generated hooks.if (!data) return nullpatterns with Suspense boundaries (useSuspenseQuery).admin/READMEso contributors know how to regen after API changes.Out of scope
Why now
Tracking out of #7601 specifically — that PR adds two new admin fetch sites, and bundling this migration into it would balloon the diff. Cleaner as a dedicated PR.