From 7bd5f55b9227ab5d4f39482dc09aae8658ba13cb Mon Sep 17 00:00:00 2001 From: Alireza Hassani Date: Tue, 15 Sep 2026 15:54:59 +0330 Subject: [PATCH] Fix Persian user-product URL redirect loop. Compare decoded/NFC slugs and stop percent-encoding path segments so detail pages no longer 307 themselves into a white screen. Co-authored-by: Cursor --- src/app/user-products/[id]/[slug]/page.tsx | 3 ++- src/lib/api/user-products.ts | 20 +++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/app/user-products/[id]/[slug]/page.tsx b/src/app/user-products/[id]/[slug]/page.tsx index 8efc73e..a5828d4 100644 --- a/src/app/user-products/[id]/[slug]/page.tsx +++ b/src/app/user-products/[id]/[slug]/page.tsx @@ -13,6 +13,7 @@ import { getUserProductByIdOrNull, getUserProductTechnicalInfoById, listUserProducts, + normalizePathSlug, productCategory, productTitle, userProductHref, @@ -51,7 +52,7 @@ export default async function UserProductDetailPage({ params }: PageProps) { if (!product) notFound(); const canonicalSlug = userProductPathSlug(product); - if (slug !== canonicalSlug) { + if (normalizePathSlug(slug) !== canonicalSlug) { redirect(userProductHref(product)); } diff --git a/src/lib/api/user-products.ts b/src/lib/api/user-products.ts index f04fbdb..58d21d7 100644 --- a/src/lib/api/user-products.ts +++ b/src/lib/api/user-products.ts @@ -77,22 +77,36 @@ export function slugifyForUrl(value: string, fallback = "user-product") { return slug || fallback; } +/** Decode + NFC so route params and API pathSlug compare safely. */ +export function normalizePathSlug(value: string) { + let decoded = value.trim(); + try { + decoded = decodeURIComponent(decoded); + } catch { + /* already decoded or malformed — keep as-is */ + } + return decoded.normalize("NFC").trim(); +} + export function userProductPathSlug( product: Pick, ) { const fromApi = product.pathSlug?.trim(); - if (fromApi) return fromApi; + if (fromApi) return normalizePathSlug(fromApi); return slugifyForUrl( product.titleFa?.trim() || product.titleEn?.trim() || "", "user-product", ); } -/** Canonical storefront path: `/user-products/{id}/{pathSlug}`. */ +/** + * Canonical storefront path: `/user-products/{id}/{pathSlug}`. + * Leave Unicode unencoded — encoding + Next param decoding caused redirect loops. + */ export function userProductHref( product: Pick, ) { - return `/user-products/${encodeURIComponent(product.id)}/${encodeURIComponent(userProductPathSlug(product))}`; + return `/user-products/${product.id}/${userProductPathSlug(product)}`; } export async function listUserProducts(params: ListUserProductsParams = {}) {