perf: optimize web data resources

This commit is contained in:
ZL-Q
2026-05-10 20:01:14 +08:00
parent a9739cddce
commit 1e4871e337
24 changed files with 1304 additions and 252 deletions
+51
View File
@@ -48,10 +48,61 @@ Login and public marketing/legal pages are not part of the authenticated app she
- Shared request behavior lives in `web/src/lib/api-client.ts`.
- Auth/session behavior lives in `web/src/lib/auth.ts`.
- Business API functions live in `web/src/lib/api.ts`.
- Shared authenticated read caching lives in `web/src/lib/data-client.ts` and `web/src/lib/resources.ts`.
- Components must call typed API helper functions, not inline `fetch('/api/...')`.
- Components that need profile, points, packages, history, notifications, or unread-count data should use the resource hooks/functions from `web/src/lib/resources.ts` instead of starting their own duplicate GET lifecycle.
- Dashboard-visible user, points, notification, and history data must come from the backend. Do not hardcode those values.
- Production API host is `https://api.meeyao.com`; local dev should use same-origin `/api` and the Vite proxy.
### Authenticated Data Resource Pattern
Use this pattern for backend reads that are reused across authenticated pages:
```typescript
// lib/api.ts: transport-only business API
export function getPointsBalance(): Promise<PointsBalance> {
return authFetch<PointsBalance>(API_ROUTES.points.balance);
}
// lib/resources.ts: cache policy + hook/function surface
export function usePoints() {
return useResource({
key: pointsBalanceKey,
ttlMs: 60_000,
fetcher: getPointsBalance,
staleWhileRevalidate: true,
});
}
```
Resource contracts:
- `lib/api.ts` remains transport-only: no per-endpoint ad hoc memory cache there.
- `lib/resources.ts` owns resource keys, TTLs, in-flight dedupe, stale-while-revalidate behavior, prefetch, and mutation invalidation.
- `clearAuth()` must clear the shared data cache so authenticated data cannot leak across users.
- Resource hooks must support disabled/optional keys for pages where an id may be absent; do not create a fetcher that intentionally rejects during normal render.
- Active hooks must refetch after invalidation when they still need the resource.
Invalidation matrix:
- Profile, avatar, or settings write -> set the profile resource with the returned backend profile.
- Divination run or follow-up completion -> invalidate points and the relevant history list/thread resources.
- Notification mark-read -> patch the notification list and decrement unread count when the item changes from unread to read.
- Mark-all-notifications-read -> patch the notification list and set unread count to zero.
- Logout, expired refresh, or invalid auth -> clear auth and clear all resource data.
Wrong vs correct:
```typescript
// Wrong: every page starts an independent duplicate GET.
useEffect(() => {
getUserProfile().then(setProfile);
}, []);
// Correct: subscribe to the shared profile resource.
const profileState = useProfile();
```
## Layout Rules
- Build mobile-first, then add `sm:`, `md:`, `lg:`, and `xl:` refinements.