Prerendering every past answer page in six languages meant 700 static pages, and the CI runner blew past Next's 60s per-page export timeout on /puzzle/236, failing the build. The set also grew by six pages a day, so every build would have been slower than the last. generateStaticParams now covers the most recent 30 days per locale (202 pages, ~35s locally). Older days keep working exactly as before: they are still linked, still in the sitemap and still indexable, but render on first request and are then cached by ISR — measured at 180ms cold. Also raises staticPageGenerationTimeout to 180s for headroom, since the runner shares a host with Gitea and Postgres. Refs #6 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QvQ1ErZNRUcWgCEkJ9uyrn
This commit is contained in:
@@ -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)),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -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 })));
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user