All files / src/lib warm-urls.ts

100% Statements 20/20
100% Branches 1/1
100% Functions 7/7
100% Lines 18/18

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                            22x     22x                         22x         22x                       22x                 22x   132x 154x     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 function topWarmUrls(siteUrl: string): 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",
	];
 
	// Hyderabad city hub and zone sub-pages.
	// Zones mirror ZONE_SLUGS in pages/interiors/hyderabad/[segment].astro.
	const hyderabadZones = ["west", "central", "east", "south", "north", "old-city"];
 
	// Hyderabad service and BHK landing pages.
	// These are the most keyword-rich pages — warm them so cold SSR isn't the
	// first request from Googlebot after a deploy.
	const hyderabadSegments = [
		"modular-kitchen",
		"full-home-interior",
		"2bhk-turnkey-package",
		"1bhk-interior",
		"2bhk-interior",
		"3bhk-interior",
		"4bhk-interior",
	];
 
	// 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 hyderabadRoutes = [
		"/interiors/hyderabad",
		...hyderabadZones.map((z) => `/interiors/hyderabad/${z}`),
		...hyderabadSegments.map((s) => `/interiors/hyderabad/${s}`),
	];
 
	const roomRoutes = roomCategories.map((c) => `/rooms/${c}`);
 
	const allPaths = [...coreRoutes, ...hyderabadRoutes, ...roomRoutes];
 
	return allPaths.map((path) => `${base}${path}`);
}
 
export interface WarmResult {
	url: string;
	status: number;
}
 
export async function warmCache(
	siteUrl: string,
	fetchImpl: typeof fetch = fetch,
): Promise<WarmResult[]> {
	const urls = topWarmUrls(siteUrl);
 
	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;
}