All files / src/lib warm-urls.ts

100% Statements 20/20
66.66% Branches 4/6
100% Functions 8/8
100% Lines 17/17

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112                                    1x                                   22x     22x                         22x   132x 154x         22x                 132x   22x   616x                         7x   7x   196x 196x                 167x       29x         7x    
// warm-urls.ts — top-N URL list and cache-warmer for the scheduled cron handler.
//
// `topWarmUrls` returns absolute URLs that are high-value SSR pages: the core
// listing/hub roots plus the Hyderabad city/zone/locality landing pages (which
// hit the most expensive D1 queries and benefit most from a warm KV cache).
//
// `warmCache` issues a plain GET for each URL (no cookies, so the request is
// anonymous and the edge-cache write path is eligible). Individual fetch errors
// are swallowed — a 404 or a network blip should not abort the whole warm run.
// The caller (the `scheduled` handler) wraps the call in `ctx.waitUntil` so it
// doesn't block the response path.
 
export type WarmCity = {
	slug: string;
	zones?: string[];
	segments?: string[];
};
 
export const DEFAULT_WARM_CITIES: WarmCity[] = [
	{
		slug: "hyderabad",
		zones: ["west", "central", "east", "south", "north", "old-city"],
		segments: [
			"modular-kitchen",
			"full-home-interior",
			"2bhk-turnkey-package",
			"1bhk-interior",
			"2bhk-interior",
			"3bhk-interior",
			"4bhk-interior",
		],
	},
];
 
export function topWarmUrls(siteUrl: string, cities: WarmCity[] = DEFAULT_WARM_CITIES): string[] {
	// Trim trailing slash so we can append paths cleanly.
	const base = siteUrl.replace(/\/+$/, "");
 
	// Core listing / hub roots — always warm first.
	const coreRoutes = [
		"/",
		"/pros",
		"/projects",
		"/rooms",
		"/blog",
		"/how-it-works",
		"/faq",
		"/tools/interior-design-cost-calculator",
	];
 
	// Live city hubs, zones, services and BHK pages. The default list is
	// Hyderabad, which is what the warmer used before cities were data-driven.
	const cityRoutes = cities.flatMap((city) => [
		`/interiors/${city.slug}`,
		...(city.zones ?? []).map((z) => `/interiors/${city.slug}/${z}`),
		...(city.segments ?? []).map((s) => `/interiors/${city.slug}/${s}`),
	]);
 
	// High-traffic room category pages (value = URL slug).
	// Mirrors ROOM_CATEGORIES values from lib/api/constants.ts.
	const roomCategories = [
		"living_room",
		"kitchen",
		"bedroom",
		"full_home",
		"bathroom",
		"dining",
	];
 
	const roomRoutes = roomCategories.map((c) => `/rooms/${c}`);
 
	const allPaths = [...coreRoutes, ...cityRoutes, ...roomRoutes];
 
	return allPaths.map((path) => `${base}${path}`);
}
 
export interface WarmResult {
	url: string;
	status: number;
}
 
export async function warmCache(
	siteUrl: string,
	fetchImpl: typeof fetch = fetch,
	cities?: WarmCity[],
): Promise<WarmResult[]> {
	const urls = topWarmUrls(siteUrl, cities);
 
	const results = await Promise.all(
		urls.map(async (url): Promise<WarmResult> => {
			try {
				const res = await fetchImpl(url, {
					// No cookies — ensures the edge-cache write path is eligible.
					// The `no-cache` directive tells the edge to revalidate even if
					// a cached copy exists, so stale HTML is replaced immediately.
					headers: {
						"Cache-Control": "no-cache",
						"User-Agent": "Interioring-Warmer/1.0",
					},
				});
				return { url, status: res.status };
			} catch {
				// Network error, DNS failure, or similar transient issue.
				// Return status 0 so the caller can log/observe without aborting.
				return { url, status: 0 };
			}
		}),
	);
 
	return results;
}