[{"data":1,"prerenderedAt":1335},["ShallowReactive",2],{"guide-slug-uniqueness-strategies":3},{"id":4,"title":5,"body":6,"date":1327,"description":1328,"extension":1329,"meta":1330,"navigation":173,"path":1331,"readingTime":225,"seo":1332,"stem":1333,"__hash__":1334},"guides\u002Fguides\u002Fslug-uniqueness-strategies.md","Slug Uniqueness Strategies Guide",{"type":7,"value":8,"toc":1319},"minimark",[9,13,18,21,50,104,107,111,114,290,296,307,312,331,337,501,505,508,603,607,621,625,636,642,645,741,744,748,751,797,975,979,990,994,1005,1009,1012,1041,1245,1252,1256,1307,1315],[10,11,12],"p",{},"Every page on your site needs a unique URL slug. When two articles share the same title — \"Introduction to Python\" appears in both a beginner series and a data science track — you need a strategy to disambiguate them. The approach you choose affects URL aesthetics, SEO value, and system complexity. This guide covers three proven strategies with their tradeoffs.",[14,15,17],"h2",{"id":16},"the-problem-title-collisions","The Problem: Title Collisions",[10,19,20],{},"Title collisions are more common than you might expect:",[22,23,24,32,38,44],"ul",{},[25,26,27,31],"li",{},[28,29,30],"strong",{},"Series articles",": \"Part 1\", \"Part 2\" → identical after slug truncation",[25,33,34,37],{},[28,35,36],{},"Updated posts",": \"React Tutorial\" reposted in 2025 and 2026",[25,39,40,43],{},[28,41,42],{},"Category overlaps",": same topic covered in different contexts",[25,45,46,49],{},[28,47,48],{},"User-generated content",": multiple users naming items identically",[51,52,57],"pre",{"className":53,"code":54,"language":55,"meta":56,"style":56},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F Collision example\nslugify('Introduction to Python')       \u002F\u002F 'introduction-to-python'\nslugify('Introduction to Python')       \u002F\u002F 'introduction-to-python' — duplicate!\n","javascript","",[58,59,60,69,90],"code",{"__ignoreMap":56},[61,62,65],"span",{"class":63,"line":64},"line",1,[61,66,68],{"class":67},"sJ8bj","\u002F\u002F Collision example\n",[61,70,72,76,80,84,87],{"class":63,"line":71},2,[61,73,75],{"class":74},"sScJk","slugify",[61,77,79],{"class":78},"sVt8B","(",[61,81,83],{"class":82},"sZZnC","'Introduction to Python'",[61,85,86],{"class":78},")       ",[61,88,89],{"class":67},"\u002F\u002F 'introduction-to-python'\n",[61,91,93,95,97,99,101],{"class":63,"line":92},3,[61,94,75],{"class":74},[61,96,79],{"class":78},[61,98,83],{"class":82},[61,100,86],{"class":78},[61,102,103],{"class":67},"\u002F\u002F 'introduction-to-python' — duplicate!\n",[10,105,106],{},"Without a uniqueness strategy, the second article either overwrites the first or fails to save — both are bad outcomes.",[14,108,110],{"id":109},"strategy-1-numeric-suffixes","Strategy 1: Numeric Suffixes",[10,112,113],{},"The simplest approach adds an incrementing number when a collision occurs:",[51,115,117],{"className":53,"code":116,"language":55,"meta":56,"style":56},"async function uniqueNumericSlug(baseSlug, slugExists) {\n  if (!await slugExists(baseSlug)) return baseSlug\n\n  let counter = 2\n  while (await slugExists(`${baseSlug}-${counter}`)) {\n    counter++\n  }\n  return `${baseSlug}-${counter}`\n}\n\n\u002F\u002F Results:\n\u002F\u002F 'introduction-to-python'\n\u002F\u002F 'introduction-to-python-2'\n\u002F\u002F 'introduction-to-python-3'\n",[58,118,119,146,169,175,191,223,232,238,256,262,267,273,278,284],{"__ignoreMap":56},[61,120,121,125,128,131,133,137,140,143],{"class":63,"line":64},[61,122,124],{"class":123},"szBVR","async",[61,126,127],{"class":123}," function",[61,129,130],{"class":74}," uniqueNumericSlug",[61,132,79],{"class":78},[61,134,136],{"class":135},"s4XuR","baseSlug",[61,138,139],{"class":78},", ",[61,141,142],{"class":135},"slugExists",[61,144,145],{"class":78},") {\n",[61,147,148,151,154,157,160,163,166],{"class":63,"line":71},[61,149,150],{"class":123},"  if",[61,152,153],{"class":78}," (",[61,155,156],{"class":123},"!await",[61,158,159],{"class":74}," slugExists",[61,161,162],{"class":78},"(baseSlug)) ",[61,164,165],{"class":123},"return",[61,167,168],{"class":78}," baseSlug\n",[61,170,171],{"class":63,"line":92},[61,172,174],{"emptyLinePlaceholder":173},true,"\n",[61,176,178,181,184,187],{"class":63,"line":177},4,[61,179,180],{"class":123},"  let",[61,182,183],{"class":78}," counter ",[61,185,186],{"class":123},"=",[61,188,190],{"class":189},"sj4cs"," 2\n",[61,192,194,197,199,202,204,206,209,211,214,217,220],{"class":63,"line":193},5,[61,195,196],{"class":123},"  while",[61,198,153],{"class":78},[61,200,201],{"class":123},"await",[61,203,159],{"class":74},[61,205,79],{"class":78},[61,207,208],{"class":82},"`${",[61,210,136],{"class":78},[61,212,213],{"class":82},"}-${",[61,215,216],{"class":78},"counter",[61,218,219],{"class":82},"}`",[61,221,222],{"class":78},")) {\n",[61,224,226,229],{"class":63,"line":225},6,[61,227,228],{"class":78},"    counter",[61,230,231],{"class":123},"++\n",[61,233,235],{"class":63,"line":234},7,[61,236,237],{"class":78},"  }\n",[61,239,241,244,247,249,251,253],{"class":63,"line":240},8,[61,242,243],{"class":123},"  return",[61,245,246],{"class":82}," `${",[61,248,136],{"class":78},[61,250,213],{"class":82},[61,252,216],{"class":78},[61,254,255],{"class":82},"}`\n",[61,257,259],{"class":63,"line":258},9,[61,260,261],{"class":78},"}\n",[61,263,265],{"class":63,"line":264},10,[61,266,174],{"emptyLinePlaceholder":173},[61,268,270],{"class":63,"line":269},11,[61,271,272],{"class":67},"\u002F\u002F Results:\n",[61,274,276],{"class":63,"line":275},12,[61,277,89],{"class":67},[61,279,281],{"class":63,"line":280},13,[61,282,283],{"class":67},"\u002F\u002F 'introduction-to-python-2'\n",[61,285,287],{"class":63,"line":286},14,[61,288,289],{"class":67},"\u002F\u002F 'introduction-to-python-3'\n",[10,291,292,295],{},[28,293,294],{},"Advantages",":",[22,297,298,301,304],{},[25,299,300],{},"URLs remain human-readable",[25,302,303],{},"Sequential numbering feels natural",[25,305,306],{},"No external dependencies",[10,308,309,295],{},[28,310,311],{},"Disadvantages",[22,313,314,321,324],{},[25,315,316,317,320],{},"Numbers carry no semantic meaning — ",[58,318,319],{},"-2"," tells you nothing about content difference",[25,322,323],{},"Gaps appear when articles are deleted (1, 2, 4 if 3 is removed)",[25,325,326,327,330],{},"Race conditions in concurrent environments — two requests might both check for ",[58,328,329],{},"introduction-to-python-2"," at the same time and both claim it",[10,332,333,336],{},[28,334,335],{},"Race condition fix",": Use a database unique constraint and retry on conflict:",[51,338,340],{"className":53,"code":339,"language":55,"meta":56,"style":56},"async function safeNumericSlug(baseSlug, saveSlug) {\n  let slug = baseSlug\n  let counter = 1\n\n  while (true) {\n    try {\n      await saveSlug(slug)  \u002F\u002F INSERT with unique constraint\n      return slug\n    } catch (error) {\n      if (error.code !== 'UNIQUE_CONSTRAINT') throw error\n      counter++\n      slug = `${baseSlug}-${counter}`\n    }\n  }\n}\n",[58,341,342,362,373,384,388,399,407,421,429,440,463,470,487,492,496],{"__ignoreMap":56},[61,343,344,346,348,351,353,355,357,360],{"class":63,"line":64},[61,345,124],{"class":123},[61,347,127],{"class":123},[61,349,350],{"class":74}," safeNumericSlug",[61,352,79],{"class":78},[61,354,136],{"class":135},[61,356,139],{"class":78},[61,358,359],{"class":135},"saveSlug",[61,361,145],{"class":78},[61,363,364,366,369,371],{"class":63,"line":71},[61,365,180],{"class":123},[61,367,368],{"class":78}," slug ",[61,370,186],{"class":123},[61,372,168],{"class":78},[61,374,375,377,379,381],{"class":63,"line":92},[61,376,180],{"class":123},[61,378,183],{"class":78},[61,380,186],{"class":123},[61,382,383],{"class":189}," 1\n",[61,385,386],{"class":63,"line":177},[61,387,174],{"emptyLinePlaceholder":173},[61,389,390,392,394,397],{"class":63,"line":193},[61,391,196],{"class":123},[61,393,153],{"class":78},[61,395,396],{"class":189},"true",[61,398,145],{"class":78},[61,400,401,404],{"class":63,"line":225},[61,402,403],{"class":123},"    try",[61,405,406],{"class":78}," {\n",[61,408,409,412,415,418],{"class":63,"line":234},[61,410,411],{"class":123},"      await",[61,413,414],{"class":74}," saveSlug",[61,416,417],{"class":78},"(slug)  ",[61,419,420],{"class":67},"\u002F\u002F INSERT with unique constraint\n",[61,422,423,426],{"class":63,"line":240},[61,424,425],{"class":123},"      return",[61,427,428],{"class":78}," slug\n",[61,430,431,434,437],{"class":63,"line":258},[61,432,433],{"class":78},"    } ",[61,435,436],{"class":123},"catch",[61,438,439],{"class":78}," (error) {\n",[61,441,442,445,448,451,454,457,460],{"class":63,"line":264},[61,443,444],{"class":123},"      if",[61,446,447],{"class":78}," (error.code ",[61,449,450],{"class":123},"!==",[61,452,453],{"class":82}," 'UNIQUE_CONSTRAINT'",[61,455,456],{"class":78},") ",[61,458,459],{"class":123},"throw",[61,461,462],{"class":78}," error\n",[61,464,465,468],{"class":63,"line":269},[61,466,467],{"class":78},"      counter",[61,469,231],{"class":123},[61,471,472,475,477,479,481,483,485],{"class":63,"line":275},[61,473,474],{"class":78},"      slug ",[61,476,186],{"class":123},[61,478,246],{"class":82},[61,480,136],{"class":78},[61,482,213],{"class":82},[61,484,216],{"class":78},[61,486,255],{"class":82},[61,488,489],{"class":63,"line":280},[61,490,491],{"class":78},"    }\n",[61,493,494],{"class":63,"line":286},[61,495,237],{"class":78},[61,497,499],{"class":63,"line":498},15,[61,500,261],{"class":78},[14,502,504],{"id":503},"strategy-2-uuid-or-hash-appends","Strategy 2: UUID or Hash Appends",[10,506,507],{},"Append a short unique identifier to guarantee uniqueness without database queries:",[51,509,511],{"className":53,"code":510,"language":55,"meta":56,"style":56},"function uuidSlug(baseSlug) {\n  const id = crypto.randomUUID().slice(0, 8)  \u002F\u002F First 8 chars of UUID v4\n  return `${baseSlug}-${id}`\n}\n\n\u002F\u002F Results:\n\u002F\u002F 'introduction-to-python-a3f1c8e7'\n\u002F\u002F 'introduction-to-python-7b2d9f41'\n",[58,512,513,527,566,581,585,589,593,598],{"__ignoreMap":56},[61,514,515,518,521,523,525],{"class":63,"line":64},[61,516,517],{"class":123},"function",[61,519,520],{"class":74}," uuidSlug",[61,522,79],{"class":78},[61,524,136],{"class":135},[61,526,145],{"class":78},[61,528,529,532,535,538,541,544,547,550,552,555,557,560,563],{"class":63,"line":71},[61,530,531],{"class":123},"  const",[61,533,534],{"class":189}," id",[61,536,537],{"class":123}," =",[61,539,540],{"class":78}," crypto.",[61,542,543],{"class":74},"randomUUID",[61,545,546],{"class":78},"().",[61,548,549],{"class":74},"slice",[61,551,79],{"class":78},[61,553,554],{"class":189},"0",[61,556,139],{"class":78},[61,558,559],{"class":189},"8",[61,561,562],{"class":78},")  ",[61,564,565],{"class":67},"\u002F\u002F First 8 chars of UUID v4\n",[61,567,568,570,572,574,576,579],{"class":63,"line":92},[61,569,243],{"class":123},[61,571,246],{"class":82},[61,573,136],{"class":78},[61,575,213],{"class":82},[61,577,578],{"class":78},"id",[61,580,255],{"class":82},[61,582,583],{"class":63,"line":177},[61,584,261],{"class":78},[61,586,587],{"class":63,"line":193},[61,588,174],{"emptyLinePlaceholder":173},[61,590,591],{"class":63,"line":225},[61,592,272],{"class":67},[61,594,595],{"class":63,"line":234},[61,596,597],{"class":67},"\u002F\u002F 'introduction-to-python-a3f1c8e7'\n",[61,599,600],{"class":63,"line":240},[61,601,602],{"class":67},"\u002F\u002F 'introduction-to-python-7b2d9f41'\n",[10,604,605,295],{},[28,606,294],{},[22,608,609,612,615,618],{},[25,610,611],{},"No database query needed — uniqueness is probabilistically guaranteed",[25,613,614],{},"No race conditions",[25,616,617],{},"No sequential numbering gaps",[25,619,620],{},"Short IDs (8 hex chars = 4 billion combinations) are sufficient for collision avoidance",[10,622,623,295],{},[28,624,311],{},[22,626,627,630,633],{},[25,628,629],{},"URLs are longer and less memorable",[25,631,632],{},"The random suffix carries no meaning",[25,634,635],{},"Aesthetically less clean than numeric suffixes",[10,637,638,641],{},[28,639,640],{},"Collision probability",": For 8 hex characters (32 bits), the birthday problem gives a 50% collision chance at roughly 65,536 slugs with the same base. For most sites, this is more than adequate. If you need stronger guarantees, use 12 characters (48 bits, 50% collision at ~16 million).",[10,643,644],{},"A compromise variant uses a content-based hash instead of random:",[51,646,648],{"className":53,"code":647,"language":55,"meta":56,"style":56},"function hashSlug(baseSlug, content) {\n  const hash = hashString(content).toString(36).slice(0, 6)\n  return `${baseSlug}-${hash}`\n}\n\n\u002F\u002F Same title + same content → same slug (idempotent)\n\u002F\u002F Same title + different content → different slug (disambiguated)\n",[58,649,650,668,708,723,727,731,736],{"__ignoreMap":56},[61,651,652,654,657,659,661,663,666],{"class":63,"line":64},[61,653,517],{"class":123},[61,655,656],{"class":74}," hashSlug",[61,658,79],{"class":78},[61,660,136],{"class":135},[61,662,139],{"class":78},[61,664,665],{"class":135},"content",[61,667,145],{"class":78},[61,669,670,672,675,677,680,683,686,688,691,694,696,698,700,702,705],{"class":63,"line":71},[61,671,531],{"class":123},[61,673,674],{"class":189}," hash",[61,676,537],{"class":123},[61,678,679],{"class":74}," hashString",[61,681,682],{"class":78},"(content).",[61,684,685],{"class":74},"toString",[61,687,79],{"class":78},[61,689,690],{"class":189},"36",[61,692,693],{"class":78},").",[61,695,549],{"class":74},[61,697,79],{"class":78},[61,699,554],{"class":189},[61,701,139],{"class":78},[61,703,704],{"class":189},"6",[61,706,707],{"class":78},")\n",[61,709,710,712,714,716,718,721],{"class":63,"line":92},[61,711,243],{"class":123},[61,713,246],{"class":82},[61,715,136],{"class":78},[61,717,213],{"class":82},[61,719,720],{"class":78},"hash",[61,722,255],{"class":82},[61,724,725],{"class":63,"line":177},[61,726,261],{"class":78},[61,728,729],{"class":63,"line":193},[61,730,174],{"emptyLinePlaceholder":173},[61,732,733],{"class":63,"line":225},[61,734,735],{"class":67},"\u002F\u002F Same title + same content → same slug (idempotent)\n",[61,737,738],{"class":63,"line":234},[61,739,740],{"class":67},"\u002F\u002F Same title + different content → different slug (disambiguated)\n",[10,742,743],{},"This makes the slug deterministic — the same article always gets the same slug, which is useful for content migration and CI\u002FCD pipelines.",[14,745,747],{"id":746},"strategy-3-database-driven-uniqueness","Strategy 3: Database-Driven Uniqueness",[10,749,750],{},"Store slugs in a dedicated table with controlled allocation:",[51,752,756],{"className":753,"code":754,"language":755,"meta":56,"style":56},"language-sql shiki shiki-themes github-light github-dark","CREATE TABLE slugs (\n  slug VARCHAR(255) PRIMARY KEY,\n  entity_type VARCHAR(50) NOT NULL,\n  entity_id BIGINT NOT NULL,\n  created_at TIMESTAMP DEFAULT NOW()\n);\n\nCREATE INDEX idx_slugs_entity ON slugs(entity_type, entity_id);\n","sql",[58,757,758,763,768,773,778,783,788,792],{"__ignoreMap":56},[61,759,760],{"class":63,"line":64},[61,761,762],{},"CREATE TABLE slugs (\n",[61,764,765],{"class":63,"line":71},[61,766,767],{},"  slug VARCHAR(255) PRIMARY KEY,\n",[61,769,770],{"class":63,"line":92},[61,771,772],{},"  entity_type VARCHAR(50) NOT NULL,\n",[61,774,775],{"class":63,"line":177},[61,776,777],{},"  entity_id BIGINT NOT NULL,\n",[61,779,780],{"class":63,"line":193},[61,781,782],{},"  created_at TIMESTAMP DEFAULT NOW()\n",[61,784,785],{"class":63,"line":225},[61,786,787],{},");\n",[61,789,790],{"class":63,"line":234},[61,791,174],{"emptyLinePlaceholder":173},[61,793,794],{"class":63,"line":240},[61,795,796],{},"CREATE INDEX idx_slugs_entity ON slugs(entity_type, entity_id);\n",[51,798,800],{"className":53,"code":799,"language":55,"meta":56,"style":56},"async function allocateSlug(baseSlug, entityType, entityId, pool) {\n  let slug = baseSlug\n  let counter = 1\n\n  while (true) {\n    try {\n      await pool.query(\n        'INSERT INTO slugs (slug, entity_type, entity_id) VALUES ($1, $2, $3)',\n        [slug, entityType, entityId]\n      )\n      return slug\n    } catch (error) {\n      if (error.code !== '23505') throw error  \u002F\u002F unique_violation\n      counter++\n      slug = `${baseSlug}-${counter}`\n    }\n  }\n}\n",[58,801,802,832,842,852,856,866,872,885,893,898,903,909,917,938,944,960,965,970],{"__ignoreMap":56},[61,803,804,806,808,811,813,815,817,820,822,825,827,830],{"class":63,"line":64},[61,805,124],{"class":123},[61,807,127],{"class":123},[61,809,810],{"class":74}," allocateSlug",[61,812,79],{"class":78},[61,814,136],{"class":135},[61,816,139],{"class":78},[61,818,819],{"class":135},"entityType",[61,821,139],{"class":78},[61,823,824],{"class":135},"entityId",[61,826,139],{"class":78},[61,828,829],{"class":135},"pool",[61,831,145],{"class":78},[61,833,834,836,838,840],{"class":63,"line":71},[61,835,180],{"class":123},[61,837,368],{"class":78},[61,839,186],{"class":123},[61,841,168],{"class":78},[61,843,844,846,848,850],{"class":63,"line":92},[61,845,180],{"class":123},[61,847,183],{"class":78},[61,849,186],{"class":123},[61,851,383],{"class":189},[61,853,854],{"class":63,"line":177},[61,855,174],{"emptyLinePlaceholder":173},[61,857,858,860,862,864],{"class":63,"line":193},[61,859,196],{"class":123},[61,861,153],{"class":78},[61,863,396],{"class":189},[61,865,145],{"class":78},[61,867,868,870],{"class":63,"line":225},[61,869,403],{"class":123},[61,871,406],{"class":78},[61,873,874,876,879,882],{"class":63,"line":234},[61,875,411],{"class":123},[61,877,878],{"class":78}," pool.",[61,880,881],{"class":74},"query",[61,883,884],{"class":78},"(\n",[61,886,887,890],{"class":63,"line":240},[61,888,889],{"class":82},"        'INSERT INTO slugs (slug, entity_type, entity_id) VALUES ($1, $2, $3)'",[61,891,892],{"class":78},",\n",[61,894,895],{"class":63,"line":258},[61,896,897],{"class":78},"        [slug, entityType, entityId]\n",[61,899,900],{"class":63,"line":264},[61,901,902],{"class":78},"      )\n",[61,904,905,907],{"class":63,"line":269},[61,906,425],{"class":123},[61,908,428],{"class":78},[61,910,911,913,915],{"class":63,"line":275},[61,912,433],{"class":78},[61,914,436],{"class":123},[61,916,439],{"class":78},[61,918,919,921,923,925,928,930,932,935],{"class":63,"line":280},[61,920,444],{"class":123},[61,922,447],{"class":78},[61,924,450],{"class":123},[61,926,927],{"class":82}," '23505'",[61,929,456],{"class":78},[61,931,459],{"class":123},[61,933,934],{"class":78}," error  ",[61,936,937],{"class":67},"\u002F\u002F unique_violation\n",[61,939,940,942],{"class":63,"line":286},[61,941,467],{"class":78},[61,943,231],{"class":123},[61,945,946,948,950,952,954,956,958],{"class":63,"line":498},[61,947,474],{"class":78},[61,949,186],{"class":123},[61,951,246],{"class":82},[61,953,136],{"class":78},[61,955,213],{"class":82},[61,957,216],{"class":78},[61,959,255],{"class":82},[61,961,963],{"class":63,"line":962},16,[61,964,491],{"class":78},[61,966,968],{"class":63,"line":967},17,[61,969,237],{"class":78},[61,971,973],{"class":63,"line":972},18,[61,974,261],{"class":78},[10,976,977,295],{},[28,978,294],{},[22,980,981,984,987],{},[25,982,983],{},"Central slug registry enables redirects when slugs change",[25,985,986],{},"Supports slug history — old slugs can 301-redirect to current ones",[25,988,989],{},"Entity association enables bulk operations (e.g., redirect all slugs for a deleted article)",[10,991,992,295],{},[28,993,311],{},[22,995,996,999,1002],{},[25,997,998],{},"Additional database table and queries",[25,1000,1001],{},"More complex migration and backup considerations",[25,1003,1004],{},"Overkill for small sites",[14,1006,1008],{"id":1007},"slug-history-and-redirects","Slug History and Redirects",[10,1010,1011],{},"When an article's title changes, its slug should change too — but the old slug must still work. A slug history table solves this:",[51,1013,1015],{"className":753,"code":1014,"language":755,"meta":56,"style":56},"CREATE TABLE slug_history (\n  old_slug VARCHAR(255) PRIMARY KEY,\n  new_slug VARCHAR(255) NOT NULL,\n  redirected_at TIMESTAMP DEFAULT NOW()\n);\n",[58,1016,1017,1022,1027,1032,1037],{"__ignoreMap":56},[61,1018,1019],{"class":63,"line":64},[61,1020,1021],{},"CREATE TABLE slug_history (\n",[61,1023,1024],{"class":63,"line":71},[61,1025,1026],{},"  old_slug VARCHAR(255) PRIMARY KEY,\n",[61,1028,1029],{"class":63,"line":92},[61,1030,1031],{},"  new_slug VARCHAR(255) NOT NULL,\n",[61,1033,1034],{"class":63,"line":177},[61,1035,1036],{},"  redirected_at TIMESTAMP DEFAULT NOW()\n",[61,1038,1039],{"class":63,"line":193},[61,1040,787],{},[51,1042,1044],{"className":53,"code":1043,"language":55,"meta":56,"style":56},"async function updateSlug(entityType, entityId, newSlug, pool) {\n  const oldSlug = await getCurrentSlug(entityType, entityId, pool)\n  if (oldSlug === newSlug) return\n\n  await pool.query('BEGIN')\n  try {\n    await pool.query(\n      'UPDATE slugs SET slug = $1 WHERE entity_type = $2 AND entity_id = $3',\n      [newSlug, entityType, entityId]\n    )\n    await pool.query(\n      'INSERT INTO slug_history (old_slug, new_slug) VALUES ($1, $2)',\n      [oldSlug, newSlug]\n    )\n    await pool.query('COMMIT')\n  } catch (error) {\n    await pool.query('ROLLBACK')\n    throw error\n  }\n}\n",[58,1045,1046,1074,1092,1108,1112,1128,1135,1146,1153,1158,1163,1173,1180,1185,1189,1204,1213,1228,1235,1240],{"__ignoreMap":56},[61,1047,1048,1050,1052,1055,1057,1059,1061,1063,1065,1068,1070,1072],{"class":63,"line":64},[61,1049,124],{"class":123},[61,1051,127],{"class":123},[61,1053,1054],{"class":74}," updateSlug",[61,1056,79],{"class":78},[61,1058,819],{"class":135},[61,1060,139],{"class":78},[61,1062,824],{"class":135},[61,1064,139],{"class":78},[61,1066,1067],{"class":135},"newSlug",[61,1069,139],{"class":78},[61,1071,829],{"class":135},[61,1073,145],{"class":78},[61,1075,1076,1078,1081,1083,1086,1089],{"class":63,"line":71},[61,1077,531],{"class":123},[61,1079,1080],{"class":189}," oldSlug",[61,1082,537],{"class":123},[61,1084,1085],{"class":123}," await",[61,1087,1088],{"class":74}," getCurrentSlug",[61,1090,1091],{"class":78},"(entityType, entityId, pool)\n",[61,1093,1094,1096,1099,1102,1105],{"class":63,"line":92},[61,1095,150],{"class":123},[61,1097,1098],{"class":78}," (oldSlug ",[61,1100,1101],{"class":123},"===",[61,1103,1104],{"class":78}," newSlug) ",[61,1106,1107],{"class":123},"return\n",[61,1109,1110],{"class":63,"line":177},[61,1111,174],{"emptyLinePlaceholder":173},[61,1113,1114,1117,1119,1121,1123,1126],{"class":63,"line":193},[61,1115,1116],{"class":123},"  await",[61,1118,878],{"class":78},[61,1120,881],{"class":74},[61,1122,79],{"class":78},[61,1124,1125],{"class":82},"'BEGIN'",[61,1127,707],{"class":78},[61,1129,1130,1133],{"class":63,"line":225},[61,1131,1132],{"class":123},"  try",[61,1134,406],{"class":78},[61,1136,1137,1140,1142,1144],{"class":63,"line":234},[61,1138,1139],{"class":123},"    await",[61,1141,878],{"class":78},[61,1143,881],{"class":74},[61,1145,884],{"class":78},[61,1147,1148,1151],{"class":63,"line":240},[61,1149,1150],{"class":82},"      'UPDATE slugs SET slug = $1 WHERE entity_type = $2 AND entity_id = $3'",[61,1152,892],{"class":78},[61,1154,1155],{"class":63,"line":258},[61,1156,1157],{"class":78},"      [newSlug, entityType, entityId]\n",[61,1159,1160],{"class":63,"line":264},[61,1161,1162],{"class":78},"    )\n",[61,1164,1165,1167,1169,1171],{"class":63,"line":269},[61,1166,1139],{"class":123},[61,1168,878],{"class":78},[61,1170,881],{"class":74},[61,1172,884],{"class":78},[61,1174,1175,1178],{"class":63,"line":275},[61,1176,1177],{"class":82},"      'INSERT INTO slug_history (old_slug, new_slug) VALUES ($1, $2)'",[61,1179,892],{"class":78},[61,1181,1182],{"class":63,"line":280},[61,1183,1184],{"class":78},"      [oldSlug, newSlug]\n",[61,1186,1187],{"class":63,"line":286},[61,1188,1162],{"class":78},[61,1190,1191,1193,1195,1197,1199,1202],{"class":63,"line":498},[61,1192,1139],{"class":123},[61,1194,878],{"class":78},[61,1196,881],{"class":74},[61,1198,79],{"class":78},[61,1200,1201],{"class":82},"'COMMIT'",[61,1203,707],{"class":78},[61,1205,1206,1209,1211],{"class":63,"line":962},[61,1207,1208],{"class":78},"  } ",[61,1210,436],{"class":123},[61,1212,439],{"class":78},[61,1214,1215,1217,1219,1221,1223,1226],{"class":63,"line":967},[61,1216,1139],{"class":123},[61,1218,878],{"class":78},[61,1220,881],{"class":74},[61,1222,79],{"class":78},[61,1224,1225],{"class":82},"'ROLLBACK'",[61,1227,707],{"class":78},[61,1229,1230,1233],{"class":63,"line":972},[61,1231,1232],{"class":123},"    throw",[61,1234,462],{"class":78},[61,1236,1238],{"class":63,"line":1237},19,[61,1239,237],{"class":78},[61,1241,1243],{"class":63,"line":1242},20,[61,1244,261],{"class":78},[10,1246,1247,1248,1251],{},"When a request comes in for the old slug, check ",[58,1249,1250],{},"slug_history"," and respond with a 301 redirect to the new one. This preserves SEO equity — search engines transfer link authority through 301s.",[14,1253,1255],{"id":1254},"choosing-a-strategy","Choosing a Strategy",[1257,1258,1259,1272],"table",{},[1260,1261,1262],"thead",{},[1263,1264,1265,1269],"tr",{},[1266,1267,1268],"th",{},"Site Size",[1266,1270,1271],{},"Recommended Strategy",[1273,1274,1275,1284,1292,1300],"tbody",{},[1263,1276,1277,1281],{},[1278,1279,1280],"td",{},"Blog (\u003C 100 posts)",[1278,1282,1283],{},"Numeric suffixes",[1263,1285,1286,1289],{},[1278,1287,1288],{},"Medium (\u003C 10K pages)",[1278,1290,1291],{},"Hash appends",[1263,1293,1294,1297],{},[1278,1295,1296],{},"Large (10K+ pages)",[1278,1298,1299],{},"Database-driven with history",[1263,1301,1302,1304],{},[1278,1303,48],{},[1278,1305,1306],{},"UUID appends (concurrency-safe)",[10,1308,1309,1310,1314],{},"Generate unique slugs instantly at ",[1311,1312,1313],"a",{"href":1313},"\u002Ftools\u002Fslug-generator",", which handles collision detection and supports numeric suffix, hash, and custom suffix modes for any content management workflow.",[1316,1317,1318],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":56,"searchDepth":71,"depth":71,"links":1320},[1321,1322,1323,1324,1325,1326],{"id":16,"depth":71,"text":17},{"id":109,"depth":71,"text":110},{"id":503,"depth":71,"text":504},{"id":746,"depth":71,"text":747},{"id":1007,"depth":71,"text":1008},{"id":1254,"depth":71,"text":1255},"2026-05-28","Numeric suffixes, UUID appends, and database-driven approaches for unique URL slugs.","md",{},"\u002Fguides\u002Fslug-uniqueness-strategies",{"title":5,"description":1328},"guides\u002Fslug-uniqueness-strategies","kE4TldiquA5y1OiLX29RfgGXiGmPMlTNitOkUws8PFM",1780401336828]