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