feat(frontend): add next.tags cache support to apiFetch; tag all GET fetches

This commit is contained in:
2026-05-15 19:38:24 +02:00
parent fe610c8b6f
commit 091c3a4706

View File

@@ -151,9 +151,13 @@ const API_BASE_URL =
? process.env.NEXT_PUBLIC_SERVER_SIDE_API_URL
: process.env.NEXT_PUBLIC_API_URL;
type ApiFetchOptions = Omit<RequestInit, 'next'> & {
next?: { tags?: string[]; revalidate?: number | false }
}
async function apiFetch<T>(
endpoint: string,
options: RequestInit = {},
options: ApiFetchOptions = {},
schema: z.ZodType<T>,
token?: string | null
): Promise<T> {
@@ -161,9 +165,11 @@ async function apiFetch<T>(
throw new Error("API_BASE_URL is not defined");
}
const { next, ...restOptions } = options;
const headers: Record<string, string> = {
"Content-Type": "application/json",
...(options.headers as Record<string, string>),
...(restOptions.headers as Record<string, string>),
};
if (token) {
@@ -171,8 +177,9 @@ async function apiFetch<T>(
}
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
...options,
...restOptions,
headers,
...(next ? { next } : {}),
});
if (!response.ok) {
@@ -202,7 +209,7 @@ export const loginUser = (data: z.infer<typeof LoginSchema>) =>
// ── Current user ──────────────────────────────────────────────────────────
export const getMe = (token: string) =>
apiFetch("/users/me", {}, MeSchema, token);
apiFetch("/users/me", { next: { tags: ['me'] } }, MeSchema, token);
export const updateProfile = (data: z.infer<typeof UpdateProfileSchema>, token: string) =>
apiFetch("/users/me", { method: "PATCH", body: JSON.stringify(data) }, UserSchema, token);
@@ -213,13 +220,13 @@ export const getMeFollowingList = (token: string) =>
// ── Users ─────────────────────────────────────────────────────────────────
export const getUserProfile = (username: string, token: string | null) =>
apiFetch(`/users/${username}`, {}, UserSchema, token);
apiFetch(`/users/${username}`, { next: { tags: [`profile:${username}`] } }, UserSchema, token);
export const getFollowersList = (username: string, token: string | null) =>
apiFetch(`/users/${username}/followers`, {}, z.object({ total: z.number(), items: z.array(UserSchema) }), token);
apiFetch(`/users/${username}/followers`, { next: { tags: [`profile:${username}`] } }, z.object({ total: z.number(), items: z.array(UserSchema) }), token);
export const getFollowingList = (username: string, token: string | null) =>
apiFetch(`/users/${username}/following`, {}, z.object({ total: z.number(), items: z.array(UserSchema) }), token);
apiFetch(`/users/${username}/following`, { next: { tags: [`profile:${username}`] } }, z.object({ total: z.number(), items: z.array(UserSchema) }), token);
export const getTopFriends = (username: string, token: string | null) =>
apiFetch(
@@ -330,7 +337,7 @@ export const getAllUsersCount = () =>
export const getFeed = (token: string, page: number = 1, pageSize: number = 20) =>
apiFetch(
`/feed?page=${page}&per_page=${pageSize}`,
{},
{ next: { tags: ['feed'] } },
z.object({ items: z.array(ThoughtSchema), total: z.number(), page: z.number(), per_page: z.number() })
.transform((d) => ({ ...d, totalPages: Math.ceil(d.total / d.per_page) })),
token
@@ -339,7 +346,7 @@ export const getFeed = (token: string, page: number = 1, pageSize: number = 20)
export const getUserThoughts = (username: string, token: string | null) =>
apiFetch(
`/users/${username}/thoughts`,
{},
{ next: { tags: [`profile:${username}`] } },
z.object({ items: z.array(ThoughtSchema), total: z.number(), page: z.number(), per_page: z.number() }),
token
);
@@ -351,7 +358,7 @@ export const deleteThought = (thoughtId: string, token: string) =>
apiFetch(`/thoughts/${thoughtId}`, { method: "DELETE" }, z.null(), token);
export const getThoughtById = (thoughtId: string, token: string | null) =>
apiFetch(`/thoughts/${thoughtId}`, {}, ThoughtSchema, token);
apiFetch(`/thoughts/${thoughtId}`, { next: { tags: [`thought:${thoughtId}`] } }, ThoughtSchema, token);
export const getThoughtThread = async (thoughtId: string, token: string | null): Promise<ThoughtThread> => {
const thoughts = await apiFetch(`/thoughts/${thoughtId}/thread`, {}, z.array(ThoughtSchema), token);
@@ -375,7 +382,7 @@ export const getThoughtThread = async (thoughtId: string, token: string | null):
export const getThoughtsByTag = (tagName: string, token: string | null) =>
apiFetch(
`/tags/${tagName}`,
{},
{ next: { tags: [`tag:${tagName}`, 'feed'] } },
z.object({ tag: z.string(), items: z.array(ThoughtSchema), total: z.number(), page: z.number(), per_page: z.number() }),
token
);
@@ -391,7 +398,7 @@ export const getPopularTags = () =>
// ── Search ────────────────────────────────────────────────────────────────
export const search = (query: string, token: string | null) =>
apiFetch(`/search?q=${encodeURIComponent(query)}`, {}, SearchResultsSchema, token);
apiFetch(`/search?q=${encodeURIComponent(query)}`, { next: { tags: ['search'] } }, SearchResultsSchema, token);
// ── API Keys ──────────────────────────────────────────────────────────────