[{"data":1,"prerenderedAt":2635},["ShallowReactive",2],{"header-latest":3,"snippets-list":1372,"snippet-detail-vue-use-clipboard-copy-en":2242},[4],{"id":5,"title":6,"body":7,"date":1346,"description":1347,"extension":1348,"faq":1349,"image":1359,"lang":1360,"meta":1361,"navigation":108,"path":1362,"published":108,"readTime":215,"seo":1363,"stem":1364,"tags":1365,"updated":1346,"__hash__":1371},"blog\u002Fblog\u002Fen\u002Ftelegram-web-apps-sells.md","Telegram Web Apps (TWA): How to Launch a Full SaaS Inside Telegram",{"type":8,"value":9,"toc":1335},"minimark",[10,19,22,25,30,33,44,52,63,70,73,856,860,863,868,875,923,927,930,1215,1219,1229,1232,1270,1283,1291,1295,1315,1318,1331],[11,12,13,14,18],"p",{},"Telegram has transcended its origins as a privacy-focused messaging client to become a powerful, cross-platform runtime environment. The introduction of ",[15,16,17],"strong",{},"Telegram Web Apps (TWA)",", officially known as Telegram Mini Apps, allows developers to build highly interactive single-page applications (SPAs) that load directly inside the Telegram application shell.",[11,20,21],{},"This provides startups and enterprises with immediate, frictionless access to over 900 million active users. By embedding your B2B SaaS, CRM interface, or utility tool inside a Telegram bot, you remove the classic conversion hurdles of app store downloads, desktop logins, and sign-up flows.",[11,23,24],{},"In this developer guide, we will analyze the technical architecture of a TWA, implement a secure hash verification check on the backend, and look at frontend integration patterns.",[26,27,29],"h2",{"id":28},"the-telegram-mini-app-ecosystem-architecture","The Telegram Mini App Ecosystem Architecture",[11,31,32],{},"Unlike traditional web applications, a TWA has a dual parent relationship. The frontend runs in a sandboxed browser component (WebView) controlled by the Telegram client app, while the backend speaks both to the Telegram Bot API and your application database.",[34,35,41],"pre",{"className":36,"code":38,"language":39,"meta":40},[37],"language-text","+-----------------------------------------------------------+\n|                      Telegram Client                      |\n|  +-----------------------------------------------------+  |\n|  |                Mini App (WebView UI)                |  |\n|  |     (Uses window.Telegram.WebApp SDK for bridge)    |  |\n|  +--------------------------+--------------------------+  |\n+-----------------------------|-----------------------------+\n       | (initData Query)     | (Secure HTTPS Request)\n       v                      v\n+--------------+       +------------------------------------+\n| Telegram API |       |            Your Backend            |\n|  (Webhooks)  |       |  (Validates initData via HMAC-256) |\n+------+-------+       +-----------------+------------------+\n       |                                 |\n       +----------------> [DB Sync] \u003C----+\n","text","",[42,43,38],"code",{"__ignoreMap":40},[26,45,47,48,51],{"id":46},"_1-security-first-validating-the-initdata-payload","1. Security First: Validating the ",[42,49,50],{},"initData"," Payload",[11,53,54,55,58,59,62],{},"When a user opens your Mini App, Telegram appends a parameter called ",[42,56,57],{},"tgWebAppData"," (or raw search parameters) containing user profiles, launch contexts, and a security ",[42,60,61],{},"hash",".",[11,64,65,66,69],{},"To prevent users from modifying their user IDs or mocking paid subscription privileges, you ",[15,67,68],{},"must validate this hash on your backend"," using your Telegram Bot Token as the HMAC key.",[11,71,72],{},"Here is the cryptographic validation implementation using Node.js:",[34,74,78],{"className":75,"code":76,"language":77,"meta":40,"style":40},"language-typescript shiki shiki-themes github-dark","import crypto from 'crypto';\n\ninterface TelegramUserData {\n  id: number;\n  first_name: string;\n  last_name?: string;\n  username?: string;\n  language_code?: string;\n  is_premium?: boolean;\n}\n\ninterface ValidationResult {\n  isValid: boolean;\n  user?: TelegramUserData;\n}\n\nexport function verifyTelegramInitData(rawQueryString: string, botToken: string): ValidationResult {\n  const urlParams = new URLSearchParams(rawQueryString);\n  const hash = urlParams.get('hash');\n  \n  if (!hash) {\n    return { isValid: false };\n  }\n\n  \u002F\u002F 1. Sort all incoming parameters alphabetically, excluding the hash itself\n  const keys = Array.from(urlParams.keys()).filter(key => key !== 'hash').sort();\n  \n  \u002F\u002F 2. Re-create the verification data-check string\n  const dataCheckString = keys\n    .map(key => `${key}=${urlParams.get(key)}`)\n    .join('\\n');\n\n  \u002F\u002F 3. Generate the secret cryptographic key\n  \u002F\u002F We use the constant string \"WebAppData\" to sign the bot token first\n  const secretKey = crypto\n    .createHmac('sha256', 'WebAppData')\n    .update(botToken)\n    .digest();\n\n  \u002F\u002F 4. Calculate the expected hash of the sorted string\n  const computedHash = crypto\n    .createHmac('sha256', secretKey)\n    .update(dataCheckString)\n    .digest('hex');\n\n  \u002F\u002F 5. Compare computed signature with the signature sent by the client\n  const isValid = computedHash === hash;\n\n  if (!isValid) {\n    return { isValid: false };\n  }\n\n  \u002F\u002F Parse user data object if validation succeeded\n  try {\n    const userRaw = urlParams.get('user');\n    const user: TelegramUserData = userRaw ? JSON.parse(userRaw) : undefined;\n    return { isValid: true, user };\n  } catch (error) {\n    return { isValid: true };\n  }\n}\n","typescript",[42,79,80,103,110,123,139,152,165,177,189,202,208,213,223,235,247,252,257,298,319,343,349,364,379,385,390,397,450,455,461,474,516,536,541,547,553,566,586,597,607,612,618,630,644,654,668,673,679,698,703,715,726,731,736,742,750,772,810,823,835,846,851],{"__ignoreMap":40},[81,82,85,89,93,96,100],"span",{"class":83,"line":84},"line",1,[81,86,88],{"class":87},"snl16","import",[81,90,92],{"class":91},"s95oV"," crypto ",[81,94,95],{"class":87},"from",[81,97,99],{"class":98},"sU2Wk"," 'crypto'",[81,101,102],{"class":91},";\n",[81,104,106],{"class":83,"line":105},2,[81,107,109],{"emptyLinePlaceholder":108},true,"\n",[81,111,113,116,120],{"class":83,"line":112},3,[81,114,115],{"class":87},"interface",[81,117,119],{"class":118},"svObZ"," TelegramUserData",[81,121,122],{"class":91}," {\n",[81,124,126,130,133,137],{"class":83,"line":125},4,[81,127,129],{"class":128},"s9osk","  id",[81,131,132],{"class":87},":",[81,134,136],{"class":135},"sDLfK"," number",[81,138,102],{"class":91},[81,140,142,145,147,150],{"class":83,"line":141},5,[81,143,144],{"class":128},"  first_name",[81,146,132],{"class":87},[81,148,149],{"class":135}," string",[81,151,102],{"class":91},[81,153,155,158,161,163],{"class":83,"line":154},6,[81,156,157],{"class":128},"  last_name",[81,159,160],{"class":87},"?:",[81,162,149],{"class":135},[81,164,102],{"class":91},[81,166,168,171,173,175],{"class":83,"line":167},7,[81,169,170],{"class":128},"  username",[81,172,160],{"class":87},[81,174,149],{"class":135},[81,176,102],{"class":91},[81,178,180,183,185,187],{"class":83,"line":179},8,[81,181,182],{"class":128},"  language_code",[81,184,160],{"class":87},[81,186,149],{"class":135},[81,188,102],{"class":91},[81,190,192,195,197,200],{"class":83,"line":191},9,[81,193,194],{"class":128},"  is_premium",[81,196,160],{"class":87},[81,198,199],{"class":135}," boolean",[81,201,102],{"class":91},[81,203,205],{"class":83,"line":204},10,[81,206,207],{"class":91},"}\n",[81,209,211],{"class":83,"line":210},11,[81,212,109],{"emptyLinePlaceholder":108},[81,214,216,218,221],{"class":83,"line":215},12,[81,217,115],{"class":87},[81,219,220],{"class":118}," ValidationResult",[81,222,122],{"class":91},[81,224,226,229,231,233],{"class":83,"line":225},13,[81,227,228],{"class":128},"  isValid",[81,230,132],{"class":87},[81,232,199],{"class":135},[81,234,102],{"class":91},[81,236,238,241,243,245],{"class":83,"line":237},14,[81,239,240],{"class":128},"  user",[81,242,160],{"class":87},[81,244,119],{"class":118},[81,246,102],{"class":91},[81,248,250],{"class":83,"line":249},15,[81,251,207],{"class":91},[81,253,255],{"class":83,"line":254},16,[81,256,109],{"emptyLinePlaceholder":108},[81,258,260,263,266,269,272,275,277,279,282,285,287,289,292,294,296],{"class":83,"line":259},17,[81,261,262],{"class":87},"export",[81,264,265],{"class":87}," function",[81,267,268],{"class":118}," verifyTelegramInitData",[81,270,271],{"class":91},"(",[81,273,274],{"class":128},"rawQueryString",[81,276,132],{"class":87},[81,278,149],{"class":135},[81,280,281],{"class":91},", ",[81,283,284],{"class":128},"botToken",[81,286,132],{"class":87},[81,288,149],{"class":135},[81,290,291],{"class":91},")",[81,293,132],{"class":87},[81,295,220],{"class":118},[81,297,122],{"class":91},[81,299,301,304,307,310,313,316],{"class":83,"line":300},18,[81,302,303],{"class":87},"  const",[81,305,306],{"class":135}," urlParams",[81,308,309],{"class":87}," =",[81,311,312],{"class":87}," new",[81,314,315],{"class":118}," URLSearchParams",[81,317,318],{"class":91},"(rawQueryString);\n",[81,320,322,324,327,329,332,335,337,340],{"class":83,"line":321},19,[81,323,303],{"class":87},[81,325,326],{"class":135}," hash",[81,328,309],{"class":87},[81,330,331],{"class":91}," urlParams.",[81,333,334],{"class":118},"get",[81,336,271],{"class":91},[81,338,339],{"class":98},"'hash'",[81,341,342],{"class":91},");\n",[81,344,346],{"class":83,"line":345},20,[81,347,348],{"class":91},"  \n",[81,350,352,355,358,361],{"class":83,"line":351},21,[81,353,354],{"class":87},"  if",[81,356,357],{"class":91}," (",[81,359,360],{"class":87},"!",[81,362,363],{"class":91},"hash) {\n",[81,365,367,370,373,376],{"class":83,"line":366},22,[81,368,369],{"class":87},"    return",[81,371,372],{"class":91}," { isValid: ",[81,374,375],{"class":135},"false",[81,377,378],{"class":91}," };\n",[81,380,382],{"class":83,"line":381},23,[81,383,384],{"class":91},"  }\n",[81,386,388],{"class":83,"line":387},24,[81,389,109],{"emptyLinePlaceholder":108},[81,391,393],{"class":83,"line":392},25,[81,394,396],{"class":395},"sAwPA","  \u002F\u002F 1. Sort all incoming parameters alphabetically, excluding the hash itself\n",[81,398,400,402,405,407,410,412,415,418,421,424,426,429,432,435,438,441,444,447],{"class":83,"line":399},26,[81,401,303],{"class":87},[81,403,404],{"class":135}," keys",[81,406,309],{"class":87},[81,408,409],{"class":91}," Array.",[81,411,95],{"class":118},[81,413,414],{"class":91},"(urlParams.",[81,416,417],{"class":118},"keys",[81,419,420],{"class":91},"()).",[81,422,423],{"class":118},"filter",[81,425,271],{"class":91},[81,427,428],{"class":128},"key",[81,430,431],{"class":87}," =>",[81,433,434],{"class":91}," key ",[81,436,437],{"class":87},"!==",[81,439,440],{"class":98}," 'hash'",[81,442,443],{"class":91},").",[81,445,446],{"class":118},"sort",[81,448,449],{"class":91},"();\n",[81,451,453],{"class":83,"line":452},27,[81,454,348],{"class":91},[81,456,458],{"class":83,"line":457},28,[81,459,460],{"class":395},"  \u002F\u002F 2. Re-create the verification data-check string\n",[81,462,464,466,469,471],{"class":83,"line":463},29,[81,465,303],{"class":87},[81,467,468],{"class":135}," dataCheckString",[81,470,309],{"class":87},[81,472,473],{"class":91}," keys\n",[81,475,477,480,483,485,487,489,492,494,497,500,502,504,506,508,510,513],{"class":83,"line":476},30,[81,478,479],{"class":91},"    .",[81,481,482],{"class":118},"map",[81,484,271],{"class":91},[81,486,428],{"class":128},[81,488,431],{"class":87},[81,490,491],{"class":98}," `${",[81,493,428],{"class":91},[81,495,496],{"class":98},"}=${",[81,498,499],{"class":91},"urlParams",[81,501,62],{"class":98},[81,503,334],{"class":118},[81,505,271],{"class":98},[81,507,428],{"class":91},[81,509,291],{"class":98},[81,511,512],{"class":98},"}`",[81,514,515],{"class":91},")\n",[81,517,519,521,524,526,529,532,534],{"class":83,"line":518},31,[81,520,479],{"class":91},[81,522,523],{"class":118},"join",[81,525,271],{"class":91},[81,527,528],{"class":98},"'",[81,530,531],{"class":135},"\\n",[81,533,528],{"class":98},[81,535,342],{"class":91},[81,537,539],{"class":83,"line":538},32,[81,540,109],{"emptyLinePlaceholder":108},[81,542,544],{"class":83,"line":543},33,[81,545,546],{"class":395},"  \u002F\u002F 3. Generate the secret cryptographic key\n",[81,548,550],{"class":83,"line":549},34,[81,551,552],{"class":395},"  \u002F\u002F We use the constant string \"WebAppData\" to sign the bot token first\n",[81,554,556,558,561,563],{"class":83,"line":555},35,[81,557,303],{"class":87},[81,559,560],{"class":135}," secretKey",[81,562,309],{"class":87},[81,564,565],{"class":91}," crypto\n",[81,567,569,571,574,576,579,581,584],{"class":83,"line":568},36,[81,570,479],{"class":91},[81,572,573],{"class":118},"createHmac",[81,575,271],{"class":91},[81,577,578],{"class":98},"'sha256'",[81,580,281],{"class":91},[81,582,583],{"class":98},"'WebAppData'",[81,585,515],{"class":91},[81,587,589,591,594],{"class":83,"line":588},37,[81,590,479],{"class":91},[81,592,593],{"class":118},"update",[81,595,596],{"class":91},"(botToken)\n",[81,598,600,602,605],{"class":83,"line":599},38,[81,601,479],{"class":91},[81,603,604],{"class":118},"digest",[81,606,449],{"class":91},[81,608,610],{"class":83,"line":609},39,[81,611,109],{"emptyLinePlaceholder":108},[81,613,615],{"class":83,"line":614},40,[81,616,617],{"class":395},"  \u002F\u002F 4. Calculate the expected hash of the sorted string\n",[81,619,621,623,626,628],{"class":83,"line":620},41,[81,622,303],{"class":87},[81,624,625],{"class":135}," computedHash",[81,627,309],{"class":87},[81,629,565],{"class":91},[81,631,633,635,637,639,641],{"class":83,"line":632},42,[81,634,479],{"class":91},[81,636,573],{"class":118},[81,638,271],{"class":91},[81,640,578],{"class":98},[81,642,643],{"class":91},", secretKey)\n",[81,645,647,649,651],{"class":83,"line":646},43,[81,648,479],{"class":91},[81,650,593],{"class":118},[81,652,653],{"class":91},"(dataCheckString)\n",[81,655,657,659,661,663,666],{"class":83,"line":656},44,[81,658,479],{"class":91},[81,660,604],{"class":118},[81,662,271],{"class":91},[81,664,665],{"class":98},"'hex'",[81,667,342],{"class":91},[81,669,671],{"class":83,"line":670},45,[81,672,109],{"emptyLinePlaceholder":108},[81,674,676],{"class":83,"line":675},46,[81,677,678],{"class":395},"  \u002F\u002F 5. Compare computed signature with the signature sent by the client\n",[81,680,682,684,687,689,692,695],{"class":83,"line":681},47,[81,683,303],{"class":87},[81,685,686],{"class":135}," isValid",[81,688,309],{"class":87},[81,690,691],{"class":91}," computedHash ",[81,693,694],{"class":87},"===",[81,696,697],{"class":91}," hash;\n",[81,699,701],{"class":83,"line":700},48,[81,702,109],{"emptyLinePlaceholder":108},[81,704,706,708,710,712],{"class":83,"line":705},49,[81,707,354],{"class":87},[81,709,357],{"class":91},[81,711,360],{"class":87},[81,713,714],{"class":91},"isValid) {\n",[81,716,718,720,722,724],{"class":83,"line":717},50,[81,719,369],{"class":87},[81,721,372],{"class":91},[81,723,375],{"class":135},[81,725,378],{"class":91},[81,727,729],{"class":83,"line":728},51,[81,730,384],{"class":91},[81,732,734],{"class":83,"line":733},52,[81,735,109],{"emptyLinePlaceholder":108},[81,737,739],{"class":83,"line":738},53,[81,740,741],{"class":395},"  \u002F\u002F Parse user data object if validation succeeded\n",[81,743,745,748],{"class":83,"line":744},54,[81,746,747],{"class":87},"  try",[81,749,122],{"class":91},[81,751,753,756,759,761,763,765,767,770],{"class":83,"line":752},55,[81,754,755],{"class":87},"    const",[81,757,758],{"class":135}," userRaw",[81,760,309],{"class":87},[81,762,331],{"class":91},[81,764,334],{"class":118},[81,766,271],{"class":91},[81,768,769],{"class":98},"'user'",[81,771,342],{"class":91},[81,773,775,777,780,782,784,786,789,792,795,797,800,803,805,808],{"class":83,"line":774},56,[81,776,755],{"class":87},[81,778,779],{"class":135}," user",[81,781,132],{"class":87},[81,783,119],{"class":118},[81,785,309],{"class":87},[81,787,788],{"class":91}," userRaw ",[81,790,791],{"class":87},"?",[81,793,794],{"class":135}," JSON",[81,796,62],{"class":91},[81,798,799],{"class":118},"parse",[81,801,802],{"class":91},"(userRaw) ",[81,804,132],{"class":87},[81,806,807],{"class":135}," undefined",[81,809,102],{"class":91},[81,811,813,815,817,820],{"class":83,"line":812},57,[81,814,369],{"class":87},[81,816,372],{"class":91},[81,818,819],{"class":135},"true",[81,821,822],{"class":91},", user };\n",[81,824,826,829,832],{"class":83,"line":825},58,[81,827,828],{"class":91},"  } ",[81,830,831],{"class":87},"catch",[81,833,834],{"class":91}," (error) {\n",[81,836,838,840,842,844],{"class":83,"line":837},59,[81,839,369],{"class":87},[81,841,372],{"class":91},[81,843,819],{"class":135},[81,845,378],{"class":91},[81,847,849],{"class":83,"line":848},60,[81,850,384],{"class":91},[81,852,854],{"class":83,"line":853},61,[81,855,207],{"class":91},[26,857,859],{"id":858},"_2-frontend-integration-theme-synchronization","2. Frontend Integration & Theme Synchronization",[11,861,862],{},"To deliver a premium UI\u002FUX, your Mini App should visually blend with the Telegram client’s dark\u002Flight settings. You can access the styles and control client-side behaviors using the official Telegram WebApp JS library.",[864,865,867],"h3",{"id":866},"step-1-include-the-script-in-nuxt-3-html","Step 1: Include the Script in Nuxt 3 \u002F HTML",[11,869,870,871,874],{},"Add the official script to your page header or use the ",[42,872,873],{},"useHead"," composable in Nuxt:",[34,876,878],{"className":75,"code":877,"language":77,"meta":40,"style":40},"\u002F\u002F app.vue or page layout\nuseHead({\n  script: [\n    { src: 'https:\u002F\u002Ftelegram.org\u002Fjs\u002Ftelegram-web-app.js', defer: true }\n  ]\n})\n",[42,879,880,885,892,897,913,918],{"__ignoreMap":40},[81,881,882],{"class":83,"line":84},[81,883,884],{"class":395},"\u002F\u002F app.vue or page layout\n",[81,886,887,889],{"class":83,"line":105},[81,888,873],{"class":118},[81,890,891],{"class":91},"({\n",[81,893,894],{"class":83,"line":112},[81,895,896],{"class":91},"  script: [\n",[81,898,899,902,905,908,910],{"class":83,"line":125},[81,900,901],{"class":91},"    { src: ",[81,903,904],{"class":98},"'https:\u002F\u002Ftelegram.org\u002Fjs\u002Ftelegram-web-app.js'",[81,906,907],{"class":91},", defer: ",[81,909,819],{"class":135},[81,911,912],{"class":91}," }\n",[81,914,915],{"class":83,"line":141},[81,916,917],{"class":91},"  ]\n",[81,919,920],{"class":83,"line":154},[81,921,922],{"class":91},"})\n",[864,924,926],{"id":925},"step-2-access-the-webapp-bridge-in-vue","Step 2: Access the WebApp Bridge in Vue",[11,928,929],{},"Create a Vue composable to access and synchronize Telegram styles:",[34,931,933],{"className":75,"code":932,"language":77,"meta":40,"style":40},"\u002F\u002F composables\u002FuseTelegram.ts\nimport { ref, onMounted } from 'vue';\n\nexport function useTelegram() {\n  const isReady = ref(false);\n  const user = ref\u003Cany>(null);\n\n  onMounted(() => {\n    const tg = (window as any).Telegram?.WebApp;\n    if (tg) {\n      tg.ready();\n      tg.expand(); \u002F\u002F Request the container to fill maximum vertical space\n      \n      user.value = tg.initDataUnsafe?.user;\n      isReady.value = true;\n\n      \u002F\u002F Apply Telegram theme colors to CSS custom properties\n      const root = document.documentElement;\n      root.style.setProperty('--color-tg-bg', tg.themeParams.bg_color);\n      root.style.setProperty('--color-tg-text', tg.themeParams.text_color);\n      root.style.setProperty('--color-tg-button', tg.themeParams.button_color);\n      root.style.setProperty('--color-tg-button-text', tg.themeParams.button_text_color);\n    }\n  });\n\n  return { isReady, user };\n}\n",[42,934,935,940,954,958,970,988,1012,1016,1029,1050,1058,1068,1081,1086,1097,1109,1113,1118,1131,1147,1161,1175,1189,1194,1199,1203,1211],{"__ignoreMap":40},[81,936,937],{"class":83,"line":84},[81,938,939],{"class":395},"\u002F\u002F composables\u002FuseTelegram.ts\n",[81,941,942,944,947,949,952],{"class":83,"line":105},[81,943,88],{"class":87},[81,945,946],{"class":91}," { ref, onMounted } ",[81,948,95],{"class":87},[81,950,951],{"class":98}," 'vue'",[81,953,102],{"class":91},[81,955,956],{"class":83,"line":112},[81,957,109],{"emptyLinePlaceholder":108},[81,959,960,962,964,967],{"class":83,"line":125},[81,961,262],{"class":87},[81,963,265],{"class":87},[81,965,966],{"class":118}," useTelegram",[81,968,969],{"class":91},"() {\n",[81,971,972,974,977,979,982,984,986],{"class":83,"line":141},[81,973,303],{"class":87},[81,975,976],{"class":135}," isReady",[81,978,309],{"class":87},[81,980,981],{"class":118}," ref",[81,983,271],{"class":91},[81,985,375],{"class":135},[81,987,342],{"class":91},[81,989,990,992,994,996,998,1001,1004,1007,1010],{"class":83,"line":154},[81,991,303],{"class":87},[81,993,779],{"class":135},[81,995,309],{"class":87},[81,997,981],{"class":118},[81,999,1000],{"class":91},"\u003C",[81,1002,1003],{"class":135},"any",[81,1005,1006],{"class":91},">(",[81,1008,1009],{"class":135},"null",[81,1011,342],{"class":91},[81,1013,1014],{"class":83,"line":167},[81,1015,109],{"emptyLinePlaceholder":108},[81,1017,1018,1021,1024,1027],{"class":83,"line":179},[81,1019,1020],{"class":118},"  onMounted",[81,1022,1023],{"class":91},"(() ",[81,1025,1026],{"class":87},"=>",[81,1028,122],{"class":91},[81,1030,1031,1033,1036,1038,1041,1044,1047],{"class":83,"line":191},[81,1032,755],{"class":87},[81,1034,1035],{"class":135}," tg",[81,1037,309],{"class":87},[81,1039,1040],{"class":91}," (window ",[81,1042,1043],{"class":87},"as",[81,1045,1046],{"class":135}," any",[81,1048,1049],{"class":91},").Telegram?.WebApp;\n",[81,1051,1052,1055],{"class":83,"line":204},[81,1053,1054],{"class":87},"    if",[81,1056,1057],{"class":91}," (tg) {\n",[81,1059,1060,1063,1066],{"class":83,"line":210},[81,1061,1062],{"class":91},"      tg.",[81,1064,1065],{"class":118},"ready",[81,1067,449],{"class":91},[81,1069,1070,1072,1075,1078],{"class":83,"line":215},[81,1071,1062],{"class":91},[81,1073,1074],{"class":118},"expand",[81,1076,1077],{"class":91},"(); ",[81,1079,1080],{"class":395},"\u002F\u002F Request the container to fill maximum vertical space\n",[81,1082,1083],{"class":83,"line":225},[81,1084,1085],{"class":91},"      \n",[81,1087,1088,1091,1094],{"class":83,"line":237},[81,1089,1090],{"class":91},"      user.value ",[81,1092,1093],{"class":87},"=",[81,1095,1096],{"class":91}," tg.initDataUnsafe?.user;\n",[81,1098,1099,1102,1104,1107],{"class":83,"line":249},[81,1100,1101],{"class":91},"      isReady.value ",[81,1103,1093],{"class":87},[81,1105,1106],{"class":135}," true",[81,1108,102],{"class":91},[81,1110,1111],{"class":83,"line":254},[81,1112,109],{"emptyLinePlaceholder":108},[81,1114,1115],{"class":83,"line":259},[81,1116,1117],{"class":395},"      \u002F\u002F Apply Telegram theme colors to CSS custom properties\n",[81,1119,1120,1123,1126,1128],{"class":83,"line":300},[81,1121,1122],{"class":87},"      const",[81,1124,1125],{"class":135}," root",[81,1127,309],{"class":87},[81,1129,1130],{"class":91}," document.documentElement;\n",[81,1132,1133,1136,1139,1141,1144],{"class":83,"line":321},[81,1134,1135],{"class":91},"      root.style.",[81,1137,1138],{"class":118},"setProperty",[81,1140,271],{"class":91},[81,1142,1143],{"class":98},"'--color-tg-bg'",[81,1145,1146],{"class":91},", tg.themeParams.bg_color);\n",[81,1148,1149,1151,1153,1155,1158],{"class":83,"line":345},[81,1150,1135],{"class":91},[81,1152,1138],{"class":118},[81,1154,271],{"class":91},[81,1156,1157],{"class":98},"'--color-tg-text'",[81,1159,1160],{"class":91},", tg.themeParams.text_color);\n",[81,1162,1163,1165,1167,1169,1172],{"class":83,"line":351},[81,1164,1135],{"class":91},[81,1166,1138],{"class":118},[81,1168,271],{"class":91},[81,1170,1171],{"class":98},"'--color-tg-button'",[81,1173,1174],{"class":91},", tg.themeParams.button_color);\n",[81,1176,1177,1179,1181,1183,1186],{"class":83,"line":366},[81,1178,1135],{"class":91},[81,1180,1138],{"class":118},[81,1182,271],{"class":91},[81,1184,1185],{"class":98},"'--color-tg-button-text'",[81,1187,1188],{"class":91},", tg.themeParams.button_text_color);\n",[81,1190,1191],{"class":83,"line":381},[81,1192,1193],{"class":91},"    }\n",[81,1195,1196],{"class":83,"line":387},[81,1197,1198],{"class":91},"  });\n",[81,1200,1201],{"class":83,"line":392},[81,1202,109],{"emptyLinePlaceholder":108},[81,1204,1205,1208],{"class":83,"line":399},[81,1206,1207],{"class":87},"  return",[81,1209,1210],{"class":91}," { isReady, user };\n",[81,1212,1213],{"class":83,"line":452},[81,1214,207],{"class":91},[26,1216,1218],{"id":1217},"_3-monetization-payments-via-telegram-stars","3. Monetization: Payments via Telegram Stars",[11,1220,1221,1222,357,1225,1228],{},"When operating inside Telegram, all digital services or content purchases must comply with Apple App Store and Google Play policies. Telegram enforces this by requiring the use of ",[15,1223,1224],{},"Telegram Stars",[42,1226,1227],{},"XTR",") for digital goods.",[11,1230,1231],{},"The flow for receiving Stars payments:",[1233,1234,1235,1242,1254,1260],"ol",{},[1236,1237,1238,1241],"li",{},[15,1239,1240],{},"Request Invoice",": The TWA requests the backend to generate an invoice.",[1236,1243,1244,1247,1248,1251,1252,62],{},[15,1245,1246],{},"Bot Sends Invoice",": The backend calls Bot API ",[42,1249,1250],{},"sendInvoice"," using the currency ",[42,1253,1227],{},[1236,1255,1256,1259],{},[15,1257,1258],{},"Client Checkout",": The Telegram client opens a native overlay allowing the user to pay using Stars purchased in-app.",[1236,1261,1262,1265,1266,1269],{},[15,1263,1264],{},"Verification",": Telegram sends a webhook ",[42,1267,1268],{},"successful_payment"," to your bot, which credits the user's account in your DB.",[11,1271,1272,1273,1278,1279,62],{},"In my Telegram bot hosting platform, ",[1274,1275,1277],"a",{"href":1276},"\u002Fprojects\u002Ftelego","TeleGo.io",", we provide exactly these billing options, allowing users to spin up their own TWAs and receive Stars instantly. Additionally, we support hybrid human-AI helpdesks, which you can learn to build in my guide on ",[1274,1280,1282],{"href":1281},"\u002Fblog\u002Ftelegram-bot-ai-rag-support","RAG AI Customer Support Bots",[11,1284,1285,1286,1290],{},"For physical goods and consulting services, traditional gateways can be used. Read my ",[1274,1287,1289],{"href":1288},"\u002Fblog\u002Fsaas-stripe-billing-integration","SaaS Stripe Billing Integration guide"," for Express\u002FTypeScript templates.",[26,1292,1294],{"id":1293},"sources-and-documentation","Sources and documentation",[1296,1297,1298,1307],"ul",{},[1236,1299,1300,1306],{},[1274,1301,1305],{"href":1302,"rel":1303},"https:\u002F\u002Fcore.telegram.org\u002Fbots\u002Fwebapps",[1304],"nofollow","Telegram Mini Apps"," — the official WebApp SDK and initData documentation",[1236,1308,1309,1314],{},[1274,1310,1313],{"href":1311,"rel":1312},"https:\u002F\u002Fcore.telegram.org\u002Fbots\u002Fpayments",[1304],"Bot Payments API"," — invoices, Telegram Stars, and payment webhooks",[11,1316,1317],{},"By combining native web frameworks with the Telegram WebApp API, developers can ship complex SaaS platforms and digital products directly into active messaging channels.",[11,1319,1320,1321,1325,1326,1330],{},"If you are planning to build a high-performance Telegram Mini App with secure billing integrations and dynamic frontend design, check out my ",[1274,1322,1324],{"href":1323},"\u002Ftelegram-bots","Telegram Bot Development Service"," or request a ",[1274,1327,1329],{"href":1328},"\u002Fconsultations","Technical Architecture Consultation"," to get a production blueprint.",[1332,1333,1334],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}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);}",{"title":40,"searchDepth":105,"depth":105,"links":1336},[1337,1338,1340,1344,1345],{"id":28,"depth":105,"text":29},{"id":46,"depth":105,"text":1339},"1. Security First: Validating the initData Payload",{"id":858,"depth":105,"text":859,"children":1341},[1342,1343],{"id":866,"depth":112,"text":867},{"id":925,"depth":112,"text":926},{"id":1217,"depth":105,"text":1218},{"id":1293,"depth":105,"text":1294},"2026-07-05","A comprehensive developer's guide to using Telegram Mini Apps as a dynamic web frontend, implementing secure hash validation, and processing in-app purchases.","md",[1350,1353,1356],{"q":1351,"a":1352},"How is a Telegram Mini App different from a regular bot?","A bot communicates through messages and buttons, while a Mini App opens a full web interface inside Telegram: dashboards, catalogs, forms, charts. It's an SPA running in a WebView with its own frontend, and authorization happens automatically through the Telegram account.",{"q":1354,"a":1355},"How do I verify that user data in a Mini App isn't forged?","Through cryptographic initData validation: Telegram signs the user data with an HMAC-SHA256 signature derived from the bot token. The backend must recompute and compare the signature on every request — without it, anyone can impersonate any user.",{"q":1357,"a":1358},"When are Telegram Stars mandatory, and when can I use Stripe?","Digital goods and subscriptions inside a Mini App must be paid with Telegram Stars per Apple and Google policies. Physical goods, services, and consulting can be sold through classic gateways like Stripe.","\u002Fimages\u002Fblog\u002Fblog_telegram_twa.jpg","en",{},"\u002Fblog\u002Fen\u002Ftelegram-web-apps-sells",{"title":6,"description":1347},"blog\u002Fen\u002Ftelegram-web-apps-sells",[1366,1367,1368,1369,1370],"Telegram","Mini Apps","SaaS","Node.js","Frontend","4blcVbDR853MsonsChj72pfs0C4mqm-aL83XkBOZ0io",[1373,1384,1393,1403,1412,1420,1430,1438,1446,1456,1464,1472,1480,1488,1495,1504,1510,1517,1525,1531,1539,1545,1555,1564,1571,1579,1588,1594,1602,1610,1618,1624,1629,1635,1641,1646,1651,1657,1662,1669,1676,1682,1688,1694,1700,1706,1712,1719,1726,1733,1739,1745,1752,1759,1766,1772,1778,1784,1790,1796,1802,1808,1814,1820,1826,1832,1838,1844,1850,1856,1862,1868,1874,1880,1886,1892,1898,1904,1910,1916,1922,1928,1934,1940,1946,1952,1958,1964,1970,1976,1982,1988,1994,2000,2006,2012,2018,2024,2030,2036,2042,2047,2053,2059,2065,2071,2077,2083,2089,2095,2102,2108,2114,2121,2127,2133,2143,2149,2154,2161,2167,2173,2179,2185,2191,2197,2204,2211,2218,2224,2230,2236],{"path":1374,"title":1375,"description":1376,"language":1377,"tags":1378,"date":1383,"lang":1360},"\u002Fsnippets\u002Fen\u002Freact-context-usereducer","React context + useReducer for global state","Replace a state management library with Context and useReducer — a predictable, testable global state pattern without external dependencies.","React.js",[1379,1380,1381,1382],"React","Context","useReducer","State Management","2026-08-07",{"path":1385,"title":1386,"description":1387,"language":1377,"tags":1388,"date":1392,"lang":1360},"\u002Fsnippets\u002Fen\u002Freact-usemediaquery-hook","React useMediaQuery — responsive hook","Track CSS media queries in React components — detect breakpoints, render different UI for mobile\u002Ftablet\u002Fdesktop, and react to orientation changes.",[1379,1389,1390,1391],"Hooks","Responsive","Media Query","2026-08-06",{"path":1394,"title":1395,"description":1396,"language":1397,"tags":1398,"date":1402,"lang":1360},"\u002Fsnippets\u002Fen\u002Ftailwind-animations-keyframes","Tailwind CSS animations — keyframes and transition","Craft custom animations with Tailwind's `animate-*` utilities and `@keyframes` — from micro-interactions to full page transitions — without leaving your config.","Tailwind CSS",[1397,1399,1400,1401],"Animation","Keyframes","UX","2026-08-05",{"path":1404,"title":1405,"description":1406,"language":1397,"tags":1407,"date":1411,"lang":1360},"\u002Fsnippets\u002Fen\u002Ftailwind-custom-theme-variables","Tailwind CSS custom theme with CSS variables","Extend Tailwind's default theme with custom colors, fonts, spacing, and breakpoints — all driven by CSS variables for runtime theme switching.",[1397,1408,1409,1410],"Theme","Customization","Design System","2026-08-04",{"path":1413,"title":1414,"description":1415,"language":1397,"tags":1416,"date":1419,"lang":1360},"\u002Fsnippets\u002Fen\u002Ftailwind-dark-mode-class","Tailwind CSS dark mode with class strategy","Toggle dark mode on demand instead of relying on system preferences — the class strategy lets users switch themes with a button, persisted in localStorage or cookie.",[1397,1417,1408,1418],"Dark Mode","Styling","2026-08-03",{"path":1421,"title":1422,"description":1423,"language":1424,"tags":1425,"date":1429,"lang":1360},"\u002Fsnippets\u002Fen\u002Fprisma-migrations-workflow","Prisma migrations — create, apply, and roll back","Manage database schema changes with Prisma Migrate — create migrations from schema changes, apply them to production, and handle drift.","Prisma",[1424,1426,1427,1428],"ORM","Migrations","Database","2026-08-02",{"path":1431,"title":1432,"description":1433,"language":1424,"tags":1434,"date":1437,"lang":1360},"\u002Fsnippets\u002Fen\u002Fprisma-pagination-cursor-offset","Prisma pagination — cursor and offset","Efficiently paginate large datasets with Prisma — offset pagination for simple lists, cursor-based pagination for infinite scroll and real-time feeds.",[1424,1426,1435,1436],"Pagination","Performance","2026-08-01",{"path":1439,"title":1440,"description":1441,"language":1424,"tags":1442,"date":1445,"lang":1360},"\u002Fsnippets\u002Fen\u002Fprisma-crud-operations","Prisma CRUD — create, read, update, delete","The four basic data operations with Prisma Client — create a user, find by email, update profile, and delete an account — all fully typed.",[1424,1426,1443,1444],"CRUD","TypeScript","2026-07-31",{"path":1447,"title":1448,"description":1449,"language":1450,"tags":1451,"date":1455,"lang":1360},"\u002Fsnippets\u002Fen\u002Fnginx-websocket-proxy","Nginx WebSocket proxy for real-time apps","Proxy WebSocket connections through nginx — required for Socket.io, live chat, and collaborative editing behind a reverse proxy.","Nginx",[1450,1452,1453,1454],"WebSocket","Proxy","Real-time","2026-07-30",{"path":1457,"title":1458,"description":1459,"language":1450,"tags":1460,"date":1463,"lang":1360},"\u002Fsnippets\u002Fen\u002Fnginx-load-balancing-nodejs","Nginx load balancing between Node.js instances","Distribute traffic across multiple Node.js backend instances with round-robin, health checks, and passive failover — no application changes needed.",[1450,1461,1369,1462],"Load Balancing","DevOps","2026-07-29",{"path":1465,"title":1466,"description":1467,"language":1450,"tags":1468,"date":1471,"lang":1360},"\u002Fsnippets\u002Fen\u002Fnginx-static-cache-server","Nginx static file server with caching","Serve static assets (CSS, JS, images) with far-future cache headers, gzip compression, and directory index — no app server needed.",[1450,1469,1470,1436],"Static Files","Caching","2026-07-28",{"path":1473,"title":1474,"description":1475,"language":1476,"tags":1477,"date":1479,"lang":1360},"\u002Fsnippets\u002Fen\u002Fgithub-actions-scheduled-cron","GitHub Actions — scheduled tasks with cron","Run workflows on a schedule — daily backups, weekly dependency updates, or hourly health checks — using cron syntax.","GitHub Actions",[1476,1478,1462],"Automation","2026-07-27",{"path":1481,"title":1482,"description":1483,"language":1476,"tags":1484,"date":1487,"lang":1360},"\u002Fsnippets\u002Fen\u002Fgithub-actions-docker-build-push","GitHub Actions — build and push Docker image to registry","Automatically build and push a Docker image to Docker Hub or GitHub Container Registry on every push to main — with layer caching.",[1476,1485,1486,1462],"Docker","CI\u002FCD","2026-07-26",{"path":1489,"title":1490,"description":1491,"language":1476,"tags":1492,"date":1494,"lang":1360},"\u002Fsnippets\u002Fen\u002Fgithub-actions-ci-pr","GitHub Actions — CI pipeline for PRs","Run linting, type-checking, and tests on every pull request before merge — fail fast with clear annotations on the PR.",[1476,1486,1493,1462],"Testing","2026-07-25",{"path":1496,"title":1497,"description":1498,"language":1499,"tags":1500,"date":1503,"lang":1360},"\u002Fsnippets\u002Fen\u002Fgit-log-filter-format","Git log — filtering and formatting commit history","Search, filter, and format your Git log — find commits by author, date, file, or content with a readable one-line output.","Git",[1499,1501,1502],"Workflow","Debugging","2026-07-24",{"path":1505,"title":1506,"description":1507,"language":1499,"tags":1508,"date":1509,"lang":1360},"\u002Fsnippets\u002Fen\u002Fgit-cherry-pick-bisect","Git cherry-pick and bisect — target specific commits","Cherry-pick applies a specific commit from any branch to your current branch. Bisect performs a binary search through history to find the commit that introduced a bug.",[1499,1501,1502],"2026-07-23",{"path":1511,"title":1512,"description":1513,"language":1499,"tags":1514,"date":1516,"lang":1360},"\u002Fsnippets\u002Fen\u002Fgit-stash-work-in-progress","Git stash — save and restore work in progress","Temporarily save uncommitted changes with git stash — switch branches, pull updates, and restore your work later without losing anything.",[1499,1501,1515],"Version Control","2026-07-22",{"path":1518,"title":1519,"description":1520,"language":1485,"tags":1521,"date":1524,"lang":1360},"\u002Fsnippets\u002Fen\u002Fdocker-security-nonroot","Docker security — non-root user and read-only filesystem","Harden your containers by running as a non-root user with a read-only root filesystem and dropped kernel capabilities.",[1485,1522,1462,1523],"Security","Production","2026-07-21",{"path":1526,"title":1527,"description":1528,"language":1485,"tags":1529,"date":1530,"lang":1360},"\u002Fsnippets\u002Fen\u002Fdocker-multistage-build-nodejs","Docker multi-stage builds for Node.js","A production Dockerfile with multi-stage builds — separate deps, build, and runtime stages for a minimal final image.",[1485,1369,1462,1523],"2026-07-20",{"path":1532,"title":1533,"description":1534,"language":1397,"tags":1535,"date":1538,"lang":1360},"\u002Fsnippets\u002Fen\u002Ftailwind-responsive-card-grid","Tailwind CSS responsive card grid","A responsive card grid with Tailwind CSS that adapts from 1 column on mobile to 4 columns on desktop — no media queries needed.",[1397,1536,1390,1537],"CSS","Grid","2026-07-19",{"path":1540,"title":1541,"description":1542,"language":1499,"tags":1543,"date":1544,"lang":1360},"\u002Fsnippets\u002Fen\u002Fgit-squash-rebase-workflow","Git squash commits and interactive rebase","Clean up your Git history with squash, fixup, and interactive rebase — keep a readable commit log before merging.",[1499,1501,1515],"2026-07-18",{"path":1546,"title":1547,"description":1548,"language":1549,"tags":1550,"date":1554,"lang":1360},"\u002Fsnippets\u002Fen\u002Fnextjs-middleware-auth","Next.js App Router middleware — authentication and redirects","Protect routes with Next.js middleware — check auth tokens, redirect unauthenticated users, and apply i18n routing.","Next.js",[1549,1551,1552,1553],"Auth","Middleware","App Router","2026-07-17",{"path":1556,"title":1557,"description":1558,"language":1369,"tags":1559,"date":1563,"lang":1360},"\u002Fsnippets\u002Fen\u002Fstripe-webhook-idempotency","Stripe webhook handler with idempotency","Process Stripe webhooks safely — verify signatures, deduplicate events, handle checkout.session.completed and subscription updates.",[1369,1560,1561,1562],"Stripe","Webhooks","Payments","2026-07-16",{"path":1565,"title":1566,"description":1567,"language":1476,"tags":1568,"date":1570,"lang":1360},"\u002Fsnippets\u002Fen\u002Fgithub-actions-deploy-vps","GitHub Actions — deploy Node.js app to VPS via SSH","A CI\u002FCD pipeline that lints, tests, builds, and deploys a Node.js app to a VPS over SSH with zero-downtime.",[1476,1486,1462,1569],"Deployment","2026-07-15",{"path":1572,"title":1573,"description":1574,"language":1575,"tags":1576,"date":1578,"lang":1360},"\u002Fsnippets\u002Fen\u002Fexpress-jwt-refresh-tokens","JWT access and refresh token rotation in Express","A complete JWT auth middleware with short-lived access tokens and httpOnly refresh token rotation — no refresh token reuse vulnerability.","Express.js",[1575,1577,1551,1522],"JWT","2026-07-14",{"path":1580,"title":1581,"description":1582,"language":1444,"tags":1583,"date":1587,"lang":1360},"\u002Fsnippets\u002Fen\u002Fzod-api-validation-schema","Zod API validation schemas","Define type-safe request validation with Zod — parse, transform, and validate API request bodies, query strings, and URL params.",[1444,1584,1585,1586],"Zod","Validation","API","2026-07-13",{"path":1589,"title":1590,"description":1591,"language":1424,"tags":1592,"date":1593,"lang":1360},"\u002Fsnippets\u002Fen\u002Fprisma-schema-user-relations","Prisma schema — User, Post, and relations","A practical Prisma schema with User, Post, Session, and Subscription models using relations, enums, and indexes.",[1424,1426,1428,1444],"2026-07-12",{"path":1595,"title":1596,"description":1597,"language":1450,"tags":1598,"date":1601,"lang":1360},"\u002Fsnippets\u002Fen\u002Fnginx-reverse-proxy-ssl","Nginx reverse proxy with SSL termination","A hardened nginx configuration for proxying Node.js apps — SSL termination, rate limiting, security headers, and gzip.",[1450,1462,1599,1600],"SSL","Reverse Proxy","2026-07-11",{"path":1603,"title":1604,"description":1605,"language":1485,"tags":1606,"date":1609,"lang":1360},"\u002Fsnippets\u002Fen\u002Fdocker-compose-node-postgres-redis","Docker Compose for Node.js + PostgreSQL + Redis","A production-style docker-compose.yml for a Node.js app with PostgreSQL and Redis — the standard SaaS backend stack.",[1485,1462,1369,1607,1608],"PostgreSQL","Redis","2026-07-10",{"path":1611,"title":1612,"description":1613,"language":1614,"tags":1615,"date":1617,"lang":1360},"\u002Fsnippets\u002Fen\u002Fvue-use-view-mode","useViewMode hook","Toggle between grid\u002Flist view and persist the choice to localStorage across visits.","Vue",[1614,1616,1389],"JavaScript","2026-07-09",{"path":1619,"title":1620,"description":1621,"language":1614,"tags":1622,"date":1623,"lang":1360},"\u002Fsnippets\u002Fen\u002Fvue-use-platform","usePlatform hook","Detects macOS vs. other platforms so you can show the right keyboard shortcut symbol (⌘ vs Ctrl).",[1614,1616,1389],"2026-07-08",{"path":1625,"title":1626,"description":1627,"language":1614,"tags":1628,"date":1623,"lang":1360},"\u002Fsnippets\u002Fen\u002Fvue-use-theme","useTheme hook","A Vue composable for light\u002Fdark\u002Fsystem theme detection and switching, persisted to localStorage.",[1614,1616,1389],{"path":1630,"title":1631,"description":1632,"language":1614,"tags":1633,"date":1634,"lang":1360},"\u002Fsnippets\u002Fen\u002Fvue-use-media-query","useMediaQuery — Reactive Media Query","A Vue composable that returns a reactive ref updated whenever a CSS media query result changes. SSR-safe — does not throw on the server.",[1614,1616,1389,1536],"2026-07-07",{"path":1636,"title":1637,"description":1638,"language":1614,"tags":1639,"date":1640,"lang":1360},"\u002Fsnippets\u002Fen\u002Fvue-use-drag-and-drop","useDragAndDrop hook","A Vue composable that wires up drag-and-drop file uploads — tracks drag state and hands dropped files to your callback.",[1614,1616,1389],"2026-07-06",{"path":1642,"title":1643,"description":1644,"language":1614,"tags":1645,"date":1640,"lang":1360},"\u002Fsnippets\u002Fen\u002Fvue-use-intersection-observer","useIntersectionObserver — Vue Composable","A Vue composable for tracking element visibility in the viewport — great for lazy-loading images, scroll animations, and infinite scroll.",[1614,1616,1389,1436],{"path":1647,"title":1648,"description":1649,"language":1614,"tags":1650,"date":1346,"lang":1360},"\u002Fsnippets\u002Fen\u002Fvue-use-debounced-ref","useDebouncedRef — Delayed Reactive Value","A Vue composable returning a pair of refs: a \"live\" value and a debounced version that only updates after a pause in input. Perfect for search queries and filters.",[1614,1616,1389,1436],{"path":1652,"title":1653,"description":1654,"language":1614,"tags":1655,"date":1656,"lang":1360},"\u002Fsnippets\u002Fen\u002Fvue-use-breakpoints","useBreakpoints hook","A Vue composable that reactively tracks the viewport width and exposes isMobile\u002FisTablet\u002FisDesktop flags.",[1614,1616,1389],"2026-07-04",{"path":1658,"title":1659,"description":1660,"language":1614,"tags":1661,"date":1656,"lang":1360},"\u002Fsnippets\u002Fen\u002Fvue-use-clipboard-copy","useClipboardCopy hook","A Vue composable that copies text to the clipboard with a fallback for older browsers and a brief \"copied\" state.",[1614,1616,1389],{"path":1663,"title":1664,"description":1665,"language":1444,"tags":1666,"date":1668,"lang":1360},"\u002Fsnippets\u002Fen\u002Ftypescript-use-fetch-with-abort","useFetch with AbortController — TypeScript","A Vue composable for fetch requests that automatically cancels the previous request when a new one is made. Reactive data, loading, and error states included.",[1444,1614,1389,1586,1667],"Async","2026-07-03",{"path":1670,"title":1671,"description":1672,"language":1444,"tags":1673,"date":1675,"lang":1360},"\u002Fsnippets\u002Fen\u002Ftypescript-pagination-offset-limit","Pagination Utilities (Offset \u002F Limit)","Typed helpers for server-side pagination — compute offset and limit from a page number, return metadata (totalPages, hasNext, hasPrev).",[1444,1674,1586],"Utils","2026-07-02",{"path":1677,"title":1678,"description":1679,"language":1444,"tags":1680,"date":1681,"lang":1360},"\u002Fsnippets\u002Fen\u002Ftypescript-group-by-key","Group Array by Key","A lodash-free groupBy — groups an array of objects by a field value into a Record. TypeScript-typed, with a note on the native Object.groupBy alternative.",[1616,1444,1674],"2026-07-01",{"path":1683,"title":1684,"description":1685,"language":1444,"tags":1686,"date":1687,"lang":1360},"\u002Fsnippets\u002Fen\u002Ftypescript-chunk-array","Chunk Array into Groups","Splits an array into groups of a given size — useful for batch processing, in-memory pagination, or sending data in parts.",[1616,1444,1674],"2026-06-30",{"path":1689,"title":1690,"description":1691,"language":1377,"tags":1692,"date":1693,"lang":1360},"\u002Fsnippets\u002Fen\u002Freact.js-use-local-storage-react","usePersistedState hook","A useState drop-in that syncs its value to localStorage so it survives page reloads.",[1377,1616,1389],"2026-06-29",{"path":1695,"title":1696,"description":1697,"language":1377,"tags":1698,"date":1699,"lang":1360},"\u002Fsnippets\u002Fen\u002Freact.js-use-fetch-hook","useFetch hook","A reusable hook for fetching data with loading\u002Ferror state, handling request cancellation when the component unmounts or the URL changes.",[1377,1616,1389],"2026-06-28",{"path":1701,"title":1702,"description":1703,"language":1377,"tags":1704,"date":1705,"lang":1360},"\u002Fsnippets\u002Fen\u002Freact.js-use-debounce-hook","useDebounce hook","A React hook that debounces a fast-changing value (like search input) so effects only run once the user stops typing.",[1377,1616,1389],"2026-06-27",{"path":1707,"title":1708,"description":1709,"language":1377,"tags":1710,"date":1711,"lang":1360},"\u002Fsnippets\u002Fen\u002Freact.js-use-click-outside-hook","useOnClickOutside hook","Detects a click outside a referenced element — the standard way to close dropdowns, modals, and popovers.",[1377,1616,1389],"2026-06-26",{"path":1713,"title":1714,"description":1715,"language":1716,"tags":1717,"date":1718,"lang":1360},"\u002Fsnippets\u002Fen\u002Fpython-retry-decorator-python","Retry Decorator with Backoff (Python)","A decorator for retrying unstable functions with exponential backoff and jitter. Configurable exceptions, attempt count, and delay.","Python",[1716,1674,1586,1667],"2026-06-25",{"path":1720,"title":1721,"description":1722,"language":1716,"tags":1723,"date":1725,"lang":1360},"\u002Fsnippets\u002Fen\u002Fpython-read-csv-file","Read a CSV file into a list of dictionaries","The standard way to parse a CSV file's header row into keys, using Python's built-in csv module — no pandas needed for simple cases.",[1716,1724],"Files","2026-06-24",{"path":1727,"title":1728,"description":1729,"language":1716,"tags":1730,"date":1732,"lang":1360},"\u002Fsnippets\u002Fen\u002Fpython-flatten-nested-list-python","Flatten a nested list","Recursively flatten an arbitrarily nested list into a single flat list.",[1716,1731],"Utility","2026-06-23",{"path":1734,"title":1735,"description":1736,"language":1716,"tags":1737,"date":1738,"lang":1360},"\u002Fsnippets\u002Fen\u002Fpython-check-file-exists","Check if a file or directory exists","The modern, cross-platform way to check for a file's existence using pathlib instead of the older os.path functions.",[1716,1724],"2026-06-22",{"path":1740,"title":1741,"description":1742,"language":1716,"tags":1743,"date":1744,"lang":1360},"\u002Fsnippets\u002Fen\u002Fpython-async-gather-limited-python","asyncio.gather with Concurrency Limit","Runs a list of async tasks in parallel, but no more than N at a time — using asyncio.Semaphore. Prevents overloading external APIs or databases when processing large task lists.",[1716,1667,1674,1586],"2026-06-21",{"path":1746,"title":1747,"description":1748,"language":1749,"tags":1750,"date":1751,"lang":1360},"\u002Fsnippets\u002Fen\u002Fphp-validate-email","Validate an email address","The correct built-in way to check an email's format in PHP — no need for a hand-rolled regex.","PHP",[1749,1731],"2026-06-20",{"path":1753,"title":1754,"description":1755,"language":1749,"tags":1756,"date":1758,"lang":1360},"\u002Fsnippets\u002Fen\u002Fphp-timezone-gmt-offset","Get a timezone's current GMT offset in PHP","Turns an IANA timezone name into a friendly label like \"(GMT+02:00) Asia\u002FJerusalem\", accounting for daylight saving time.",[1749,1731,1757],"i18n","2026-06-19",{"path":1760,"title":1761,"description":1762,"language":1749,"tags":1763,"date":1765,"lang":1360},"\u002Fsnippets\u002Fen\u002Fphp-telegram-webhook-verify-secret","Verify Telegram Webhook Secret","Validates the X-Telegram-Bot-Api-Secret-Token header on an incoming webhook request — protects the endpoint from requests that don't come from Telegram.",[1749,1366,1764,1522],"Bots","2026-06-18",{"path":1767,"title":1768,"description":1769,"language":1749,"tags":1770,"date":1771,"lang":1360},"\u002Fsnippets\u002Fen\u002Fphp-telegram-send-message-php","Send Telegram Message via Bot API (PHP)","Sends a message through the Telegram Bot API without any SDK — only built-in curl. Supports MarkdownV2, ReplyMarkup, reply_to_message_id, and any other sendMessage parameters.",[1749,1366,1764,1586],"2026-06-17",{"path":1773,"title":1774,"description":1775,"language":1749,"tags":1776,"date":1777,"lang":1360},"\u002Fsnippets\u002Fen\u002Fphp-telegram-get-user-php","Get Telegram User ID via PHP Webhook","Reads an incoming Telegram update from php:\u002F\u002Finput and returns a normalised user object — id, username, name, and language code. Works with message, callback_query, and other update types.",[1749,1366,1764],"2026-06-16",{"path":1779,"title":1780,"description":1781,"language":1749,"tags":1782,"date":1783,"lang":1360},"\u002Fsnippets\u002Fen\u002Fphp-secure-random-password-php","Generate a secure random password","Create a cryptographically secure random password using PHP's random_bytes() and bin2hex().",[1749,1522],"2026-06-15",{"path":1785,"title":1786,"description":1787,"language":1749,"tags":1788,"date":1789,"lang":1360},"\u002Fsnippets\u002Fen\u002Fphp-sanitize-utf8-php","Sanitize a string to valid UTF-8","Strips or repairs invalid byte sequences so a string is always safe to store, log, or return as JSON.",[1749,1731],"2026-06-14",{"path":1791,"title":1792,"description":1793,"language":1749,"tags":1794,"date":1795,"lang":1360},"\u002Fsnippets\u002Fen\u002Fphp-retry-with-backoff","Retry an operation with exponential backoff + jitter","Retries a flaky API call with increasing delays between attempts, plus random jitter so many clients don't retry in lockstep.",[1749,1586],"2026-06-13",{"path":1797,"title":1798,"description":1799,"language":1749,"tags":1800,"date":1801,"lang":1360},"\u002Fsnippets\u002Fen\u002Fphp-rate-limiter-redis","Redis Rate Limiter (PHP)","A simple Redis-based rate limiter — restricts the number of requests from a user within a sliding time window. Atomic increment via INCR\u002FEXPIRE with no extra dependencies.",[1749,1608,1586,1522],"2026-06-12",{"path":1803,"title":1804,"description":1805,"language":1749,"tags":1806,"date":1807,"lang":1360},"\u002Fsnippets\u002Fen\u002Fphp-parse-bcp47-language-tag","Parse a BCP-47 language tag","Normalizes language codes like en-GB, pt_BR, or sr_RS (as sent by browsers, Telegram, etc.) into a language + region pair.",[1749,1731],"2026-06-11",{"path":1809,"title":1810,"description":1811,"language":1749,"tags":1812,"date":1813,"lang":1360},"\u002Fsnippets\u002Fen\u002Fphp-mask-token-preview","Mask a token\u002FAPI key for safe display","Shows only the first and last 4 characters of a secret (e.g. an API key) so it's safe to log or display in a UI.",[1749,1522],"2026-06-10",{"path":1815,"title":1816,"description":1817,"language":1749,"tags":1818,"date":1819,"lang":1360},"\u002Fsnippets\u002Fen\u002Fphp-ip-geolocation-php","Get IP geolocation info (ipapi.co)","Look up a visitor's city, country and timezone from their IP address using the free ipapi.co API.",[1749,1586],"2026-06-09",{"path":1821,"title":1822,"description":1823,"language":1749,"tags":1824,"date":1825,"lang":1360},"\u002Fsnippets\u002Fen\u002Fphp-initials-avatar-php","Generate a colored initials avatar (HTML\u002FCSS)","Renders a circular avatar with the user's initials and a color derived from their name — a lightweight fallback when there's no profile photo.",[1749,1731],"2026-06-08",{"path":1827,"title":1828,"description":1829,"language":1749,"tags":1830,"date":1831,"lang":1360},"\u002Fsnippets\u002Fen\u002Fphp-hash-password-bcrypt-php","Bcrypt Password Hashing (PHP)","Hashes and verifies passwords using PHP's native password_hash with bcrypt (cost 12). Includes a rehash check for when hashing parameters change.",[1749,1522,1551],"2026-06-07",{"path":1833,"title":1834,"description":1835,"language":1749,"tags":1836,"date":1837,"lang":1360},"\u002Fsnippets\u002Fen\u002Fphp-get-client-ip-behind-proxy","Get the real client IP behind a proxy\u002FCDN","Reads the visitor's real IP from CDN\u002Fproxy headers (Cloudflare, X-Forwarded-For, etc.), falling back to $_SERVER['REMOTE_ADDR'], and rejects private\u002Fspoofed values.",[1749,1522],"2026-06-06",{"path":1839,"title":1840,"description":1841,"language":1749,"tags":1842,"date":1843,"lang":1360},"\u002Fsnippets\u002Fen\u002Fphp-format-bytes-human-readable","Format bytes as a human-readable size","Turns a raw byte count into \"1.5 MB\", \"820 KB\", etc. — the standard file-size display helper.",[1749,1731],"2026-06-05",{"path":1845,"title":1846,"description":1847,"language":1749,"tags":1848,"date":1849,"lang":1360},"\u002Fsnippets\u002Fen\u002Fphp-escape-telegram-markdownv2","Escape text for Telegram MarkdownV2","Telegram's Bot API rejects messages containing unescaped MarkdownV2 special characters — this escapes all of them safely.",[1749,1586,1366],"2026-06-04",{"path":1851,"title":1852,"description":1853,"language":1749,"tags":1854,"date":1855,"lang":1360},"\u002Fsnippets\u002Fen\u002Fphp-curl-get-request","Make a GET request with cURL","The classic PHP recipe for fetching JSON from an external API without any extra dependencies.",[1749,1586],"2026-06-03",{"path":1857,"title":1858,"description":1859,"language":1369,"tags":1860,"date":1861,"lang":1360},"\u002Fsnippets\u002Fen\u002Fnode.js-timing-safe-string-compare","Constant-time string comparison for secrets","Compares two secrets (API tokens, webhook signatures) without leaking timing information that could help an attacker guess them character by character.",[1369,1522],"2026-06-02",{"path":1863,"title":1864,"description":1865,"language":1369,"tags":1866,"date":1867,"lang":1360},"\u002Fsnippets\u002Fen\u002Fnode.js-read-file-async","Read a file asynchronously","The modern Promise-based way to read a file's contents in Node.js — no callbacks, and no blocking the event loop like fs.readFileSync would.",[1369,1724],"2026-06-01",{"path":1869,"title":1870,"description":1871,"language":1369,"tags":1872,"date":1873,"lang":1360},"\u002Fsnippets\u002Fen\u002Fnode.js-read-env-defaults-node","Read environment variables with defaults","A tiny helper that reads a Node.js environment variable and falls back to a default when it's missing.",[1369,1616],"2026-05-31",{"path":1875,"title":1876,"description":1877,"language":1369,"tags":1878,"date":1879,"lang":1360},"\u002Fsnippets\u002Fen\u002Fnode.js-hash-password-bcrypt","Hash and verify a password with bcrypt","The standard way to store passwords safely in Node.js — never store or compare plain text.",[1369,1522],"2026-05-30",{"path":1881,"title":1882,"description":1883,"language":1369,"tags":1884,"date":1885,"lang":1360},"\u002Fsnippets\u002Fen\u002Fnode.js-create-http-server","Create a basic HTTP server","The bare Node.js way to serve requests — no Express needed for something this simple.",[1369,1586],"2026-05-29",{"path":1887,"title":1888,"description":1889,"language":1549,"tags":1890,"date":1891,"lang":1360},"\u002Fsnippets\u002Fen\u002Fnext.js-redirect","Redirect from a Server Component or Route Handler","The App Router's built-in way to send a user to another page — no need to construct a Response manually.",[1549,1377],"2026-05-28",{"path":1893,"title":1894,"description":1895,"language":1549,"tags":1896,"date":1897,"lang":1360},"\u002Fsnippets\u002Fen\u002Fnext.js-read-server-search-params-nextjs","Read search params in a Server Component","Access the current URL's query string inside an App Router Server Component without client-side JavaScript.",[1549,1377],"2026-05-27",{"path":1899,"title":1900,"description":1901,"language":1549,"tags":1902,"date":1903,"lang":1360},"\u002Fsnippets\u002Fen\u002Fnext.js-dynamic-metadata","Generate dynamic page metadata","Sets a page's \u003Ctitle> and meta tags based on fetched data — the App Router replacement for next\u002Fhead.",[1549,1377],"2026-05-26",{"path":1905,"title":1906,"description":1907,"language":1549,"tags":1908,"date":1909,"lang":1360},"\u002Fsnippets\u002Fen\u002Fnext.js-api-route-handler","Create an API route handler","The App Router way to build a REST endpoint — export a function named after the HTTP method instead of a single catch-all handler.",[1549,1586],"2026-05-25",{"path":1911,"title":1912,"description":1913,"language":1616,"tags":1914,"date":1915,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-validate-media-url","Validate a media URL","Checks that a URL is HTTPS and points to a recognized image\u002Fvideo\u002Faudio file extension — a common guard before accepting a user-supplied media link.",[1616,1731],"2026-05-24",{"path":1917,"title":1918,"description":1919,"language":1616,"tags":1920,"date":1921,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-validate-email","Validate Email Address","Checks email correctness without an over-complicated regex — strict enough for forms and APIs without rejecting rare but valid addresses.",[1616,1444,1674,1585],"2026-05-23",{"path":1923,"title":1924,"description":1925,"language":1616,"tags":1926,"date":1927,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-truncate-text-by-words","Truncate Text by Word Count","Truncates a string to a given number of words and appends a suffix (\"…\" by default). Unlike character truncation, it never splits a word in the middle.",[1616,1444,1674],"2026-05-22",{"path":1929,"title":1930,"description":1931,"language":1616,"tags":1932,"date":1933,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-to-boolean","Parse loose values into a real boolean","Turns strings like \"yes\"\u002F\"on\"\u002F\"1\" (and numbers) into a proper boolean — handy for parsing env vars, query params, or CSV imports.",[1616,1731],"2026-05-21",{"path":1935,"title":1936,"description":1937,"language":1616,"tags":1938,"date":1939,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-timezone-gmt-offset-label","Display a timezone with its GMT offset","Turns an IANA timezone name into a friendly label like \"(GMT+02:00) Asia\u002FJerusalem\" for timezone pickers.",[1616,1731,1757],"2026-05-20",{"path":1941,"title":1942,"description":1943,"language":1616,"tags":1944,"date":1945,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-throttle-function","Throttle a function","Ensures a function runs at most once every X milliseconds — ideal for scroll and mousemove handlers that would otherwise fire hundreds of times a second.",[1616,1731],"2026-05-19",{"path":1947,"title":1948,"description":1949,"language":1616,"tags":1950,"date":1951,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-telegram-user-link","Telegram User Link and Mention","Generates a clickable link to a Telegram user profile and mention markup for MarkdownV2 — via username if available, otherwise via a tg:\u002F\u002Fuser?id= deep link.",[1616,1366,1764],"2026-05-18",{"path":1953,"title":1954,"description":1955,"language":1616,"tags":1956,"date":1957,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-telegram-get-user-from-update","Get User ID and Data from Telegram Update","Extracts the user ID, name, and username from any type of incoming Telegram update — message, callback_query, edited_message, inline_query, and others.",[1616,1366,1764],"2026-05-17",{"path":1959,"title":1960,"description":1961,"language":1616,"tags":1962,"date":1963,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-telegram-answer-callback-query","Answer Telegram Callback Query","Responds to an inline button press (answerCallbackQuery) without an SDK — removes the loading spinner from the button, optionally showing a notification or alert to the user.",[1616,1366,1764,1586],"2026-05-16",{"path":1965,"title":1966,"description":1967,"language":1616,"tags":1968,"date":1969,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-smart-telegram-button-layout","Auto-arrange Telegram inline keyboard buttons","Packs short buttons two-per-row and gives long ones their own row, based on the message text length above them — keeps keyboards compact without truncating labels.",[1616,1366],"2026-05-15",{"path":1971,"title":1972,"description":1973,"language":1616,"tags":1974,"date":1975,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-sleep-delay-async","sleep() \u002F Async Delay","A minimal sleep function for JavaScript and TypeScript — pauses an async function for a given number of milliseconds without any third-party library.",[1616,1444,1674,1667],"2026-05-14",{"path":1977,"title":1978,"description":1979,"language":1616,"tags":1980,"date":1981,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-seconds-until-utc-rollover","Get seconds until the next UTC day\u002Fmonth rollover","Computes how many seconds remain until midnight UTC (or the 1st of next month) — the exact TTL you want on a Redis key for a daily\u002Fmonthly quota.",[1616,1731],"2026-05-13",{"path":1983,"title":1984,"description":1985,"language":1616,"tags":1986,"date":1987,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-scroll-to-anchor","Smooth Scroll to Anchor","Scrolls the page to an element by ID, accounting for an offset (e.g. a fixed header height) — no jQuery, native smooth scroll.",[1616,1536,1674],"2026-05-12",{"path":1989,"title":1990,"description":1991,"language":1616,"tags":1992,"date":1993,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-safe-json-parse","A JSON.parse that never throws","Wraps JSON.parse to return null on invalid input instead of crashing — perfect for deserializing values from Redis, localStorage, or user input.",[1616,1731],"2026-05-11",{"path":1995,"title":1996,"description":1997,"language":1616,"tags":1998,"date":1999,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-remove-duplicate-array-items","Remove duplicates from an array","The one-line way to deduplicate an array of primitives using Set, plus the pattern for deduplicating an array of objects by a key.",[1616,1731],"2026-05-10",{"path":2001,"title":2002,"description":2003,"language":1616,"tags":2004,"date":2005,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-pluralize-intl","Pluralize words with Intl.PluralRules","Correctly pluralizes nouns for any locale — including languages like Russian with multiple plural forms — using the built-in Intl API.",[1616,1731,1757],"2026-05-09",{"path":2007,"title":2008,"description":2009,"language":1616,"tags":2010,"date":2011,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-pick-omit-object-keys","Pick and omit object keys","Two small helpers for building a new object with only (or without) a specific set of keys — handy for trimming payloads before sending them to an API.",[1616,1731],"2026-05-08",{"path":2013,"title":2014,"description":2015,"language":1616,"tags":2016,"date":2017,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-parse-telegram-start-params","Parse Telegram \u002Fstart deep-link parameters","Extracts traffic source, UTM tags, or a referrer ID from the payload Telegram passes after \u002Fstart — the standard way to track where bot users came from.",[1616,1366],"2026-05-07",{"path":2019,"title":2020,"description":2021,"language":1616,"tags":2022,"date":2023,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-jwt-decode-payload","Decode JWT Payload Without Verification","Extracts the payload from a JWT token on the client without verifying the signature — handy for reading exp, userId, and roles in UI. Never use for server-side authorization.",[1616,1444,1551,1577],"2026-05-06",{"path":2025,"title":2026,"description":2027,"language":1616,"tags":2028,"date":2029,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-iso-date-without-timezone-shift","Convert Date to\u002Ffrom \"YYYY-MM-DD\" without timezone shifts","date.toISOString().slice(0,10) silently shifts to UTC and can show yesterday's date near midnight — these two helpers stay in local time.",[1616,1731],"2026-05-05",{"path":2031,"title":2032,"description":2033,"language":1616,"tags":2034,"date":2035,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-is-editable-target","Check if an event target is an editable field","Guards global keyboard shortcuts from firing while the user is typing in an input, textarea, select, or contenteditable element.",[1616,1731],"2026-05-04",{"path":2037,"title":2038,"description":2039,"language":1616,"tags":2040,"date":2041,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-is-color-light","Check if a hex color is light or dark","Computes perceived brightness from a hex color to decide whether black or white text reads better on top of it.",[1616,1731],"2026-05-03",{"path":2043,"title":1816,"description":2044,"language":1616,"tags":2045,"date":2046,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-ip-geolocation-js","Look up a visitor's country and city from their IP address using the free ipapi.co API.",[1616,1586],"2026-05-02",{"path":2048,"title":2049,"description":2050,"language":1616,"tags":2051,"date":2052,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-generate-uuid","Generate a UUID","Creates a spec-compliant random UUID (v4) using the built-in Web Crypto API — no external library needed.",[1616,1731],"2026-05-01",{"path":2054,"title":2055,"description":2056,"language":1616,"tags":2057,"date":2058,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-generate-slug-from-string","Generate Slug from String","Converts an arbitrary string (including Cyrillic) into a URL-friendly slug — lowercase letters, no spaces or special characters, hyphens instead of spaces.",[1616,1444,1674],"2026-04-30",{"path":2060,"title":2061,"description":2062,"language":1616,"tags":2063,"date":2064,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-format-date-in-timezone","Format a date\u002Ftime in any IANA timezone","Renders a date as if you were standing in a specific timezone, regardless of the visitor's own local time.",[1616,1731,1757],"2026-04-29",{"path":2066,"title":2067,"description":2068,"language":1616,"tags":2069,"date":2070,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-format-compact-number","Format a number compactly (1.2K, 3M)","Turns large numbers into short, human-readable labels — 600000 becomes 600K.",[1616,1731],"2026-04-28",{"path":2072,"title":2073,"description":2074,"language":1616,"tags":2075,"date":2076,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-detect-visitor-timezone","Detect the visitor's timezone automatically","One line to get the visitor's IANA timezone (e.g. \"Asia\u002FJerusalem\") straight from the browser, no geolocation permission needed.",[1616,1731,1757],"2026-04-27",{"path":2078,"title":2079,"description":2080,"language":1616,"tags":2081,"date":2082,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-deep-clone-object","Deep Clone an Object","Creates a fully independent copy of an object with no shared references — uses the native structuredClone where available, falling back to a JSON round-trip for older environments.",[1616,1444,1674],"2026-04-26",{"path":2084,"title":2085,"description":2086,"language":1616,"tags":2087,"date":2088,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-dedupe-sort-tag-list","Manage a deduplicated, sorted tag list","Add\u002Fremove operations for a tag array that always stays unique and alphabetically sorted — handy for tag pickers and filter chips.",[1616,1731],"2026-04-25",{"path":2090,"title":2091,"description":2092,"language":1616,"tags":2093,"date":2094,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-debounce-function","Debounce a function","Delay a function's execution until a burst of calls has stopped — ideal for search inputs and resize handlers.",[1616,1731],"2026-04-24",{"path":2096,"title":2097,"description":2098,"language":1616,"tags":2099,"date":2101,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-cosine-similarity","Cosine similarity between two vectors","Measures how similar two embedding vectors are — the core operation behind semantic search and RAG retrieval.",[1616,2100,1731],"AI","2026-04-23",{"path":2103,"title":2104,"description":2105,"language":1616,"tags":2106,"date":2107,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-cn-classname-merge","Merge Tailwind classes safely (cn helper)","Combines conditional classes with clsx and resolves conflicting Tailwind utilities with tailwind-merge — the standard \"cn()\" helper used across shadcn\u002Fui-style codebases.",[1616,1536,1731],"2026-04-22",{"path":2109,"title":2110,"description":2111,"language":1616,"tags":2112,"date":2113,"lang":1360},"\u002Fsnippets\u002Fen\u002Fjavascript-capitalize-first-letter","Capitalize the first letter of a string","The short, correct way to uppercase just the first character — plus a version that capitalizes every word.",[1616,1731],"2026-04-21",{"path":2115,"title":2116,"description":2117,"language":2118,"tags":2119,"date":2120,"lang":1360},"\u002Fsnippets\u002Fen\u002Fgo-simple-http-server","Create a simple HTTP server","The standard-library way to serve HTTP routes in Go — no framework needed for a small API.","Go",[2118,1586],"2026-04-20",{"path":2122,"title":2123,"description":2124,"language":2118,"tags":2125,"date":2126,"lang":1360},"\u002Fsnippets\u002Fen\u002Fgo-read-file-lines-go","Read a file line by line","Stream a text file line by line without loading the whole file into memory.",[2118,1724],"2026-04-19",{"path":2128,"title":2129,"description":2130,"language":2118,"tags":2131,"date":2132,"lang":1360},"\u002Fsnippets\u002Fen\u002Fgo-http-get-request","Make an HTTP GET request","The standard-library way to fetch and parse JSON from an API — no third-party HTTP client needed.",[2118,1586],"2026-04-18",{"path":2134,"title":2135,"description":2136,"language":2118,"tags":2137,"date":2142,"lang":1360},"\u002Fsnippets\u002Fen\u002Fgo-graceful-shutdown","HTTP Server with Graceful Shutdown (Go)","Production-ready Go HTTP server template that intercepts SIGINT\u002FSIGTERM signals to shut down cleanly without interrupting active client requests.",[2118,2138,2139,2140,2141],"Golang","HTTP","Backend","Concurrency","2026-04-17",{"path":2144,"title":2145,"description":2146,"language":2118,"tags":2147,"date":2148,"lang":1360},"\u002Fsnippets\u002Fen\u002Fgo-goroutines-waitgroup","Run tasks concurrently with goroutines","The standard pattern for launching multiple goroutines and waiting for all of them to finish, using sync.WaitGroup.",[2118,1731],"2026-04-16",{"path":2150,"title":2049,"description":2151,"language":2118,"tags":2152,"date":2153,"lang":1360},"\u002Fsnippets\u002Fen\u002Fgo-generate-uuid","Creates a random v4 UUID using only the standard library's crypto\u002Frand — no external uuid package required.",[2118,1731],"2026-04-15",{"path":2155,"title":2156,"description":2157,"language":2118,"tags":2158,"date":2160,"lang":1360},"\u002Fsnippets\u002Fen\u002Fgo-context-timeout","HTTP Request with Timeout via Context (Go)","Sending HTTP requests in Go using context.WithTimeout to enforce network deadline limits and prevent dangling goroutines.",[2118,2138,2139,2159,1380],"Timeout","2026-04-14",{"path":2162,"title":2163,"description":2164,"language":1575,"tags":2165,"date":2166,"lang":1360},"\u002Fsnippets\u002Fen\u002Fexpress.js-sanitize-filename-express","Sanitize an uploaded filename","Strip a file extension and normalize the remaining name to a safe, URL-friendly slug before saving it to disk.",[1575,1369,1616],"2026-04-13",{"path":2168,"title":2169,"description":2170,"language":1575,"tags":2171,"date":2172,"lang":1360},"\u002Fsnippets\u002Fen\u002Fexpress.js-rate-limiting-middleware","Rate limit requests","Caps how many requests a single IP can make in a time window — the standard first line of defense against abuse and brute-force attempts.",[1575,1369,1522],"2026-04-12",{"path":2174,"title":2175,"description":2176,"language":1575,"tags":2177,"date":2178,"lang":1360},"\u002Fsnippets\u002Fen\u002Fexpress.js-jwt-auth-middleware","JWT Auth Middleware (Express)","Express middleware for verifying Authorization header JWT token, parsing payload data, and extending the Request object in TypeScript safely.",[1575,1369,1522,1577,1444],"2026-04-11",{"path":2180,"title":2181,"description":2182,"language":1575,"tags":2183,"date":2184,"lang":1360},"\u002Fsnippets\u002Fen\u002Fexpress.js-error-handling-middleware","Centralized error-handling middleware","Catches errors from any route\u002Fmiddleware in one place instead of repeating try\u002Fcatch and error-formatting logic everywhere.",[1575,1369],"2026-04-10",{"path":2186,"title":2187,"description":2188,"language":1575,"tags":2189,"date":2190,"lang":1360},"\u002Fsnippets\u002Fen\u002Fexpress.js-cors-middleware","Enable CORS in Express","The standard way to allow cross-origin requests from your frontend — either quick-and-open, or locked down to specific origins.",[1575,1369,1586],"2026-04-09",{"path":2192,"title":2193,"description":2194,"language":1536,"tags":2195,"date":2196,"lang":1360},"\u002Fsnippets\u002Fen\u002Fcss-truncate-text-ellipsis","Truncate single-line text with an ellipsis","Cuts off overflowing text on one line and adds \"…\" at the end — the classic CSS-only text truncation.",[1536,1731],"2026-04-08",{"path":2198,"title":2199,"description":2200,"language":1536,"tags":2201,"date":2203,"lang":1360},"\u002Fsnippets\u002Fen\u002Fcss-skeleton-loading","Skeleton Loading Animation (CSS)","A shimmer effect for loading skeletons — a pulsing gradient simulates content loading. No JavaScript, pure CSS animation using custom properties.",[1536,2202,1399],"UI","2026-04-07",{"path":2205,"title":2206,"description":2207,"language":1536,"tags":2208,"date":2210,"lang":1360},"\u002Fsnippets\u002Fen\u002Fcss-line-clamp","Multiline text-overflow with Ellipsis","Truncates text after N lines with an ellipsis via -webkit-line-clamp — works in all modern browsers without JavaScript.",[1536,1674,2209],"Typography","2026-04-06",{"path":2212,"title":2213,"description":2214,"language":1536,"tags":2215,"date":2217,"lang":1360},"\u002Fsnippets\u002Fen\u002Fcss-grid-auto-fit","CSS Grid auto-fit — Responsive Grid Without Media Queries","Creates a self-adapting column grid with auto-fit and minmax — cards reflow automatically as the container width changes, with zero breakpoints.",[1536,2216,1390,1537],"Layout","2026-04-05",{"path":2219,"title":2220,"description":2221,"language":1536,"tags":2222,"date":2223,"lang":1360},"\u002Fsnippets\u002Fen\u002Fcss-gradient-text","CSS Gradient Text","Applies a linear or radial gradient to text via background-clip: text — no SVG, no JavaScript. Supported by all modern browsers.",[1536,2202,2209],"2026-04-04",{"path":2225,"title":2226,"description":2227,"language":1536,"tags":2228,"date":2229,"lang":1360},"\u002Fsnippets\u002Fen\u002Fcss-fluid-typography","Fluid Typography with clamp()","Scales font size smoothly between a minimum and maximum value based on viewport width — no media queries, just clamp().",[1536,2209,1390],"2026-04-03",{"path":2231,"title":2232,"description":2233,"language":1536,"tags":2234,"date":2235,"lang":1360},"\u002Fsnippets\u002Fen\u002Fcss-custom-scrollbar","Custom CSS Scrollbar","Styles the scrollbar to match a design system without JavaScript — thin track, rounded thumb, hover state. Includes a variant to hide the scrollbar while keeping scroll behaviour.",[1536,1674,2202],"2026-04-02",{"path":2237,"title":2238,"description":2239,"language":1536,"tags":2240,"date":2241,"lang":1360},"\u002Fsnippets\u002Fen\u002Fcss-center-with-flexbox-css","Center anything with flexbox","A reusable utility class that perfectly centers a single child both horizontally and vertically.",[1536,2216],"2026-04-01",{"id":2243,"title":1659,"body":2244,"date":1656,"description":1660,"extension":1348,"lang":1360,"language":1614,"meta":2630,"navigation":108,"path":1658,"published":108,"seo":2631,"stem":2632,"tags":2633,"__hash__":2634},"snippets\u002Fsnippets\u002Fen\u002Fvue-use-clipboard-copy.md",{"type":8,"value":2245,"toc":2628},[2246,2265,2625],[11,2247,2248,2249,2252,2253,2256,2257,2260,2261,2264],{},"How it works: it prefers the modern ",[42,2250,2251],{},"navigator.clipboard.writeText"," API, falling back to a hidden ",[42,2254,2255],{},"\u003Ctextarea>"," + ",[42,2258,2259],{},"document.execCommand('copy')"," for older\u002Finsecure contexts, and exposes a ",[42,2262,2263],{},"copied"," flag that auto-resets after 1.5s for UI feedback.",[34,2266,2270],{"className":2267,"code":2268,"language":2269,"meta":40,"style":40},"language-js shiki shiki-themes github-dark","import { ref } from 'vue'\n\nexport function useClipboardCopy() {\n  const isCopying = ref(false)\n  const copied = ref(false)\n\n  async function copy(text) {\n    if (!text) return false\n\n    isCopying.value = true\n    try {\n      if (navigator.clipboard?.writeText) {\n        await navigator.clipboard.writeText(text)\n      } else {\n        const el = document.createElement('textarea')\n        el.value = text\n        el.style.position = 'fixed'\n        el.style.opacity = '0'\n        document.body.appendChild(el)\n        el.select()\n        document.execCommand('copy')\n        document.body.removeChild(el)\n      }\n      copied.value = true\n      setTimeout(() => { copied.value = false }, 1500)\n      return true\n    } catch {\n      return false\n    } finally {\n      isCopying.value = false\n    }\n  }\n\n  return { copy, isCopying, copied }\n}\n","js",[42,2271,2272,2284,2288,2299,2316,2333,2337,2354,2371,2375,2385,2392,2400,2414,2424,2447,2457,2467,2477,2488,2499,2514,2523,2528,2537,2562,2569,2578,2584,2593,2602,2606,2610,2614,2621],{"__ignoreMap":40},[81,2273,2274,2276,2279,2281],{"class":83,"line":84},[81,2275,88],{"class":87},[81,2277,2278],{"class":91}," { ref } ",[81,2280,95],{"class":87},[81,2282,2283],{"class":98}," 'vue'\n",[81,2285,2286],{"class":83,"line":105},[81,2287,109],{"emptyLinePlaceholder":108},[81,2289,2290,2292,2294,2297],{"class":83,"line":112},[81,2291,262],{"class":87},[81,2293,265],{"class":87},[81,2295,2296],{"class":118}," useClipboardCopy",[81,2298,969],{"class":91},[81,2300,2301,2303,2306,2308,2310,2312,2314],{"class":83,"line":125},[81,2302,303],{"class":87},[81,2304,2305],{"class":135}," isCopying",[81,2307,309],{"class":87},[81,2309,981],{"class":118},[81,2311,271],{"class":91},[81,2313,375],{"class":135},[81,2315,515],{"class":91},[81,2317,2318,2320,2323,2325,2327,2329,2331],{"class":83,"line":141},[81,2319,303],{"class":87},[81,2321,2322],{"class":135}," copied",[81,2324,309],{"class":87},[81,2326,981],{"class":118},[81,2328,271],{"class":91},[81,2330,375],{"class":135},[81,2332,515],{"class":91},[81,2334,2335],{"class":83,"line":154},[81,2336,109],{"emptyLinePlaceholder":108},[81,2338,2339,2342,2344,2347,2349,2351],{"class":83,"line":167},[81,2340,2341],{"class":87},"  async",[81,2343,265],{"class":87},[81,2345,2346],{"class":118}," copy",[81,2348,271],{"class":91},[81,2350,39],{"class":128},[81,2352,2353],{"class":91},") {\n",[81,2355,2356,2358,2360,2362,2365,2368],{"class":83,"line":179},[81,2357,1054],{"class":87},[81,2359,357],{"class":91},[81,2361,360],{"class":87},[81,2363,2364],{"class":91},"text) ",[81,2366,2367],{"class":87},"return",[81,2369,2370],{"class":135}," false\n",[81,2372,2373],{"class":83,"line":191},[81,2374,109],{"emptyLinePlaceholder":108},[81,2376,2377,2380,2382],{"class":83,"line":204},[81,2378,2379],{"class":91},"    isCopying.value ",[81,2381,1093],{"class":87},[81,2383,2384],{"class":135}," true\n",[81,2386,2387,2390],{"class":83,"line":210},[81,2388,2389],{"class":87},"    try",[81,2391,122],{"class":91},[81,2393,2394,2397],{"class":83,"line":215},[81,2395,2396],{"class":87},"      if",[81,2398,2399],{"class":91}," (navigator.clipboard?.writeText) {\n",[81,2401,2402,2405,2408,2411],{"class":83,"line":225},[81,2403,2404],{"class":87},"        await",[81,2406,2407],{"class":91}," navigator.clipboard.",[81,2409,2410],{"class":118},"writeText",[81,2412,2413],{"class":91},"(text)\n",[81,2415,2416,2419,2422],{"class":83,"line":237},[81,2417,2418],{"class":91},"      } ",[81,2420,2421],{"class":87},"else",[81,2423,122],{"class":91},[81,2425,2426,2429,2432,2434,2437,2440,2442,2445],{"class":83,"line":249},[81,2427,2428],{"class":87},"        const",[81,2430,2431],{"class":135}," el",[81,2433,309],{"class":87},[81,2435,2436],{"class":91}," document.",[81,2438,2439],{"class":118},"createElement",[81,2441,271],{"class":91},[81,2443,2444],{"class":98},"'textarea'",[81,2446,515],{"class":91},[81,2448,2449,2452,2454],{"class":83,"line":254},[81,2450,2451],{"class":91},"        el.value ",[81,2453,1093],{"class":87},[81,2455,2456],{"class":91}," text\n",[81,2458,2459,2462,2464],{"class":83,"line":259},[81,2460,2461],{"class":91},"        el.style.position ",[81,2463,1093],{"class":87},[81,2465,2466],{"class":98}," 'fixed'\n",[81,2468,2469,2472,2474],{"class":83,"line":300},[81,2470,2471],{"class":91},"        el.style.opacity ",[81,2473,1093],{"class":87},[81,2475,2476],{"class":98}," '0'\n",[81,2478,2479,2482,2485],{"class":83,"line":321},[81,2480,2481],{"class":91},"        document.body.",[81,2483,2484],{"class":118},"appendChild",[81,2486,2487],{"class":91},"(el)\n",[81,2489,2490,2493,2496],{"class":83,"line":345},[81,2491,2492],{"class":91},"        el.",[81,2494,2495],{"class":118},"select",[81,2497,2498],{"class":91},"()\n",[81,2500,2501,2504,2507,2509,2512],{"class":83,"line":351},[81,2502,2503],{"class":91},"        document.",[81,2505,2506],{"class":118},"execCommand",[81,2508,271],{"class":91},[81,2510,2511],{"class":98},"'copy'",[81,2513,515],{"class":91},[81,2515,2516,2518,2521],{"class":83,"line":366},[81,2517,2481],{"class":91},[81,2519,2520],{"class":118},"removeChild",[81,2522,2487],{"class":91},[81,2524,2525],{"class":83,"line":381},[81,2526,2527],{"class":91},"      }\n",[81,2529,2530,2533,2535],{"class":83,"line":387},[81,2531,2532],{"class":91},"      copied.value ",[81,2534,1093],{"class":87},[81,2536,2384],{"class":135},[81,2538,2539,2542,2544,2546,2549,2551,2554,2557,2560],{"class":83,"line":392},[81,2540,2541],{"class":118},"      setTimeout",[81,2543,1023],{"class":91},[81,2545,1026],{"class":87},[81,2547,2548],{"class":91}," { copied.value ",[81,2550,1093],{"class":87},[81,2552,2553],{"class":135}," false",[81,2555,2556],{"class":91}," }, ",[81,2558,2559],{"class":135},"1500",[81,2561,515],{"class":91},[81,2563,2564,2567],{"class":83,"line":399},[81,2565,2566],{"class":87},"      return",[81,2568,2384],{"class":135},[81,2570,2571,2574,2576],{"class":83,"line":452},[81,2572,2573],{"class":91},"    } ",[81,2575,831],{"class":87},[81,2577,122],{"class":91},[81,2579,2580,2582],{"class":83,"line":457},[81,2581,2566],{"class":87},[81,2583,2370],{"class":135},[81,2585,2586,2588,2591],{"class":83,"line":463},[81,2587,2573],{"class":91},[81,2589,2590],{"class":87},"finally",[81,2592,122],{"class":91},[81,2594,2595,2598,2600],{"class":83,"line":476},[81,2596,2597],{"class":91},"      isCopying.value ",[81,2599,1093],{"class":87},[81,2601,2370],{"class":135},[81,2603,2604],{"class":83,"line":518},[81,2605,1193],{"class":91},[81,2607,2608],{"class":83,"line":538},[81,2609,384],{"class":91},[81,2611,2612],{"class":83,"line":543},[81,2613,109],{"emptyLinePlaceholder":108},[81,2615,2616,2618],{"class":83,"line":549},[81,2617,1207],{"class":87},[81,2619,2620],{"class":91}," { copy, isCopying, copied }\n",[81,2622,2623],{"class":83,"line":555},[81,2624,207],{"class":91},[1332,2626,2627],{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}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);}",{"title":40,"searchDepth":105,"depth":105,"links":2629},[],{},{"title":1659,"description":1660},"snippets\u002Fen\u002Fvue-use-clipboard-copy",[1614,1616,1389],"oF_g54w5GJjEp2N53uSyRdC-PyEBtAC1btDAqLvmXRg",1784561424337]