diff --git a/app/(en)/puzzle/[number]/page.tsx b/app/(en)/puzzle/[number]/page.tsx index cbeb1f6..3767e36 100644 --- a/app/(en)/puzzle/[number]/page.tsx +++ b/app/(en)/puzzle/[number]/page.tsx @@ -2,7 +2,7 @@ import type { Metadata } from "next"; import { notFound } from "next/navigation"; import PuzzleAnswers from "@/components/PuzzleAnswers"; import { ARCHIVE_COPY } from "@/lib/archive-content"; -import { getArchiveDateKeys, getPastPuzzleSet, getPuzzleNumber, getTodayKey } from "@/lib/game-engine"; +import { getPastPuzzleSet, getPuzzleNumber, getRecentArchiveDateKeys, getTodayKey } from "@/lib/game-engine"; import { BASE_URL, puzzleHreflang, puzzlePath } from "@/lib/seo-content"; export const revalidate = 3600; @@ -11,11 +11,11 @@ type PuzzlePageProps = { params: Promise<{ number: string }>; }; -// Past answers never change, so every one of them is prerendered. Days that roll -// over after the build are still served: dynamicParams stays on by default and -// they get rendered (and cached) on first request. +// Only the recent window is prerendered (see PRERENDERED_ARCHIVE_DAYS). Older +// days and days that roll over after the build are still served: dynamicParams +// stays on by default, so they render on first request and are then cached. export function generateStaticParams() { - return getArchiveDateKeys(getTodayKey()).map((dateKey) => ({ + return getRecentArchiveDateKeys(getTodayKey()).map((dateKey) => ({ number: String(getPuzzleNumber(dateKey)), })); } diff --git a/app/(intl)/[locale]/puzzle/[number]/page.tsx b/app/(intl)/[locale]/puzzle/[number]/page.tsx index ba41a48..2684639 100644 --- a/app/(intl)/[locale]/puzzle/[number]/page.tsx +++ b/app/(intl)/[locale]/puzzle/[number]/page.tsx @@ -2,7 +2,7 @@ import type { Metadata } from "next"; import { notFound } from "next/navigation"; import PuzzleAnswers from "@/components/PuzzleAnswers"; import { ARCHIVE_COPY } from "@/lib/archive-content"; -import { getArchiveDateKeys, getPastPuzzleSet, getPuzzleNumber, getTodayKey } from "@/lib/game-engine"; +import { getPastPuzzleSet, getPuzzleNumber, getRecentArchiveDateKeys, getTodayKey } from "@/lib/game-engine"; import { BASE_URL, INTL_LOCALES, puzzleHreflang, puzzlePath } from "@/lib/seo-content"; import { resolveIntlLocale } from "@/lib/intl-route"; @@ -12,10 +12,10 @@ type LocalePuzzleProps = { params: Promise<{ locale: string; number: string }>; }; -// 5 locales x every past day. They are tiny data-only pages, and prerendering -// them is what makes the localized long tail crawlable without a cold render. +// 5 locales x the recent window (see PRERENDERED_ARCHIVE_DAYS). The whole +// archive is still crawlable — the rest is rendered on demand and cached. export function generateStaticParams() { - const numbers = getArchiveDateKeys(getTodayKey()).map((dateKey) => String(getPuzzleNumber(dateKey))); + const numbers = getRecentArchiveDateKeys(getTodayKey()).map((dateKey) => String(getPuzzleNumber(dateKey))); return INTL_LOCALES.flatMap((locale) => numbers.map((number) => ({ locale, number }))); } diff --git a/lib/game-engine.ts b/lib/game-engine.ts index c0f2e76..d0ae78a 100644 --- a/lib/game-engine.ts +++ b/lib/game-engine.ts @@ -55,6 +55,17 @@ export function getPastPuzzleSet(rawNumber: string): { number: number; dateKey: return { number, dateKey, sets }; } +// How many recent days get prerendered at build time. The archive grows by one +// day forever, so an unbounded generateStaticParams would make every CI build +// slower than the last — 700 pages already blew past Next's per-page export +// timeout. Older answer pages stay fully public and indexable: they are just +// rendered on first request and then cached by ISR. +export const PRERENDERED_ARCHIVE_DAYS = 30; + +export function getRecentArchiveDateKeys(todayKey: string): string[] { + return getArchiveDateKeys(todayKey).slice(0, PRERENDERED_ARCHIVE_DAYS); +} + export function getDailyPuzzles(dateKey: string): Puzzle[] { const dated = puzzles.filter((puzzle) => puzzle.date === dateKey); const source = dated.length === DIFFICULTIES.length ? dated : getRotatingPuzzleSet(dateKey); diff --git a/next.config.js b/next.config.js index f741376..62b0fb2 100644 --- a/next.config.js +++ b/next.config.js @@ -2,6 +2,9 @@ const nextConfig = { htmlLimitedBots: /.*/, output: "standalone", + // The CI runner shares a host with other services, so a cold page render can + // occasionally crawl. 60s (the default) was not enough headroom. + staticPageGenerationTimeout: 180, }; module.exports = nextConfig;