All files / src/lib/seo interior-designers-redirect.ts

100% Statements 17/17
100% Branches 15/15
100% Functions 3/3
100% Lines 16/16

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                    1x                                                 12x     12x 4x       8x 1x         7x 1x           16x 12x 1x         5x   5x 12x 3x       2x    
/**
 * Resolves legacy `/interior-designers/*` paths to their live `/interiors/*`
 * equivalents. The URL prefix was renamed pre-launch (see
 * docs/wip/plans/seo-aeo-hyderabad.md); these 301s catch any external link,
 * bookmark, or citation that still uses the old (competitor-canonical) pattern.
 *
 * Hyderabad is the only city with `/interiors` content today, so every fallback
 * lands on the Hyderabad city hub — never on a known 404.
 */
 
const HUB = "/interiors/hyderabad";
 
/**
 * Minimal structural view of the `getSeoCity` result — only the slug lists this
 * resolver needs to classify a path segment. Decoupled from the full client
 * return type so the resolver stays a pure, easily-tested function.
 */
export interface SeoCityClassification {
	localities: { slug: string | null; zoneSlug: string | null }[];
	zones: { slug: string | null }[];
	services: { slug: string }[];
	bhkTypes: { slug: string }[];
}
 
/**
 * @param segments path parts after `/interior-designers/`, already lowercased
 *   and stripped of empties (e.g. `["hyderabad", "gachibowli"]`)
 * @param cityData `getSeoCity("hyderabad")` result, or `null` when the city is
 *   not Hyderabad or the lookup failed — both collapse to the hub fallback.
 * @returns an absolute `/interiors/*` path. Always a live route; never 301→404.
 */
export function resolveInteriorDesignersRedirect(
	segments: string[],
	cityData: SeoCityClassification | null,
): string {
	const [city, seg, locality] = segments;
 
	// No city, a non-Hyderabad city (no content), or a failed lookup → hub.
	if (city !== "hyderabad" || !cityData) {
		return HUB;
	}
 
	// `/interior-designers/hyderabad`
	if (!seg) {
		return HUB;
	}
 
	// `/interior-designers/hyderabad/<zone>/<locality>` already zone-shaped:
	// pass through; the destination route validates and 404s a bad combo.
	if (locality) {
		return `${HUB}/${seg}/${locality}`;
	}
 
	// Single segment: locality URLs lack the zone, so inject it from cityData.
	// Optional-chain every list: a partial API payload must degrade to the hub
	// fallback, never throw and crash the SSR redirect.
	const matchedLocality = cityData.localities?.find((l) => l.slug === seg);
	if (matchedLocality?.zoneSlug) {
		return `${HUB}/${matchedLocality.zoneSlug}/${seg}`;
	}
 
	// Zone / service / bhk all live directly under the city hub.
	const isSingleSegmentRoute =
		cityData.zones?.some((z) => z.slug === seg) ||
		cityData.services?.some((s) => s.slug === seg) ||
		cityData.bhkTypes?.some((b) => b.slug === seg);
	if (isSingleSegmentRoute) {
		return `${HUB}/${seg}`;
	}
 
	// Unknown segment (e.g. a room category like "kitchen", or a stale slug).
	return HUB;
}