Skip to content

Commit 4dd23b2

Browse files
(SP: 2) [Frontend] Reduce Vercel variable costs via caching and analytics cleanup (#367)
* perf(vercel): cut runtime costs via notification, blog cache, and analytics changes * perf(blog): remove server searchParams usage to preserve ISR
1 parent 2c77e2a commit 4dd23b2

12 files changed

Lines changed: 139 additions & 145 deletions

File tree

frontend/actions/notifications.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use server';
22

33
import { and,desc, eq } from 'drizzle-orm';
4-
import { revalidatePath } from 'next/cache';
54

65
import { db } from '@/db';
76
import { notifications } from '@/db/schema/notifications';
@@ -39,7 +38,6 @@ export async function markAsRead(notificationId: string) {
3938
)
4039
);
4140

42-
revalidatePath('/', 'layout');
4341
return { success: true };
4442
} catch (error) {
4543
console.error('Failed to mark notification as read:', error);
@@ -57,7 +55,6 @@ export async function markAllAsRead() {
5755
.set({ isRead: true })
5856
.where(and(eq(notifications.userId, session.id), eq(notifications.isRead, false)));
5957

60-
revalidatePath('/', 'layout');
6158
return { success: true };
6259
} catch (error) {
6360
console.error('Failed to mark all notifications as read:', error);
@@ -86,7 +83,6 @@ export async function createNotification(data: {
8683
})
8784
.returning();
8885

89-
revalidatePath('/', 'layout');
9086
return result;
9187
} catch (error) {
9288
console.error('Failed to create notification:', error);

frontend/app/[locale]/blog/[slug]/PostDetails.tsx

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import { Link } from '@/i18n/routing';
99
import { formatBlogDate } from '@/lib/blog/date';
1010
import { shouldBypassImageOptimization } from '@/lib/blog/image';
1111

12-
export const revalidate = 0;
13-
1412
type SocialLink = {
1513
_key?: string;
1614
platform?: string;
@@ -383,18 +381,14 @@ export default async function PostDetails({
383381
const slugParam = String(slug || '').trim();
384382
if (!slugParam) return notFound();
385383

386-
const post: Post | null = await client
387-
.withConfig({ useCdn: false })
388-
.fetch(query, {
389-
slug: slugParam,
390-
locale,
391-
});
392-
const recommendedAll: Post[] = await client
393-
.withConfig({ useCdn: false })
394-
.fetch(recommendedQuery, {
395-
slug: slugParam,
396-
locale,
397-
});
384+
const post: Post | null = await client.fetch(query, {
385+
slug: slugParam,
386+
locale,
387+
});
388+
const recommendedAll: Post[] = await client.fetch(recommendedQuery, {
389+
slug: slugParam,
390+
locale,
391+
});
398392
const recommendedPosts = seededShuffle(
399393
recommendedAll,
400394
hashString(slugParam)

frontend/app/[locale]/blog/[slug]/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { client } from '@/client';
44

55
import PostDetails from './PostDetails';
66

7+
export const revalidate = 3600;
8+
79
export async function generateStaticParams() {
810
const slugs = await client.fetch<string[]>(
911
groq`*[_type == "post" && defined(slug.current)][].slug.current`

frontend/app/[locale]/blog/category/[category]/page.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { Link } from '@/i18n/routing';
1111
import { formatBlogDate } from '@/lib/blog/date';
1212
import { shouldBypassImageOptimization } from '@/lib/blog/image';
1313

14-
export const revalidate = 0;
14+
export const revalidate = 3600;
1515

1616
type Author = {
1717
name?: string;
@@ -50,9 +50,7 @@ export default async function BlogCategoryPage({
5050
const t = await getTranslations({ locale, namespace: 'blog' });
5151
const tNav = await getTranslations({ locale, namespace: 'navigation' });
5252
const categoryKey = String(category || '').toLowerCase();
53-
const categories: Category[] = await client
54-
.withConfig({ useCdn: false })
55-
.fetch(categoriesQuery);
53+
const categories: Category[] = await client.fetch(categoriesQuery);
5654
const matchedCategory = categories.find(
5755
item => slugify(item.title) === categoryKey
5856
);
@@ -61,7 +59,7 @@ export default async function BlogCategoryPage({
6159
const categoryTitle = matchedCategory.title;
6260
const categoryDisplay = getCategoryLabel(categoryTitle, t);
6361

64-
const posts: Post[] = await client.withConfig({ useCdn: false }).fetch(
62+
const posts: Post[] = await client.fetch(
6563
groq`
6664
*[_type == "post" && defined(slug.current) && $category in categories[]->title]
6765
| order(coalesce(publishedAt, _createdAt) desc) {

frontend/app/[locale]/blog/page.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import groq from 'groq';
2-
import { unstable_noStore as noStore } from 'next/cache';
32
import { getTranslations } from 'next-intl/server';
43

54
import { client } from '@/client';
65
import BlogFilters from '@/components/blog/BlogFilters';
76
import { BlogPageHeader } from '@/components/blog/BlogPageHeader';
87
import { DynamicGridBackground } from '@/components/shared/DynamicGridBackground';
98

10-
export const revalidate = 0;
9+
export const revalidate = 3600;
1110

1211
export async function generateMetadata({
1312
params,
@@ -25,19 +24,13 @@ export async function generateMetadata({
2524

2625
export default async function BlogPage({
2726
params,
28-
searchParams,
2927
}: {
3028
params: Promise<{ locale: string }>;
31-
searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
3229
}) {
33-
noStore();
3430
const { locale } = await params;
3531
const t = await getTranslations({ locale, namespace: 'blog' });
36-
const sp = searchParams ? await searchParams : undefined;
37-
const authorParam = typeof sp?.author === 'string' ? sp.author.trim() : '';
38-
const hasAuthorFilter = authorParam.length > 0;
3932

40-
const posts = await client.withConfig({ useCdn: false }).fetch(
33+
const posts = await client.fetch(
4134
groq`
4235
*[_type == "post" && defined(slug.current)]
4336
| order(coalesce(publishedAt, _createdAt) desc) {
@@ -74,7 +67,7 @@ export default async function BlogPage({
7467
`,
7568
{ locale }
7669
);
77-
const categories = await client.withConfig({ useCdn: false }).fetch(
70+
const categories = await client.fetch(
7871
groq`
7972
*[_type == "category"] | order(orderRank asc) {
8073
_id,
@@ -87,9 +80,7 @@ export default async function BlogPage({
8780
return (
8881
<DynamicGridBackground className="bg-gray-50 py-10 transition-colors duration-300 dark:bg-transparent">
8982
<main className="relative z-10 mx-auto max-w-7xl px-4 pt-6 pb-12 sm:px-6 lg:px-8">
90-
{!hasAuthorFilter && (
91-
<BlogPageHeader title={t('title')} subtitle={t('subtitle')} />
92-
)}
83+
<BlogPageHeader title={t('title')} subtitle={t('subtitle')} />
9384
<BlogFilters
9485
posts={posts}
9586
categories={categories}

frontend/app/[locale]/layout.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import groq from 'groq';
2+
import { unstable_cache } from 'next/cache';
23
import { notFound } from 'next/navigation';
34
import { NextIntlClientProvider } from 'next-intl';
45
import { getMessages } from 'next-intl/server';
@@ -17,6 +18,18 @@ import { getCurrentUser } from '@/lib/auth';
1718

1819
export const dynamic = 'force-dynamic';
1920

21+
const getCachedBlogCategories = unstable_cache(
22+
async () =>
23+
client.fetch<Array<{ _id: string; title: string }>>(groq`
24+
*[_type == "category"] | order(orderRank asc) {
25+
_id,
26+
title
27+
}
28+
`),
29+
['blog-categories'],
30+
{ revalidate: 3600, tags: ['blog-categories'] }
31+
);
32+
2033
export default async function LocaleLayout({
2134
children,
2235
params,
@@ -30,16 +43,7 @@ export default async function LocaleLayout({
3043

3144
const messages = await getMessages({ locale });
3245
const user = await getCurrentUser();
33-
const blogCategories: Array<{ _id: string; title: string }> = await client
34-
.withConfig({ useCdn: false })
35-
.fetch(
36-
groq`
37-
*[_type == "category"] | order(orderRank asc) {
38-
_id,
39-
title
40-
}
41-
`
42-
);
46+
const blogCategories = await getCachedBlogCategories();
4347

4448
const userExists = Boolean(user);
4549
const enableAdmin =

frontend/app/api/blog-author/route.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ export async function GET(request: Request) {
3232
return NextResponse.json(null, { status: 400 });
3333
}
3434

35-
const author = await client
36-
.withConfig({ useCdn: false })
37-
.fetch(authorQuery, { name, locale });
35+
const author = await client.fetch(authorQuery, { name, locale });
3836

3937
return NextResponse.json(author || null, {
4038
headers: { 'Cache-Control': 'no-store' },

frontend/app/layout.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import './globals.css';
22

33
import { Analytics } from '@vercel/analytics/next';
4-
import { SpeedInsights } from '@vercel/speed-insights/next';
54
import type { Metadata } from 'next';
65
import { Geist, Geist_Mono } from 'next/font/google';
76

@@ -90,8 +89,7 @@ export default function RootLayout({
9089
className={`${geistSans.variable} ${geistMono.variable} bg-gray-50 text-gray-900 antialiased transition-colors duration-300 dark:bg-neutral-950 dark:text-gray-100`}
9190
>
9291
{children}
93-
<Analytics />
94-
<SpeedInsights />
92+
{process.env.NODE_ENV === 'production' && <Analytics />}
9593
</body>
9694
</html>
9795
);

frontend/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ import { createClient } from '@sanity/client';
33
export const client = createClient({
44
projectId: '6y9ive6v',
55
dataset: 'production',
6-
useCdn: false,
6+
useCdn: true,
77
apiVersion: '2025-11-29',
88
});

0 commit comments

Comments
 (0)