Skip to content

Commit 747756c

Browse files
committed
Optimize SSI public page data caching
1 parent 7cea375 commit 747756c

17 files changed

Lines changed: 290 additions & 65 deletions

File tree

src/app/[locale]/analytics/page.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ export default async function AnalyticsPage({
144144
searchParams: Promise<{
145145
aiAnalytics?: string;
146146
experimentalAnalytics?: string;
147+
history?: string;
147148
volatilityWindow?: string;
148149
}>;
149150
}) {
@@ -153,7 +154,11 @@ export default async function AnalyticsPage({
153154
const fxRatesPromise = getFxRates();
154155
const respondentCountPromise = getActiveRespondentCountData();
155156
const activeRespondentCount = await respondentCountPromise;
156-
const history = await getAnalyticsHistory(activeRespondentCount);
157+
const useFullHistory =
158+
queryParams.history === "full" ||
159+
queryParams.experimentalAnalytics === "1" ||
160+
queryParams.aiAnalytics === "1";
161+
const history = await getAnalyticsHistory(activeRespondentCount, useFullHistory);
157162
const fxRates = await fxRatesPromise;
158163
const snapshot = buildMarketSnapshot(
159164
history,
@@ -638,9 +643,9 @@ function PublishedValuesTable({
638643
);
639644
}
640645

641-
async function getAnalyticsHistory(activeRespondentCount: number) {
646+
async function getAnalyticsHistory(activeRespondentCount: number, useFullHistory: boolean) {
642647
if (hasDatabaseUrl()) {
643-
const realHistory = await getRealAnalyticsHistory();
648+
const realHistory = await getRealAnalyticsHistory(useFullHistory);
644649

645650
if (realHistory.length > 0 || !allowMockFallback()) {
646651
return realHistory;
@@ -654,8 +659,10 @@ async function getAnalyticsHistory(activeRespondentCount: number) {
654659
return [];
655660
}
656661

657-
async function getRealAnalyticsHistory(): Promise<AnalyticsPoint[]> {
658-
const rows = await getPublicHistoryData();
662+
async function getRealAnalyticsHistory(useFullHistory: boolean): Promise<AnalyticsPoint[]> {
663+
const rows = await getPublicHistoryData(
664+
useFullHistory ? { scope: "analytics" } : undefined,
665+
);
659666

660667
return rows
661668
.map((row) => ({

src/app/[locale]/media-hub/page.tsx

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { unstable_cache } from "next/cache";
12
import { redirect } from "next/navigation";
23
import { PublicMediaHub } from "@/components/media-hub/public-media-hub";
34
import type { Locale } from "@/lib/i18n";
@@ -17,9 +18,63 @@ import {
1718

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

21+
const MEDIA_HUB_LIVE_CACHE_SECONDS = 60 * 60;
22+
const MEDIA_HUB_REPORT_CACHE_SECONDS = 12 * 60 * 60;
23+
const MEDIA_HUB_ARCHIVE_CACHE_SECONDS = 12 * 60 * 60;
24+
25+
const getCachedSpikeMediaHubLiveWindows = unstable_cache(
26+
async (locale: Locale) => getSpikeMediaHubLiveWindows(locale),
27+
["spike-media-hub-live-windows"],
28+
{
29+
revalidate: MEDIA_HUB_LIVE_CACHE_SECONDS,
30+
tags: ["spike-media-hub-live"],
31+
},
32+
);
33+
34+
const getCachedSpikePublishedSummary = unstable_cache(
35+
async (
36+
kind: Exclude<MediaHubPublicationKind, "none"> | undefined,
37+
locale: Locale,
38+
periodEndDate: string | undefined,
39+
) =>
40+
getLatestPublishedMediaHubReportSummary({
41+
kind,
42+
locale,
43+
periodEndDate,
44+
tenantId: "spike-ua",
45+
}),
46+
["spike-media-hub-published-summary"],
47+
{
48+
revalidate: MEDIA_HUB_REPORT_CACHE_SECONDS,
49+
tags: ["media-hub-report-summary"],
50+
},
51+
);
52+
53+
const getCachedSpikeReportArchive = unstable_cache(
54+
async (
55+
date: string | undefined,
56+
kind: Exclude<MediaHubPublicationKind, "none"> | undefined,
57+
locale: Locale,
58+
query: string | undefined,
59+
) =>
60+
getMediaHubReportArchive({
61+
date,
62+
kind,
63+
locale,
64+
query,
65+
tenantId: "spike-ua",
66+
limit: 24,
67+
}),
68+
["spike-media-hub-report-archive"],
69+
{
70+
revalidate: MEDIA_HUB_ARCHIVE_CACHE_SECONDS,
71+
tags: ["media-hub-report-archive"],
72+
},
73+
);
74+
2075
type MediaHubPageProps = {
2176
params: Promise<{ locale: Locale }>;
22-
searchParams: Promise<{ date?: string; kind?: string; q?: string; window?: string }>;
77+
searchParams: Promise<{ archive?: string; date?: string; kind?: string; q?: string; window?: string }>;
2378
};
2479

2580
export default async function MediaHubPage({
@@ -38,21 +93,13 @@ export default async function MediaHubPage({
3893
const requestedDate = normalizeDate(search.date);
3994
const requestedQuery = normalizeQuery(search.q);
4095
const summaryKind = requestedKind ?? (requestedWindow ? windowToKind(requestedWindow) : undefined);
96+
const shouldLoadArchive = search.archive === "1" || Boolean(requestedDate || requestedKind || requestedQuery);
4197
const [liveWindows, publishedSummary, archive] = await Promise.all([
42-
getSpikeMediaHubLiveWindows(locale),
43-
getLatestPublishedMediaHubReportSummary({
44-
kind: summaryKind,
45-
locale,
46-
periodEndDate: requestedDate,
47-
tenantId: "spike-ua",
48-
}),
49-
getMediaHubReportArchive({
50-
date: requestedDate,
51-
kind: summaryKind,
52-
locale,
53-
query: requestedQuery,
54-
tenantId: "spike-ua",
55-
}),
98+
getCachedSpikeMediaHubLiveWindows(locale),
99+
getCachedSpikePublishedSummary(summaryKind, locale, requestedDate),
100+
shouldLoadArchive
101+
? getCachedSpikeReportArchive(requestedDate, summaryKind, locale, requestedQuery)
102+
: Promise.resolve([]),
56103
]);
57104
const selectedWindow = requestedWindow
58105
? requestedWindow
@@ -75,10 +122,12 @@ export default async function MediaHubPage({
75122
archiveQuery={{
76123
date: requestedDate,
77124
kind: requestedKind,
125+
loaded: shouldLoadArchive,
78126
q: requestedQuery,
79127
}}
80128
archiveHref={(filter) => {
81129
const params = new URLSearchParams();
130+
params.set("archive", "1");
82131
if (filter.kind) params.set("kind", filter.kind);
83132
if (filter.date) params.set("date", filter.date);
84133
if (filter.q) params.set("q", filter.q);

src/app/[locale]/page.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { connection } from "next/server";
21
import Link from "next/link";
32
import { HomeHero } from "@/components/ui/home-hero";
43
import { getDictionary, type Locale } from "@/lib/i18n";
@@ -11,8 +10,6 @@ export default async function LocaleHome({
1110
}: {
1211
params: Promise<{ locale: Locale }>;
1312
}) {
14-
await connection();
15-
1613
const { locale } = await params;
1714
const dict = getDictionary(locale);
1815
const { activeIndex, fxRates, respondentCount, snapshot, updatedAt } =

src/app/admin/respondents/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { revalidatePath } from "next/cache";
1+
import { revalidatePath, revalidateTag } from "next/cache";
22
import dynamicImport from "next/dynamic";
33
import { ManualHelpCard } from "@/components/manual/manual-ui";
44
import { SITE_CONFIG } from "@/lib/constants";
@@ -359,4 +359,5 @@ function revalidateRespondentPages() {
359359
revalidatePath("/en/analytics");
360360
revalidatePath("/api/public/latest");
361361
revalidatePath("/api/public/history");
362+
revalidateTag("public-index-data", "max");
362363
}

src/app/api/cron/uga-spike-demo-sync/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { revalidatePath } from "next/cache";
1+
import { revalidatePath, revalidateTag } from "next/cache";
22
import { NextResponse } from "next/server";
33
import { isCronRequestAuthorized } from "@/lib/cron-auth";
44
import { syncUgaDemoIndicesFromSpike } from "@/lib/uga-spike-demo-sync";
@@ -36,6 +36,7 @@ export async function GET(request: Request) {
3636
revalidatePath("/en/analytics");
3737
revalidatePath("/api/public/latest");
3838
revalidatePath("/api/public/history");
39+
revalidateTag("public-index-data", "max");
3940

4041
return NextResponse.json(result);
4142
}

src/app/api/public/history/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export const dynamic = "force-dynamic";
55

66
export async function GET(request: Request) {
77
try {
8-
return publicDataResponse(await getPublicHistoryData(), 0, request);
8+
return publicDataResponse(await getPublicHistoryData(), 12 * 60 * 60, request);
99
} catch (error) {
1010
return publicDataUnavailableResponse("public_history_unavailable", error);
1111
}

src/app/api/public/latest/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export const dynamic = "force-dynamic";
55

66
export async function GET(request: Request) {
77
try {
8-
return publicDataResponse(await getPublicLatestData(), 0, request);
8+
return publicDataResponse(await getPublicLatestData(), 12 * 60 * 60, request);
99
} catch (error) {
1010
return publicDataUnavailableResponse("public_latest_unavailable", error);
1111
}

src/app/api/public/public-routes.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ afterEach(() => {
1414
});
1515

1616
describe("public API routes", () => {
17-
it("returns latest data with no-store cache headers", async () => {
17+
it("returns latest data with public snapshot cache headers", async () => {
1818
const dataModule = await import("@/lib/public-api-data");
1919
vi.mocked(dataModule.getPublicLatestData).mockResolvedValue([
2020
{
@@ -36,7 +36,7 @@ describe("public API routes", () => {
3636
const body = await response.json();
3737

3838
expect(response.status).toBe(200);
39-
expect(response.headers.get("cache-control")).toBe("no-store");
39+
expect(response.headers.get("cache-control")).toContain("s-maxage=43200");
4040
expect(response.headers.get("etag")).toMatch(/^W\//);
4141
expect(body.data).toHaveLength(1);
4242
});

src/app/media-hub/page.tsx

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { unstable_cache } from "next/cache";
12
import { redirect } from "next/navigation";
23
import { PublicMediaHub } from "@/components/media-hub/public-media-hub";
34
import { PlatformShell } from "@/components/platform/platform-shell";
@@ -17,8 +18,60 @@ import { isPlatformSite } from "@/lib/platform-site";
1718

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

21+
const MEDIA_HUB_LIVE_CACHE_SECONDS = 60 * 60;
22+
const MEDIA_HUB_REPORT_CACHE_SECONDS = 12 * 60 * 60;
23+
const MEDIA_HUB_ARCHIVE_CACHE_SECONDS = 12 * 60 * 60;
24+
25+
const getCached1d3xRssWindows = unstable_cache(
26+
async () => get1d3xRssWindows(),
27+
["id3x-media-hub-rss-windows"],
28+
{
29+
revalidate: MEDIA_HUB_LIVE_CACHE_SECONDS,
30+
tags: ["id3x-media-hub-live"],
31+
},
32+
);
33+
34+
const getCached1d3xPublishedSummary = unstable_cache(
35+
async (
36+
kind: Exclude<MediaHubPublicationKind, "none"> | undefined,
37+
periodEndDate: string | undefined,
38+
) =>
39+
getLatestPublishedMediaHubReportSummary({
40+
kind,
41+
locale: "en",
42+
periodEndDate,
43+
tenantId: "1d3x",
44+
}),
45+
["id3x-media-hub-published-summary"],
46+
{
47+
revalidate: MEDIA_HUB_REPORT_CACHE_SECONDS,
48+
tags: ["media-hub-report-summary"],
49+
},
50+
);
51+
52+
const getCached1d3xReportArchive = unstable_cache(
53+
async (
54+
date: string | undefined,
55+
kind: Exclude<MediaHubPublicationKind, "none"> | undefined,
56+
query: string | undefined,
57+
) =>
58+
getMediaHubReportArchive({
59+
date,
60+
kind,
61+
locale: "en",
62+
query,
63+
tenantId: "1d3x",
64+
limit: 24,
65+
}),
66+
["id3x-media-hub-report-archive"],
67+
{
68+
revalidate: MEDIA_HUB_ARCHIVE_CACHE_SECONDS,
69+
tags: ["media-hub-report-archive"],
70+
},
71+
);
72+
2073
type PlatformMediaHubPageProps = {
21-
searchParams: Promise<{ date?: string; kind?: string; q?: string; window?: string }>;
74+
searchParams: Promise<{ archive?: string; date?: string; kind?: string; q?: string; window?: string }>;
2275
};
2376

2477
export default async function PlatformMediaHubPage({
@@ -34,21 +87,13 @@ export default async function PlatformMediaHubPage({
3487
const requestedDate = normalizeDate(search.date);
3588
const requestedQuery = normalizeQuery(search.q);
3689
const summaryKind = requestedKind ?? (requestedWindow ? windowToKind(requestedWindow) : undefined);
90+
const shouldLoadArchive = search.archive === "1" || Boolean(requestedDate || requestedKind || requestedQuery);
3791
const [liveWindows, publishedSummary, archive] = await Promise.all([
38-
get1d3xRssWindows(),
39-
getLatestPublishedMediaHubReportSummary({
40-
kind: summaryKind,
41-
locale: "en",
42-
periodEndDate: requestedDate,
43-
tenantId: "1d3x",
44-
}),
45-
getMediaHubReportArchive({
46-
date: requestedDate,
47-
kind: summaryKind,
48-
locale: "en",
49-
query: requestedQuery,
50-
tenantId: "1d3x",
51-
}),
92+
getCached1d3xRssWindows(),
93+
getCached1d3xPublishedSummary(summaryKind, requestedDate),
94+
shouldLoadArchive
95+
? getCached1d3xReportArchive(requestedDate, summaryKind, requestedQuery)
96+
: Promise.resolve([]),
5297
]);
5398
const selectedWindow = requestedWindow
5499
? requestedWindow
@@ -75,10 +120,12 @@ export default async function PlatformMediaHubPage({
75120
archiveQuery={{
76121
date: requestedDate,
77122
kind: requestedKind,
123+
loaded: shouldLoadArchive,
78124
q: requestedQuery,
79125
}}
80126
archiveHref={(filter) => {
81127
const params = new URLSearchParams();
128+
params.set("archive", "1");
82129
if (filter.kind) params.set("kind", filter.kind);
83130
if (filter.date) params.set("date", filter.date);
84131
if (filter.q) params.set("q", filter.q);

0 commit comments

Comments
 (0)