refactor: use "use cache: remote" for request-time reads and updateTag/refresh for mutations

- Replace `"use cache"` with `"use cache: remote"` on functions that run inside dynamic holes (`getContributions`, `getRepos`, `getPostStats`, `getCachedComments`) so entries are shared across server instances
- Switch comment and view server actions from `revalidatePath`/`revalidateTag` to `updateTag` + `refresh` for read-your-writes invalidation
- Extract a `commentsTag(pageSlug)` helper so each page's comment cache is expired independently instead of blowing the whole "comments" tag
- Refactor `getComments` to delegate to a cached inner function that throws on failure (so errors are never cached); `getCommentCount` now reuses that cache entry instead of issuing a separate COUNT query
- Drop `lib/data/views.ts` (`getAllViewCounts`) — the `/api/hits` route now calls `getPostStats()` instead
- Remove `revalidateTag("views")` from `incrementViews`; view counts in `getPostStats` are allowed to lag by a cache TTL
- Remove the `Suspense`/`Skeleton` wrapper on the projects page since `getContributions` is now a remote-cached call
This commit is contained in:
jake committed 2026-10-03 09:50:06 -04:00
1 parent 373f1cfc75
commit d3b2d07de7
10 files changed
+75 -96

No files matched your search

+39 -35
View File
@@ -1,5 +1,6 @@
import "server-only";
import { desc, eq, sql } from "drizzle-orm";
import { desc, eq } from "drizzle-orm";
import { cacheLife, cacheTag } from "next/cache";
import { db } from "@/lib/db";
import * as schema from "@/lib/db/schema";
@@ -11,51 +12,54 @@ export type CommentWithUser = typeof schema.comment.$inferSelect & {
user: CommentUser;
};
/** Cache tag for a single page's comments; expired by the actions in lib/server/comments.ts */
export const commentsTag = (pageSlug: string) => `comments-${pageSlug}`;
/**
* Shared across server instances since it's read at request time (inside dynamic holes), not at prerender. Throws on
* failure so an error is never cached.
*/
const getCachedComments = async (pageSlug: string): Promise<CommentWithUser[]> => {
"use cache: remote";
cacheLife("hours");
cacheTag(commentsTag(pageSlug));
// Fetch all comments for the page with user details
const commentsWithUsers = await db
.select()
.from(schema.comment)
.innerJoin(schema.user, eq(schema.comment.userId, schema.user.id))
.where(eq(schema.comment.pageSlug, pageSlug))
.orderBy(desc(schema.comment.createdAt));
return commentsWithUsers.map(({ comment, user }) =>
Object.assign(comment, {
user: {
// we're namely worried about keeping the user's email private here, but nothing sensitive is stored in the db
id: user.id,
name: user.name,
image: user.image,
},
}),
);
};
/**
* Retrieves all comments for a given slug, newest first, with each author's public profile fields
*/
export const getComments = async (pageSlug: string): Promise<CommentWithUser[]> => {
try {
// Fetch all comments for the page with user details
const commentsWithUsers = await db
.select()
.from(schema.comment)
.innerJoin(schema.user, eq(schema.comment.userId, schema.user.id))
.where(eq(schema.comment.pageSlug, pageSlug))
.orderBy(desc(schema.comment.createdAt));
return commentsWithUsers.map(({ comment, user }) =>
Object.assign(comment, {
user: {
// we're namely worried about keeping the user's email private here, but nothing sensitive is stored in the db
id: user.id,
name: user.name,
image: user.image,
},
}),
);
return await getCachedComments(pageSlug);
} catch (error) {
console.error("[data/comments] error fetching comments:", error);
// Return empty array instead of throwing during prerendering
return [];
}
};
/**
* Retrieves the number of comments for a given slug (uncached -- call from inside a <Suspense> boundary)
* Retrieves the number of comments for a given slug (shares getComments' cache entry)
*/
export const getCommentCount = async (slug: string): Promise<number> => {
try {
const result = await db
.select({
count: sql<number>`cast(count(${schema.comment.id}) as int)`,
})
.from(schema.comment)
.where(eq(schema.comment.pageSlug, slug));
return result[0]?.count ?? 0;
} catch (error) {
console.error("[data/comments] error fetching comment count:", error);
return 0;
}
export const getCommentCount = async (pageSlug: string): Promise<number> => {
const comments = await getComments(pageSlug);
return comments.length;
};