[{"data":1,"prerenderedAt":2022},["ShallowReactive",2],{"header-latest":3,"post-\u002Fblog\u002Fwebsite-optimization-ai-crawlers":1372},[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",{"id":1373,"title":1374,"body":1375,"date":1999,"description":2000,"extension":1348,"faq":2001,"image":2011,"lang":1360,"meta":2012,"navigation":108,"path":2013,"published":108,"readTime":215,"seo":2014,"stem":2015,"tags":2016,"updated":1346,"__hash__":2021},"blog\u002Fblog\u002Fen\u002Fwebsite-optimization-ai-crawlers.md","Optimizing Your Website for AI Crawlers: Robots.txt & JSON-LD",{"type":8,"value":1376,"toc":1991},[1377,1396,1399,1410,1414,1417,1423,1429,1433,1436,1443,1453,1782,1786,1789,1796,1800,1828,1921,1924,1934,1936,1970,1977,1988],[11,1378,1379,1380,1383,1384,1387,1388,1391,1392,1395],{},"The web scraping landscape has shifted. The scrapers visiting your server are no longer limited to Googlebot, YandexBot, or Bingbot. Today, your server logs are likely dominated by ",[15,1381,1382],{},"GPTBot"," (OpenAI), ",[15,1385,1386],{},"ClaudeBot"," (Anthropic), ",[15,1389,1390],{},"PerplexityBot",", and ",[15,1393,1394],{},"OAI-SearchBot"," (OpenAI’s conversational search engine agent).",[11,1397,1398],{},"As these LLM agents actively search the web to answer live user queries, you must ensure that your technical setup permits them to scrape public information, maps your services accurately, and prevents them from reading private database endpoints.",[11,1400,1401,1402,1405,1406,1409],{},"In this technical guide, we will configure an AI-friendly ",[42,1403,1404],{},"robots.txt"," policy, implement nested ",[42,1407,1408],{},"JSON-LD"," schemas, and discuss how to bypass hydration\u002FJavaScript crawling issues.",[26,1411,1413],{"id":1412},"_1-directives-configuring-robotstxt-for-ai-crawlers","1. Directives: Configuring Robots.txt for AI Crawlers",[11,1415,1416],{},"You should treat AI bots as distinct entities in your crawl configuration. While you want them to index your blog articles and service offerings so they can cite you, you must block them from indexing resources like API endpoints, user checkout routes, or local search configurations.",[11,1418,1419,1420,1422],{},"Here is a secure, production-ready ",[42,1421,1404],{}," configuration:",[34,1424,1427],{"className":1425,"code":1426,"language":39,"meta":40},[37],"# Global rules for all standard crawlers\nUser-agent: *\nAllow: \u002F\nDisallow: \u002Fadmin\u002F\nDisallow: \u002Fapi\u002F\nDisallow: \u002Fcheckout\u002F\n\n# OpenAI ChatGPT User Agent (Used to retrieve links for ChatGPT answers)\nUser-agent: GPTBot\nAllow: \u002F\nDisallow: \u002Fadmin\u002F\nDisallow: \u002Fapi\u002F\nDisallow: \u002Fcheckout\u002F\nDisallow: \u002Fprivate\u002F\n\n# OpenAI Search Agent (Used for real-time search engine features)\nUser-agent: OAI-SearchBot\nAllow: \u002F\nDisallow: \u002Fadmin\u002F\nDisallow: \u002Fapi\u002F\n\n# Anthropic Claude User Agent\nUser-agent: ClaudeBot\nAllow: \u002F\nDisallow: \u002Fadmin\u002F\nDisallow: \u002Fapi\u002F\n\n# Perplexity Search Agent\nUser-agent: PerplexityBot\nAllow: \u002F\nDisallow: \u002Fadmin\u002F\nDisallow: \u002Fapi\u002F\n\n# Block aggressive, non-citing scrapers (Scrapers that steal data without giving traffic)\nUser-agent: CCBot\nDisallow: \u002F\nUser-agent: Amazonbot\nDisallow: \u002F\n",[42,1428,1426],{"__ignoreMap":40},[26,1430,1432],{"id":1431},"_2-implementing-nested-json-ld-schema-graphs","2. Implementing Nested JSON-LD Schema Graphs",[11,1434,1435],{},"LLMs do not parse text like human readers; they map concepts and values to semantic entities. If you describe your business in a plain paragraph, an LLM might misinterpret your prices, locations, or provider identities.",[11,1437,1438,1439,1442],{},"By injecting structured ",[15,1440,1441],{},"JSON-LD (JSON Linked Data)"," inside your page headers, you tell the crawlers exactly what your business represents.",[11,1444,1445,1446,1449,1450,132],{},"Here is an advanced nested graph representing an ",[15,1447,1448],{},"Organization"," offering a specialized ",[15,1451,1452],{},"SaaS Development Service",[34,1454,1458],{"className":1455,"code":1456,"language":1457,"meta":40,"style":40},"language-json shiki shiki-themes github-dark","{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@graph\": [\n    {\n      \"@type\": \"Organization\",\n      \"@id\": \"https:\u002F\u002Falexrel.com\u002F#organization\",\n      \"name\": \"Alex Rel Development\",\n      \"url\": \"https:\u002F\u002Falexrel.com\",\n      \"logo\": \"https:\u002F\u002Falexrel.com\u002Flogo.png\",\n      \"sameAs\": [\n        \"https:\u002F\u002Fgithub.com\u002Falexrel\",\n        \"https:\u002F\u002Flinkedin.com\u002Fin\u002Falexrel\"\n      ]\n    },\n    {\n      \"@type\": \"Service\",\n      \"@id\": \"https:\u002F\u002Falexrel.com\u002Fsaas-development#service\",\n      \"name\": \"B2B SaaS Architecture and Development\",\n      \"description\": \"Full-cycle SaaS development from database schema isolation to Stripe webhook billing and vector support bots.\",\n      \"provider\": {\n        \"@id\": \"https:\u002F\u002Falexrel.com\u002F#organization\"\n      },\n      \"areaServed\": \"Worldwide\",\n      \"offers\": {\n        \"@type\": \"Offer\",\n        \"priceCurrency\": \"USD\",\n        \"price\": \"5000\",\n        \"priceSpecification\": {\n          \"@type\": \"UnitPriceSpecification\",\n          \"priceType\": \"https:\u002F\u002Fschema.org\u002FMinimumPrice\",\n          \"price\": \"5000\",\n          \"priceCurrency\": \"USD\"\n        }\n      }\n    }\n  ]\n}\n","json",[42,1459,1460,1465,1479,1487,1492,1504,1516,1528,1540,1552,1559,1566,1571,1576,1581,1585,1596,1607,1618,1630,1638,1648,1653,1665,1672,1684,1696,1708,1715,1727,1739,1750,1760,1765,1770,1774,1778],{"__ignoreMap":40},[81,1461,1462],{"class":83,"line":84},[81,1463,1464],{"class":91},"{\n",[81,1466,1467,1470,1473,1476],{"class":83,"line":105},[81,1468,1469],{"class":135},"  \"@context\"",[81,1471,1472],{"class":91},": ",[81,1474,1475],{"class":98},"\"https:\u002F\u002Fschema.org\"",[81,1477,1478],{"class":91},",\n",[81,1480,1481,1484],{"class":83,"line":112},[81,1482,1483],{"class":135},"  \"@graph\"",[81,1485,1486],{"class":91},": [\n",[81,1488,1489],{"class":83,"line":125},[81,1490,1491],{"class":91},"    {\n",[81,1493,1494,1497,1499,1502],{"class":83,"line":141},[81,1495,1496],{"class":135},"      \"@type\"",[81,1498,1472],{"class":91},[81,1500,1501],{"class":98},"\"Organization\"",[81,1503,1478],{"class":91},[81,1505,1506,1509,1511,1514],{"class":83,"line":154},[81,1507,1508],{"class":135},"      \"@id\"",[81,1510,1472],{"class":91},[81,1512,1513],{"class":98},"\"https:\u002F\u002Falexrel.com\u002F#organization\"",[81,1515,1478],{"class":91},[81,1517,1518,1521,1523,1526],{"class":83,"line":167},[81,1519,1520],{"class":135},"      \"name\"",[81,1522,1472],{"class":91},[81,1524,1525],{"class":98},"\"Alex Rel Development\"",[81,1527,1478],{"class":91},[81,1529,1530,1533,1535,1538],{"class":83,"line":179},[81,1531,1532],{"class":135},"      \"url\"",[81,1534,1472],{"class":91},[81,1536,1537],{"class":98},"\"https:\u002F\u002Falexrel.com\"",[81,1539,1478],{"class":91},[81,1541,1542,1545,1547,1550],{"class":83,"line":191},[81,1543,1544],{"class":135},"      \"logo\"",[81,1546,1472],{"class":91},[81,1548,1549],{"class":98},"\"https:\u002F\u002Falexrel.com\u002Flogo.png\"",[81,1551,1478],{"class":91},[81,1553,1554,1557],{"class":83,"line":204},[81,1555,1556],{"class":135},"      \"sameAs\"",[81,1558,1486],{"class":91},[81,1560,1561,1564],{"class":83,"line":210},[81,1562,1563],{"class":98},"        \"https:\u002F\u002Fgithub.com\u002Falexrel\"",[81,1565,1478],{"class":91},[81,1567,1568],{"class":83,"line":215},[81,1569,1570],{"class":98},"        \"https:\u002F\u002Flinkedin.com\u002Fin\u002Falexrel\"\n",[81,1572,1573],{"class":83,"line":225},[81,1574,1575],{"class":91},"      ]\n",[81,1577,1578],{"class":83,"line":237},[81,1579,1580],{"class":91},"    },\n",[81,1582,1583],{"class":83,"line":249},[81,1584,1491],{"class":91},[81,1586,1587,1589,1591,1594],{"class":83,"line":254},[81,1588,1496],{"class":135},[81,1590,1472],{"class":91},[81,1592,1593],{"class":98},"\"Service\"",[81,1595,1478],{"class":91},[81,1597,1598,1600,1602,1605],{"class":83,"line":259},[81,1599,1508],{"class":135},[81,1601,1472],{"class":91},[81,1603,1604],{"class":98},"\"https:\u002F\u002Falexrel.com\u002Fsaas-development#service\"",[81,1606,1478],{"class":91},[81,1608,1609,1611,1613,1616],{"class":83,"line":300},[81,1610,1520],{"class":135},[81,1612,1472],{"class":91},[81,1614,1615],{"class":98},"\"B2B SaaS Architecture and Development\"",[81,1617,1478],{"class":91},[81,1619,1620,1623,1625,1628],{"class":83,"line":321},[81,1621,1622],{"class":135},"      \"description\"",[81,1624,1472],{"class":91},[81,1626,1627],{"class":98},"\"Full-cycle SaaS development from database schema isolation to Stripe webhook billing and vector support bots.\"",[81,1629,1478],{"class":91},[81,1631,1632,1635],{"class":83,"line":345},[81,1633,1634],{"class":135},"      \"provider\"",[81,1636,1637],{"class":91},": {\n",[81,1639,1640,1643,1645],{"class":83,"line":351},[81,1641,1642],{"class":135},"        \"@id\"",[81,1644,1472],{"class":91},[81,1646,1647],{"class":98},"\"https:\u002F\u002Falexrel.com\u002F#organization\"\n",[81,1649,1650],{"class":83,"line":366},[81,1651,1652],{"class":91},"      },\n",[81,1654,1655,1658,1660,1663],{"class":83,"line":381},[81,1656,1657],{"class":135},"      \"areaServed\"",[81,1659,1472],{"class":91},[81,1661,1662],{"class":98},"\"Worldwide\"",[81,1664,1478],{"class":91},[81,1666,1667,1670],{"class":83,"line":387},[81,1668,1669],{"class":135},"      \"offers\"",[81,1671,1637],{"class":91},[81,1673,1674,1677,1679,1682],{"class":83,"line":392},[81,1675,1676],{"class":135},"        \"@type\"",[81,1678,1472],{"class":91},[81,1680,1681],{"class":98},"\"Offer\"",[81,1683,1478],{"class":91},[81,1685,1686,1689,1691,1694],{"class":83,"line":399},[81,1687,1688],{"class":135},"        \"priceCurrency\"",[81,1690,1472],{"class":91},[81,1692,1693],{"class":98},"\"USD\"",[81,1695,1478],{"class":91},[81,1697,1698,1701,1703,1706],{"class":83,"line":452},[81,1699,1700],{"class":135},"        \"price\"",[81,1702,1472],{"class":91},[81,1704,1705],{"class":98},"\"5000\"",[81,1707,1478],{"class":91},[81,1709,1710,1713],{"class":83,"line":457},[81,1711,1712],{"class":135},"        \"priceSpecification\"",[81,1714,1637],{"class":91},[81,1716,1717,1720,1722,1725],{"class":83,"line":463},[81,1718,1719],{"class":135},"          \"@type\"",[81,1721,1472],{"class":91},[81,1723,1724],{"class":98},"\"UnitPriceSpecification\"",[81,1726,1478],{"class":91},[81,1728,1729,1732,1734,1737],{"class":83,"line":476},[81,1730,1731],{"class":135},"          \"priceType\"",[81,1733,1472],{"class":91},[81,1735,1736],{"class":98},"\"https:\u002F\u002Fschema.org\u002FMinimumPrice\"",[81,1738,1478],{"class":91},[81,1740,1741,1744,1746,1748],{"class":83,"line":518},[81,1742,1743],{"class":135},"          \"price\"",[81,1745,1472],{"class":91},[81,1747,1705],{"class":98},[81,1749,1478],{"class":91},[81,1751,1752,1755,1757],{"class":83,"line":538},[81,1753,1754],{"class":135},"          \"priceCurrency\"",[81,1756,1472],{"class":91},[81,1758,1759],{"class":98},"\"USD\"\n",[81,1761,1762],{"class":83,"line":543},[81,1763,1764],{"class":91},"        }\n",[81,1766,1767],{"class":83,"line":549},[81,1768,1769],{"class":91},"      }\n",[81,1771,1772],{"class":83,"line":555},[81,1773,1193],{"class":91},[81,1775,1776],{"class":83,"line":568},[81,1777,917],{"class":91},[81,1779,1780],{"class":83,"line":588},[81,1781,207],{"class":91},[26,1783,1785],{"id":1784},"_3-resolving-client-side-rendering-csr-issues-for-ai-bots","3. Resolving Client-Side Rendering (CSR) Issues for AI Bots",[11,1787,1788],{},"Unlike Googlebot, which allocates massive CPU resources to render JavaScript and execute client-side hydration, many AI search scraper agents are lightweight tools that pull raw HTML responses to minimize processing time.",[11,1790,1791,1792,1795],{},"If your web application relies entirely on client-side rendering (e.g., a standard single-page React or Vue app with an empty ",[42,1793,1794],{},"\u003Cdiv id=\"app\">\u003C\u002Fdiv>","), AI crawlers might only see a blank page, excluding your brand from their index.",[864,1797,1799],{"id":1798},"how-to-solve-this-in-nuxt-3","How to solve this in Nuxt 3:",[1233,1801,1802,1815],{},[1236,1803,1804,1807,1808,1811,1812,62],{},[15,1805,1806],{},"Enable Server-Side Rendering (SSR)",": Ensure ",[42,1809,1810],{},"ssr: true"," is configured in your ",[42,1813,1814],{},"nuxt.config.ts",[1236,1816,1817,1820,1821,1823,1824,1827],{},[15,1818,1819],{},"Use Pre-rendering for Static Pages",": Configure routes like ",[42,1822,1323],{}," and ",[42,1825,1826],{},"\u002Fsaas-development"," to build as static HTML files during deployment:",[34,1829,1831],{"className":75,"code":1830,"language":77,"meta":40,"style":40},"\u002F\u002F nuxt.config.ts\nexport default defineNuxtConfig({\n  routeRules: {\n    '\u002F': { prerender: true },\n    '\u002Fsaas-development': { prerender: true },\n    '\u002Ftelegram-bots': { prerender: true },\n    '\u002Fgeo-seo': { prerender: true },\n    '\u002Fconsultations': { prerender: true }\n  }\n});\n",[42,1832,1833,1838,1850,1855,1868,1879,1890,1901,1912,1916],{"__ignoreMap":40},[81,1834,1835],{"class":83,"line":84},[81,1836,1837],{"class":395},"\u002F\u002F nuxt.config.ts\n",[81,1839,1840,1842,1845,1848],{"class":83,"line":105},[81,1841,262],{"class":87},[81,1843,1844],{"class":87}," default",[81,1846,1847],{"class":118}," defineNuxtConfig",[81,1849,891],{"class":91},[81,1851,1852],{"class":83,"line":112},[81,1853,1854],{"class":91},"  routeRules: {\n",[81,1856,1857,1860,1863,1865],{"class":83,"line":125},[81,1858,1859],{"class":98},"    '\u002F'",[81,1861,1862],{"class":91},": { prerender: ",[81,1864,819],{"class":135},[81,1866,1867],{"class":91}," },\n",[81,1869,1870,1873,1875,1877],{"class":83,"line":141},[81,1871,1872],{"class":98},"    '\u002Fsaas-development'",[81,1874,1862],{"class":91},[81,1876,819],{"class":135},[81,1878,1867],{"class":91},[81,1880,1881,1884,1886,1888],{"class":83,"line":154},[81,1882,1883],{"class":98},"    '\u002Ftelegram-bots'",[81,1885,1862],{"class":91},[81,1887,819],{"class":135},[81,1889,1867],{"class":91},[81,1891,1892,1895,1897,1899],{"class":83,"line":167},[81,1893,1894],{"class":98},"    '\u002Fgeo-seo'",[81,1896,1862],{"class":91},[81,1898,819],{"class":135},[81,1900,1867],{"class":91},[81,1902,1903,1906,1908,1910],{"class":83,"line":179},[81,1904,1905],{"class":98},"    '\u002Fconsultations'",[81,1907,1862],{"class":91},[81,1909,819],{"class":135},[81,1911,912],{"class":91},[81,1913,1914],{"class":83,"line":191},[81,1915,384],{"class":91},[81,1917,1918],{"class":83,"line":204},[81,1919,1920],{"class":91},"});\n",[11,1922,1923],{},"Enforcing static rendering ensures that whenever GPTBot or ClaudeBot fetches your pages, they receive full, semantic text structures immediately.",[11,1925,1926,1927,1823,1931,1933],{},"When establishing the indexing architecture for ",[1274,1928,1930],{"href":1929},"\u002Fprojects\u002Flingvohabit","LingvoHabit",[1274,1932,1277],{"href":1276},", we deployed these exact pre-rendered configurations and nested schemas. This resulted in immediate citations across major AI search indexes.",[26,1935,1294],{"id":1293},[1296,1937,1938,1946,1954,1962],{},[1236,1939,1940,1945],{},[1274,1941,1944],{"href":1942,"rel":1943},"https:\u002F\u002Fplatform.openai.com\u002Fdocs\u002Fbots",[1304],"OpenAI crawler documentation"," — current GPTBot and OAI-SearchBot user agents",[1236,1947,1948,1953],{},[1274,1949,1952],{"href":1950,"rel":1951},"https:\u002F\u002Fsupport.anthropic.com\u002Fen\u002Farticles\u002F8896518-does-anthropic-crawl-data-from-the-web-and-how-can-site-owners-block-the-crawler",[1304],"Anthropic: how ClaudeBot works"," — official Claude crawler rules",[1236,1955,1956,1961],{},[1274,1957,1960],{"href":1958,"rel":1959},"https:\u002F\u002Fschema.org",[1304],"Schema.org"," — the full structured-data vocabulary",[1236,1963,1964,1969],{},[1274,1965,1968],{"href":1966,"rel":1967},"https:\u002F\u002Fdevelopers.google.com\u002Fsearch\u002Fdocs\u002Fcrawling-indexing\u002Frobots\u002Fintro",[1304],"Google: intro to robots.txt"," — directive syntax and precedence",[11,1971,1972,1973,62],{},"Structuring your technical SEO layer for AI scraping ensures that your site functions as an open, readable directory for the LLM agents dominating the next generation of web search. To learn how this technical layout integrates with a full AI-first marketing strategy, check out my introduction to ",[1274,1974,1976],{"href":1975},"\u002Fblog\u002Fgenerative-engine-optimization-geo","Generative Engine Optimization (GEO)",[11,1978,1979,1980,1984,1985,62],{},"If you need a professional developer to implement structured JSON-LD schemas, configure SSR routes, or audit your robots.txt setup, visit my ",[1274,1981,1983],{"href":1982},"\u002Fgeo-seo","GEO & SEO Services"," page or book a ",[1274,1986,1987],{"href":1328},"Technical Consultation",[1332,1989,1990],{},"html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#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 pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":40,"searchDepth":105,"depth":105,"links":1992},[1993,1994,1995,1998],{"id":1412,"depth":105,"text":1413},{"id":1431,"depth":105,"text":1432},{"id":1784,"depth":105,"text":1785,"children":1996},[1997],{"id":1798,"depth":112,"text":1799},{"id":1293,"depth":105,"text":1294},"2026-06-28","A technical masterclass on configuring robots.txt controls for AI agents, implementing nested JSON-LD schema graphs, and solving client-side rendering hurdles.",[2002,2005,2008],{"q":2003,"a":2004},"Should I allow every AI bot to crawl my site?","No. Separate bots by purpose: search and answer bots (GPTBot, OAI-SearchBot, ClaudeBot, PerplexityBot) are worth allowing — they bring citations and traffic. Pure training crawlers like CCBot can be blocked: they collect data without sending anything back.",{"q":2006,"a":2007},"Will AI crawlers see the content of my Vue or React SPA?","Not reliably. Some AI bots don't execute JavaScript, so a client-rendered app looks like an empty page to them. The fix is SSR or prerendering: the server must return complete HTML with text and markup.",{"q":2009,"a":2010},"Which JSON-LD markup matters most for AI search?","A linked @graph with Organization\u002FPerson (who you are), Service or Product (what you offer), and WebPage with dateModified. It's critical that the markup matches the visible content exactly — divergence undermines trust in the source.","\u002Fimages\u002Fblog\u002Fblog_ai_crawlers.jpg",{},"\u002Fblog\u002Fen\u002Fwebsite-optimization-ai-crawlers",{"title":1374,"description":2000},"blog\u002Fen\u002Fwebsite-optimization-ai-crawlers",[2017,2018,1408,2019,2020],"GEO","SEO","Schema","Technical","jymLTxFvhUq1G9stPtwy9EpBR6NrdTfbYFw6cv0jNcQ",1784561425809]