All files / src/lib/seo pagination-canonical.ts

100% Statements 23/23
92.3% Branches 12/13
100% Functions 2/2
100% Lines 23/23

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                          12x 12x     12x 12x   12x   5x         7x 7x 5x 3x         7x 3x   7x 2x       5x 1x 1x 1x 1x 1x     1x 1x       4x    
export interface CanonicalInput {
	baseUrl: string;
	pathname: string;
	searchParams: URLSearchParams;
	allowedCanonicalParams?: string[];
}
 
export interface CanonicalOutput {
	canonicalUrl: string;
	robots: string;
}
 
export function computeCanonical(input: CanonicalInput): CanonicalOutput {
	const { baseUrl, pathname, searchParams, allowedCanonicalParams = [] } = input;
	const base = `${baseUrl}${pathname}`;
 
	// Rule 1 & 5: page param handling
	const pageRaw = searchParams.get("page");
	const pageNum = pageRaw !== null ? parseInt(pageRaw, 10) : 1;
 
	if (pageNum > 1) {
		// Rule 1: page 2+ → noindex, canonical strips all query params
		return { canonicalUrl: base, robots: "noindex, follow" };
	}
 
	// At this point page is absent, "1", or invalid (treat as 1).
	// Collect non-page params to check for filters.
	const nonPageParams: string[] = [];
	for (const key of searchParams.keys()) {
		if (key !== "page") {
			nonPageParams.push(key);
		}
	}
 
	// Rule 2: any param NOT in allowedCanonicalParams present → index, canonical is bare base
	const hasDisallowedParam = nonPageParams.some(
		(key) => !allowedCanonicalParams.includes(key),
	);
	if (hasDisallowedParam) {
		return { canonicalUrl: base, robots: "index, follow" };
	}
 
	// Rule 3: only allowedCanonicalParams present → preserve them in canonical
	if (nonPageParams.length > 0) {
		const preserved = new URLSearchParams();
		for (const key of allowedCanonicalParams) {
			const values = searchParams.getAll(key);
			for (const v of values) {
				preserved.append(key, v);
			}
		}
		const qs = preserved.toString();
		return { canonicalUrl: qs ? `${base}?${qs}` : base, robots: "index, follow" };
	}
 
	// Rule 4: no params (or only page=1) → canonical is bare base
	return { canonicalUrl: base, robots: "index, follow" };
}