diff --git a/app/api/tina/rules-by-author/route.ts b/app/api/tina/rules-by-author/route.ts index 7be30a737..8674f4ac4 100644 --- a/app/api/tina/rules-by-author/route.ts +++ b/app/api/tina/rules-by-author/route.ts @@ -1,6 +1,11 @@ +import { unstable_cache } from "next/cache"; import { NextRequest, NextResponse } from "next/server"; import client from "@/tina/__generated__/client"; +const FIRST_BATCH_SIZE = 20; +const FIRST_BATCH_CACHE_SECONDS = 60 * 60 * 24; +const FIRST_BATCH_STALE_WHILE_REVALIDATE_SECONDS = 60; + export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url); @@ -13,17 +18,42 @@ export async function GET(request: NextRequest) { } const last = lastStr ? Number(lastStr) : undefined; + const isFirstBatch = before === undefined && (last === undefined || last === FIRST_BATCH_SIZE); + const isDev = process.env.NODE_ENV === "development"; + + const fetchRulesByAuthor = (a: string, l?: number, b?: string) => + client.queries.rulesByAuthor({ + authorTitle: a, + last: l, + before: b, + sort: "created", + }); + + const result = isFirstBatch && !isDev + ? await unstable_cache( + async (a: string) => fetchRulesByAuthor(a, FIRST_BATCH_SIZE, undefined), + [`tina-rules-by-author-first-batch-${authorTitle}-${FIRST_BATCH_SIZE}`], + { revalidate: FIRST_BATCH_CACHE_SECONDS } + )(authorTitle) + : await fetchRulesByAuthor(authorTitle, last, before); - const result = await client.queries.rulesByAuthor({ - authorTitle, - last, - before, - sort: "created", - }); + const res = NextResponse.json(result, { status: 200 }); + + if (isFirstBatch && !isDev) { + res.headers.set( + "Cache-Control", + `public, s-maxage=${FIRST_BATCH_CACHE_SECONDS}, stale-while-revalidate=${FIRST_BATCH_STALE_WHILE_REVALIDATE_SECONDS}` + ); + } else if (isDev) { + res.headers.set("Cache-Control", "no-store"); + } - return NextResponse.json(result, { status: 200 }); + return res; } catch (error) { console.error("Error fetching rules by author from Tina:", error); - return NextResponse.json({ error: "Failed to fetch rules", details: error instanceof Error ? error.message : String(error) }, { status: 500 }); + return NextResponse.json( + { error: "Failed to fetch rules", details: error instanceof Error ? error.message : String(error) }, + { status: 500 } + ); } } diff --git a/app/user/client-page.tsx b/app/user/client-page.tsx index 839811f85..3878e2a4a 100644 --- a/app/user/client-page.tsx +++ b/app/user/client-page.tsx @@ -111,12 +111,13 @@ export default function UserRulesClientPage({ ruleCount }) { let hasMore = true; let pageCount = 0; const MAX_PAGES = 100; // Safety limit to prevent infinite loops + const MAX_AUTHORED_RULES = 100; const allRulesFromTina: any[] = []; const seenKeys = new Set(); try { - while (hasMore && pageCount < MAX_PAGES) { + while (hasMore && pageCount < MAX_PAGES && allRulesFromTina.length < MAX_AUTHORED_RULES) { pageCount++; const params = new URLSearchParams(); @@ -152,6 +153,9 @@ export default function UserRulesClientPage({ ruleCount }) { }); allRulesFromTina.push(...batch); + if (allRulesFromTina.length > MAX_AUTHORED_RULES) { + allRulesFromTina.length = MAX_AUTHORED_RULES; + } // Render as soon as we have the first page. // Backend returns results sorted by created, so we can append without re-sorting.