[{"data":1,"prerenderedAt":1128},["ShallowReactive",2],{"blog-uk-failed-to-send-a-request-to-the-edge-function":3,"blog-index-uk":599},{"id":4,"title":5,"body":6,"category":555,"cover":556,"coverAlt":557,"description":558,"draft":559,"extension":560,"faq":561,"image":579,"keywords":580,"meta":587,"navigation":588,"ogTitle":589,"path":590,"published":591,"seo":592,"stem":593,"tldr":594,"updated":591,"__hash__":598},"blog_uk\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function.md","\"Failed to send a request to the Edge Function\": справжня причина",{"type":7,"value":8,"toc":544},"minimark",[9,13,24,27,35,40,43,65,68,83,95,99,102,114,121,127,136,153,159,163,166,254,271,280,286,305,324,328,331,350,358,366,373,376,388,394,399,403,406,415,423,426,441,445,451,460,475,485,502,506,529],[10,11,12],"p",{},"У вашому застосунку є кнопка, яка викликає Supabase Edge Function, той\nневеликий шматок серверного коду, яким застосунок, зроблений у Lovable, Bolt,\nCursor чи Claude Code, користується для роботи, що не може відбуватися в\nбраузері, наприклад для виклику OpenAI з ключем. Ви натискаєте її, і замість\nрезультату застосунок показує ось це:",[14,15,20],"pre",{"className":16,"code":18,"language":19},[17],"language-text","Failed to send a request to the Edge Function\n","text",[21,22,18],"code",{"__ignoreMap":23},"",[10,25,26],{},"Функцію задеплоєно. Якщо викликати її з термінала, вона, можливо, навіть\nвідповідає бездоганно. З вашого застосунку вона щоразу падає, і \"failed to send\na request to the Edge Function\" звучить так, ніби ваш запит нікуди не пішов.",[10,28,29,30,34],{},"Цілком можливо, що пішов. ",[31,32,33],"strong",{},"Ваша функція могла відпрацювати, зробити свою\nроботу й відповісти, а браузер відмовився віддати цю відповідь вашому\nзастосунку."," Що саме з цього сталося, записано лише в одному місці, і це не та\nпомилка, яку отримав ваш застосунок.",[36,37,39],"h2",{"id":38},"що-означає-failed-to-send-a-request-to-the-edge-function","Що означає \"Failed to send a request to the Edge Function\"?",[10,41,42],{},"Це означає, що supabase-js попросив відповідь і не отримав жодної, яку йому\nдозволено прочитати.",[10,44,45,46,53,54,57,58,64],{},"Це речення записане незмінним текстом усередині\n",[47,48,52],"a",{"href":49,"rel":50},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fsupabase-js\u002Fblob\u002Fmaster\u002Fpackages\u002Fcore\u002Ffunctions-js\u002Fsrc\u002Ftypes.ts",[51],"nofollow","supabase-js","\nі належить помилці з назвою ",[21,55,56],{},"FunctionsFetchError",". supabase-js кидає її, коли не\nвдається сам запит, ще до того, як зʼявиться бодай якийсь код статусу. Цим вона й\nвідрізняється від своєї відомішої сестри:\n",[47,59,61],{"href":60},"\u002Fblog\u002Fedge-function-returned-a-non-2xx-status-code",[21,62,63],{},"Edge Function returned a non-2xx status code","\nозначає, що відповідь повернулася і була помилкою. Ця ж означає, що жодної\nвідповіді, яку можна прочитати, не повернулося взагалі.",[10,66,67],{},"Запит падає так із кількох причин. Пристрій без мережі, адреса проєкту у вашому\nзастосунку неправильна, або правила браузера для інших адрес відхилили\nвідповідь. Ці правила називаються CORS, скорочення від Cross-Origin Resource\nSharing, і вони не дають вебсторінці читати відповідь з іншої адреси, якщо сама\nвідповідь цього не дозволяє. Коли функція відповідає з термінала й падає лише у\nвашому застосунку, підозрювати варто CORS, і браузер скаже, чи це він.",[10,69,70,71,74,75,78,79,82],{},"Ваш застосунок живе за однією адресою, скажімо ",[21,72,73],{},"yourapp.lovable.app",", а ваша\nфункція за іншою, ",[21,76,77],{},"\u003Cproject-ref>.supabase.co",". Уявіть браузер як швейцара вашого\nзастосунку. Пошту з іншої адреси він віддає застосунку, лише якщо на ній є ярлик\nвідправника про те, що вашому застосунку її можна отримати: заголовок відповіді\nз назвою ",[21,80,81],{},"Access-Control-Allow-Origin",". Перед листом на кшталт вашого швейцар\nспершу стукає у двері відправника й питає, чи той узагалі такий приймає. А коли\nщось відхиляє, вашому застосунку каже лише, що не вийшло. Причину він записує у\nсвій журнал.",[10,84,85,86,89,90,94],{},"Третє повідомлення з тієї самої родини. ",[21,87,88],{},"Relay Error invoking the Edge Function","\nце supabase-js повідомляє, що relay Supabase не зміг дістатися вашої функції,\nтож ",[47,91,93],{"href":92},"\u002Fblog\u002Fis-supabase-down-or-your-app","перевірте, чи немає проблеми в Supabase",",\nперш ніж змінювати код.",[36,96,98],{"id":97},"чому-ваш-застосунок-не-дізнається-більше","Чому ваш застосунок не дізнається більше",[10,100,101],{},"Бо браузер навмисно ховає причину від вашого коду.",[10,103,104,109,110,113],{},[47,105,108],{"href":106,"rel":107},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FHTTP\u002FGuides\u002FCORS\u002FErrors",[51],"Сторінка MDN про помилки CORS","\nкаже це прямо: з міркувань безпеки код JavaScript не має доступу до подробиць\nтого, що пішло не так у запиті CORS. Ваш застосунок дізнається, що щось не\nвдалося, і більше нічого. У Chrome помилка, яку обгортає supabase-js, звучить як\n",[21,111,112],{},"TypeError: Failed to fetch",", і Chromium використовує цей текст однаково для\nбудь-якого мережевого збою, тож виводити її немає сенсу. За тією самою\nсторінкою MDN, єдиний спосіб дізнатися, що сталося, це консоль браузера.",[10,115,116,117,120],{},"Це і є журнал швейцара. Відкрийте опублікований застосунок у Chrome або Edge,\nнатисніть F12, оберіть ",[31,118,119],{},"Console"," і ще раз натисніть кнопку в застосунку.\nПотрібний рядок червоний і починається так:",[14,122,125],{"className":123,"code":124,"language":19},[17],"Access to fetch at 'https:\u002F\u002F\u003Cproject-ref>.supabase.co\u002Ffunctions\u002Fv1\u002F\u003Cname>' from origin 'https:\u002F\u002Fyourapp.lovable.app' has been blocked by CORS policy:\n",[21,126,124],{"__ignoreMap":23},[10,128,129,130,135],{},"Усе після двокрапки і є причина. Firefox і Safari формулюють це інакше; цитати в\nцьому дописі взяті з Chrome, з\n",[47,131,134],{"href":132,"rel":133},"https:\u002F\u002Fgithub.com\u002Fchromium\u002Fchromium\u002Fblob\u002Fmain\u002Fthird_party\u002Fblink\u002Frenderer\u002Fplatform\u002Floader\u002Fcors\u002Fcors_error_string.cc",[51],"вихідного коду Chromium",".",[10,137,138,139,142,143,146,147,152],{},"Стук у двері має технічну назву, preflight request. Це запит із методом\n",[21,140,141],{},"OPTIONS",", який браузер надсилає перед вашим, і supabase-js завжди його\nспричиняє: він надсилає ваш ключ у заголовку ",[21,144,145],{},"Authorization",", а дані у форматі\nJSON, і ",[47,148,151],{"href":149,"rel":150},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FHTTP\u002FGuides\u002FCORS",[51],"посібник MDN про CORS","\nне відносить жодне з цього до того, що сторінка може надіслати, не спитавши\nспершу.",[154,155],"diagram",{"alt":156,"caption":157,"src":158},"Дві спроби того самого виклику. Угорі відповідь на стук OPTIONS від браузера повертається з порожнім контуром там, де мав бути її ярлик, браузер її відхиляє, і запит POST так і не виходить. Унизу відповідь на стук має свій ярлик і проходить, запит POST доходить до функції, яка робить свою роботу, а відповідь повертається без ярлика й зупиняється на перевірці браузера.","Угорі стук відхилено, і ваш запит так і не виходить. Унизу стук проходить, функція робить роботу, а затримують лише її відповідь.","\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function\u002Fthe-knock-first-1600x760.png",[36,160,162],{"id":161},"яка-половина-не-вдалася-і-чи-запускалася-ваша-функція","Яка половина не вдалася, і чи запускалася ваша функція?",[10,164,165],{},"Це вирішують слова \"preflight request\" у рядку консолі. Якщо вони там є,\nбраузер зупинився на стуку, і ваш запит так і не надіслали. Якщо їх немає, ваш\nзапит пройшов, функція відпрацювала, а її відповідь затримали.",[167,168,169,185],"table",{},[170,171,172],"thead",{},[173,174,175,179,182],"tr",{},[176,177,178],"th",{},"Рядок консолі закінчується на",[176,180,181],{},"Що сталося",[176,183,184],{},"Чи зробила функція роботу?",[186,187,188,202,215,227,243],"tbody",{},[173,189,190,196,199],{},[191,192,193],"td",{},[21,194,195],{},"Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.",[191,197,198],{},"На стук відповіли без ярлика",[191,200,201],{},"Ні. До неї дійшов лише стук",[173,203,204,209,212],{},[191,205,206],{},[21,207,208],{},"Response to preflight request doesn't pass access control check: It does not have HTTP ok status.",[191,210,211],{},"На стук відповіли помилкою",[191,213,214],{},"Ні",[173,216,217,222,225],{},[191,218,219],{},[21,220,221],{},"Request header field \u003Cname> is not allowed by Access-Control-Allow-Headers in preflight response.",[191,223,224],{},"Ваш застосунок надсилає заголовок, якого немає у списку функції",[191,226,214],{},[173,228,229,235,238],{},[191,230,231,234],{},[21,232,233],{},"No 'Access-Control-Allow-Origin' header is present on the requested resource.",", без згадки про preflight",[191,236,237],{},"Стук пройшов, ваш запит дійшов, а відповідь була без ярлика",[191,239,240],{},[31,241,242],{},"Так",[173,244,245,248,251],{},[191,246,247],{},"Жодного рядка про CORS",[191,249,250],{},"Не CORS: зʼєднання, адреса або тайм-аут, заданий вашим кодом",[191,252,253],{},"Перегляньте вкладку Invocations",[10,255,256,261,266,267,270],{},[31,257,258,259,135],{},"Перший рядок дає функція без обробника для ",[21,260,141],{},[47,262,265],{"href":263,"rel":264},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Ftroubleshooting\u002Fedge-function-500-error-response",[51],"Сторінка Supabase про помилку 500","\nописує це з боку функції: стук доходить до вашого коду, як будь-який інший\nзапит, і функція пише в лог ",[21,268,269],{},"SyntaxError: Unexpected end of JSON input",", бо стук\nу двері не має вмісту, а код спробував його прочитати. Її відповідь є помилкою\nбез заголовків, і Chrome повідомляє про відсутній заголовок, ще не дивлячись на\nстатус. Якщо цей рядок є на вкладці Logs функції, ви знайшли причину.",[10,272,273,276,277,279],{},[31,274,275],{},"Другий рядок означає, що стук отримав заголовки, а разом із ними помилку.","\nОдин зі шляхів до цього: перевірка над гілкою для ",[21,278,141],{},", яку стук не може\nпройти, наприклад перевірка того, хто викликає. Стук не несе жодного з ваших\nзаголовків, зокрема й ключа, тож щоразу її не проходить.",[10,281,282,285],{},[31,283,284],{},"На четвертому рядку варто зупинитися, бо роботу зроблено."," Якщо функція\nзберігає рядок, надсилає лист чи списує гроші з картки, це вже сталося, і\nповторне натискання кнопки зробить це вдруге. Не вдалася відповідь, яка пішла\nбез ярлика. Спершу шукайте помилку: збій, відповідь на який ваш код так і не\nвстиг написати, або гілку помилки, що повертає відповідь без заголовків. Та сама\nсторінка Supabase попереджає, що тоді браузер звинувачує CORS у збої, справжня\nпричина якого інша.",[10,287,288,289,292,293,296,297,300,301,304],{},"Тож прочитайте власний журнал функції, перш ніж чіпати будь-які заголовки. У\nпанелі Supabase відкрийте ",[31,290,291],{},"Edge Functions",", оберіть функцію й перегляньте її\nвкладки ",[31,294,295],{},"Invocations"," і ",[31,298,299],{},"Logs"," за ту хвилину, коли ви натиснули кнопку.\n",[47,302,303],{"href":60},"Як прочитати справжню помилку","\nрозбирає обидві, а ще те, що означає кожен код статусу.",[10,306,307,310,311,314,315,318,319,323],{},[31,308,309],{},"Останній рядок означає, що швейцар нічого не відхиляв."," Запит не пройшов\nдорогою: пристрій утратив зʼєднання, або адреси проєкту, яку викликає ваш\nзастосунок, не існує. Один випадок походить із вашого власного коду. Якщо він\nпередає ",[21,312,313],{},"timeout"," у ",[21,316,317],{},"supabase.functions.invoke",",\n",[47,320,52],{"href":321,"rel":322},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fsupabase-js\u002Fblob\u002Fmaster\u002Fpackages\u002Fcore\u002Ffunctions-js\u002Fsrc\u002FFunctionsClient.ts",[51],"\nскасовує запит, щойно час вичерпано, і повідомляє те саме речення, хоча функція,\nможливо, ще працює.",[36,325,327],{"id":326},"як-виправити-відповісти-на-стук-і-причепити-ярлик-до-кожної-відповіді","Як виправити: відповісти на стук і причепити ярлик до кожної відповіді",[10,329,330],{},"Дві зміни у функції, і вони йдуть в одному деплої.",[332,333,334,344],"ol",{},[335,336,337,343],"li",{},[31,338,339,340,342],{},"Гілка для ",[21,341,141],{}," найпершою",", до будь-якого рядка, що читає вміст або\nперевіряє того, хто викликає. Вона відповідає на стук заголовками CORS і\nнічим більше.",[335,345,346,349],{},[31,347,348],{},"Заголовки CORS у кожній відповіді, яку повертає функція",", і в успішних, і\nв помилках. Відповідь без них затримують, хоч би що в ній було.",[10,351,352,357],{},[47,353,356],{"href":354,"rel":355},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Ffunctions\u002Fcors",[51],"Посібник Supabase про CORS","\nбудує функцію приблизно так:",[14,359,364],{"className":360,"code":362,"language":363,"meta":23},[361],"language-ts","import { corsHeaders } from 'npm:@supabase\u002Fsupabase-js@^2\u002Fcors'\n\nDeno.serve(async (req) => {\n  if (req.method === 'OPTIONS') {\n    return Response.json({ ok: true }, { headers: corsHeaders })\n  }\n  try {\n    const body = await req.json()\n    \u002F\u002F the function's own work goes here\n    return Response.json({ done: true }, { headers: corsHeaders })\n  } catch (error) {\n    const message = error instanceof Error ? error.message : String(error)\n    return Response.json({ error: message }, { status: 400, headers: corsHeaders })\n  }\n})\n","ts",[21,365,362],{"__ignoreMap":23},[10,367,368,369,372],{},"Заголовки беруться із самого supabase-js, а не зі списку, вставленого у функцію.\nПосібник імпортує їх так, починаючи з версії supabase-js 2.95.0, щоб список\nзбігався з тим, що надсилають клієнтські бібліотеки, і радить задеплоїти\nфункцію заново після оновлення. Старіші функції мають написану вручну копію того\nсамого списку у файлі з назвою ",[21,370,371],{},"_shared\u002Fcors.ts",". Саме з цієї копії береться\nтретій рядок таблиці: заголовок, який надсилає ваш застосунок і якого немає у\nсписку.",[10,374,375],{},"Якщо ви просите Lovable, Bolt, Cursor чи Claude Code зробити виправлення, ці два\nпронумеровані пункти і є промпт. Назвіть обидва. Другий легко пропустити, і\nфункція, де є лише перший, під час наступного збою переходить із першого рядка\nтаблиці на четвертий.",[10,377,378,379,382,383,387],{},"Новіші приклади Supabase загортають усю функцію у ",[21,380,381],{},"withSupabase",", який\nвідповідає на стук за вас.\n",[47,384,386],{"href":385},"\u002Fblog\u002Fmove-a-secret-to-an-edge-function","Як перенести секрет в Edge Function","\nпоказує цю обгортку разом із перевіркою того, хто викликає, і саме про цю\nперевірку наступний розділ.",[10,389,390,391,393],{},"Коли заголовки є в кожній відповіді, функція, що падає, перестає показувати це\nповідомлення й починає показувати ",[21,392,63],{},". Тепер відповідь доходить до вашого застосунку, і причина всередині неї.",[154,395],{"alt":396,"caption":397,"src":398},"Функція надсилає три відповіді назад до перевірки браузера. Відповідь 200 з ярликом проходить і доходить до застосунку. Відповідь 500 без ярлика зупиняється на перевірці. Відповідь 400 з ярликом проходить, тож застосунок отримує помилку й може її прочитати.","Браузер шукає ярлик і ніколи не дивиться, що написано у відповіді. Помилка із заголовками доходить до вашого застосунку; помилку без них затримують і повідомляють як невдалий запит.","\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function\u002Ftag-every-reply-1600x720.png",[36,400,402],{"id":401},"чи-вирішує-заголовок-cors-хто-може-викликати-вашу-функцію","Чи вирішує заголовок CORS, хто може викликати вашу функцію?",[10,404,405],{},"Ні. Він вирішує, які вебсторінки можуть прочитати відповідь, і дивляться на\nнього лише браузери.",[10,407,408,409,314,411,414],{},"Власний список Supabase встановлює ",[21,410,81],{},[21,412,413],{},"*",", що\nдозволяє будь-яке джерело, і в копії, яку експортує supabase-js, і в старій,\nнаписаній вручну. Для функції, що перевіряє токен того, хто викликає, це\nнормально. Для функції, що нічого не перевіряє, це дає будь-якій сторінці\nбудь-якого сайту змогу викликати вашу функцію з браузерів своїх відвідувачів і\nчитати, що повертається.",[10,416,417,418,422],{},"Обмеження заголовка вашим власним доменом теж не закриває функцію. Скрипт,\nтермінал чи інший сервер надсилає той самий запит без браузера, ніколи не читає\nзаголовок і отримує всю відповідь.\n",[47,419,421],{"href":420},"\u002Fblog\u002Fcors-wildcard-security-risk","Що відкриває і чого не відкриває wildcard у CORS","\nрозбирає це докладно.",[10,424,425],{},"Замок на Edge Function: перевірка того, хто викликає, вписана у саму функцію.\nНайбільше вона важить для функцій, що опиняються за цією помилкою, бо саме вони\nтримають ключ OpenAI або секретний ключ вашого проєкту, а секретний ключ обходить\nкожне правило row level security, яке ви написали.",[10,427,428,429,432,433,436,437,135],{},"Інший вихід із помилки CORS полягає в тому, щоб перестати користуватися\nфункцією: покласти ключ у змінну з ",[21,430,431],{},"VITE_"," і викликати OpenAI прямо зі сторінки.\nЦе працює одразу й кладе ключ у файл, який завантажує кожен відвідувач. Це\nперше з ",[47,434,435],{"href":60},"двох виправлень, після яких вам гірше",".\nЦю помилку видно ззовні. Наше безкоштовне сканування читає JavaScript вашого\nопублікованого застосунку так, як прочитав би сторонній, і називає кожен ключ,\nякий знаходить, зокрема й те, чи йому там місце. Це займає близько 20 секунд і\nне потребує облікового запису:\n",[47,438,440],{"href":439},"\u002Fsecurity-scanner","проскануйте свій застосунок",[36,442,444],{"id":443},"щоб-ключ-не-опинився-на-сторінці-після-наступної-зміни","Щоб ключ не опинився на сторінці після наступної зміни",[10,446,447,450],{},[31,448,449],{},"Функцію, яку ви виправили сьогодні, можуть переписати наступного разу, коли\nви попросите свій білдер про нову функцію",", а найшвидший спосіб обійти помилку\nCORS кладе ваш ключ на сторінку.",[10,452,453],{},[31,454,455,459],{},[47,456,458],{"href":457},"\u002Fwebsite-monitoring","Reeve Monitor"," щогодини заново сканує ваш опублікований застосунок:",[461,462,463,466,469,472],"ul",{},[335,464,465],{},"усі девʼять перевірок щогодини, для трьох застосунків",[335,467,468],{},"лист того дня, коли нове сканування оцінить ваш застосунок гірше за попереднє; секретний ключ у вашому JavaScript сам обмежує оцінку рівнем D",[335,470,471],{},"чи застосунок доступний, кожні 60 секунд",[335,473,474],{},"щомісячний звіт про те, що він бачив",[10,476,477],{},[31,478,479,480,484],{},"Функція, що тримає секретний ключ вашого проєкту, може змінити будь-який рядок\nвашої бази для будь-кого, хто її викликає. ",[47,481,483],{"href":482},"\u002Fsupabase-backups","Reeve Care","\nтримає копію вашої бази Supabase там, куди ніщо у вашому проєкті не дістане.",[461,486,487,490,493,496,499],{},[335,488,489],{},"зашифрована копія за розкладом, у власному сховищі Reeve в ЄС, поза вашим обліковим записом Supabase",[335,491,492],{},"кожна копія перевірена, перш ніж її зарахують, підрахунком рядків у кожній таблиці",[335,494,495],{},"відновлення в один клік, яке зберігає те, що є зараз, перш ніж замінити",[335,497,498],{},"ваші завантажені файли теж, щойно ви приєднаєте доступ до Storage",[335,500,501],{},"усе, що робить Monitor",[36,503,505],{"id":504},"що-зробити-зараз","Що зробити зараз",[507,508,509],"key-takeaways",{},[461,510,511,514,520,523,526],{},[335,512,513],{},"Відкрийте консоль браузера в опублікованому застосунку, ще раз натисніть кнопку й прочитайте кінець червоного рядка про CORS.",[335,515,516,517,519],{},"Якщо в рядку згадано preflight request, додайте на самий початок функції гілку для ",[21,518,141],{},", яка повертає заголовки CORS.",[335,521,522],{},"Якщо ні, ваша функція відпрацювала. Перегляньте її вкладки Invocations і Logs і перевірте, що вона вже зробила, перш ніж хтось знову натисне кнопку.",[335,524,525],{},"Повертайте заголовки CORS у кожній відповіді, з помилками включно, й імпортуйте їх із supabase-js 2.95.0 або новішого, щоб список лишався актуальним.",[335,527,528],{},"Тримайте у функції перевірку того, хто викликає. Заголовок CORS лише каже браузерам, хто може прочитати відповідь.",[10,530,531,532,534,535,538,539,543],{},"Коли помилка у вашому застосунку зміниться на ",[21,533,63],{},", заголовки працюють і відповідь доходить;\n",[47,536,537],{"href":60},"той допис"," читає її далі.\n",[47,540,542],{"href":541},"\u002Fchecklist","Десятихвилинний чекліст безпеки"," охоплює, що ще новий застосунок\nзазвичай лишає відкритим.",{"title":23,"searchDepth":545,"depth":545,"links":546},3,[547,549,550,551,552,553,554],{"id":38,"depth":548,"text":39},2,{"id":97,"depth":548,"text":98},{"id":161,"depth":548,"text":162},{"id":326,"depth":548,"text":327},{"id":401,"depth":548,"text":402},{"id":443,"depth":548,"text":444},{"id":504,"depth":548,"text":505},"Основи безпеки","\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function\u002Fcover-1200x630.png","Опущений шлагбаум поперек проїзду, а за ним затриманий конверт і порожній жовтий контур там, де мав би висіти його ярлик.","\"Failed to send a request to the Edge Function\": браузер не отримав відповіді, яку можна прочитати. Його консоль каже, чому, і чи ваша функція запускалася.",false,"md",[562,564,567,570,573,576],{"q":39,"a":563},"Це означає, що supabase-js попросив відповідь у вашої Supabase Edge Function і не отримав жодної, яку йому дозволено прочитати. Або запит не пройшов дорогою, наприклад тому, що пристрій був без мережі, або браузер відмовився віддати відповідь вашому застосунку за своїми правилами для запитів на інші адреси, які називають CORS. Браузер навмисно ховає причину від вашого коду й пише її у свою консоль.",{"q":565,"a":566},"Чому моя Edge Function працює з термінала, але не з мого застосунку?","Бо CORS застосовують лише браузери. Термінал, скрипт чи інший сервер надсилає запит і читає всю відповідь. Браузер спершу перевіряє, чи відповідь дозволяє вашому застосунку її прочитати, а перед запитом на кшталт того, що надсилає supabase-js, ще й питає в функції дозволу окремим запитом OPTIONS. Якщо не вдається хоч один із цих кроків, ваш застосунок отримує цю помилку, хоча той самий виклик працює будь-де ще.",{"q":568,"a":569},"Чи запускалася моя Edge Function, якщо я бачу цю помилку?","Прочитайте червоний рядок у консолі браузера. Якщо в ньому згадано preflight request, браузер зупинився на запиті дозволу, і ваш запит так і не пішов. Якщо ні, запит пройшов, функція зробила свою роботу, а затримали лише відповідь. Тоді перегляньте вкладки Invocations і Logs функції, перш ніж знову натискати кнопку, бо друге натискання повторить роботу.",{"q":571,"a":572},"Як виправити помилку CORS у Supabase Edge Function?","Нехай функція першою справою відповідає на запит OPTIONS, ще до будь-якого коду, що читає вміст або перевіряє того, хто викликає, і повертає заголовки CORS у кожній відповіді, зокрема й у відповідях з помилкою. Посібник Supabase про CORS імпортує ці заголовки з supabase-js версії 2.95.0 або новішої, щоб список збігався з тим, що надсилає клієнт. Після зміни задеплойте функцію заново.",{"q":574,"a":575},"Що таке \"Relay Error invoking the Edge Function\"?","Це третє повідомлення supabase-js, яке зʼявляється, коли relay Supabase не зміг дістатися вашої функції. Це не відмова CORS і не відповідь вашого власного коду, тож спершу перевірте, чи немає проблеми в самого Supabase, а вже потім щось змінюйте.",{"q":577,"a":578},"Чи безпечно дозволяти будь-яке джерело в заголовках CORS моєї Edge Function?","Лише якщо функція перевіряє, хто її викликає. Заголовок CORS каже браузерам, які вебсторінки можуть прочитати відповідь; він нікому не заважає викликати функцію. Власні заголовки Supabase дозволяють будь-яке джерело. Для функції, що перевіряє токен того, хто викликає, це нормально, а функцію без перевірки так може використовувати будь-який сайт. Обмеження заголовка вашим доменом теж не закриває функцію від скриптів.","\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function\u002Fcard-800x500.png",[581,56,582,583,584,585,586],"failed to send a request to the edge function","помилка cors supabase edge function","supabase edge function blocked by cors policy","response to preflight request doesn't pass access control check","relay error invoking the edge function","lovable edge function cors",{},true,"\"Failed to send a request to the Edge Function\" у Supabase","\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function","2026-11-03",{"title":5,"description":558},"blog\u002Ffailed-to-send-a-request-to-the-edge-function",[595,596,597],"\"Failed to send a request to the Edge Function\" означає, що supabase-js не отримав жодної відповіді, яку йому дозволено прочитати. Або запит не пройшов дорогою, або браузер затримав відповідь, і ваш застосунок навмисно не дізнається більше.","Причина є в консолі браузера. Рядок, де згадано preflight request, означає, що ваш запит так і не пішов. Рядок без цієї згадки означає, що ваша функція відпрацювала, а затримали лише її відповідь.","Виправлення: гілка для OPTIONS на самому початку функції та заголовки CORS у кожній відповіді, з помилками включно. Ці заголовки лише кажуть браузерам, хто може прочитати відповідь; хто може викликати функцію, вирішує перевірка у вашому коді.","Xr8dSaH6nJFx66RhZhvzlc6qBAYfG8_dgoF03HBtQ8A",[600,606,612,618,624,630,636,637,643,649,655,662,668,674,679,685,691,697,703,709,715,721,727,733,739,745,751,757,763,769,775,781,787,793,799,805,810,816,822,828,833,839,845,851,857,863,868,874,880,886,892,898,904,910,916,922,928,934,940,946,952,958,964,970,976,982,988,994,1000,1006,1012,1018,1024,1030,1036,1042,1048,1053,1059,1065,1071,1077,1083,1089,1094,1100,1106,1112,1117,1123],{"path":601,"title":602,"description":603,"published":604,"category":555,"image":605,"draft":559},"\u002Fblog\u002Fmax-client-connections-reached","Supabase \"max client connections reached\": пояснення","Supabase \"max client connections reached\": у пулера немає місця ще для одного клієнта. Ліміт визначає порт у вашому рядку підключення.","2026-11-09","\u002Fblog\u002Fmax-client-connections-reached\u002Fcard-800x500.png",{"path":607,"title":608,"description":609,"published":610,"category":555,"image":611,"draft":559},"\u002Fblog\u002Funsupported-provider-provider-is-not-enabled","Помилка Supabase: \"Unsupported provider: provider is not enabled\"","\"Unsupported provider: provider is not enabled\": вхід через Google вимкнено у вашому проєкті Supabase. Як увімкнути та виправити redirect_uri_mismatch.","2026-11-08","\u002Fblog\u002Funsupported-provider-provider-is-not-enabled\u002Fcard-800x500.png",{"path":613,"title":614,"description":615,"published":616,"category":555,"image":617,"draft":559},"\u002Fblog\u002Fviolates-foreign-key-constraint","Помилка Supabase \"violates foreign key constraint\" під час вставки","Supabase \"insert or update on table violates foreign key constraint\": ваш рядок посилається на рядок, якого немає. Який, чому і як це виправити.","2026-11-07","\u002Fblog\u002Fviolates-foreign-key-constraint\u002Fcard-800x500.png",{"path":619,"title":620,"description":621,"published":622,"category":555,"image":623,"draft":559},"\u002Fblog\u002Fnull-value-violates-not-null-constraint","Помилка Supabase \"null value in column violates not-null constraint\"","Supabase \"null value in column violates not-null constraint\": обовʼязковий стовпець прийшов порожнім. Звідки взявся null і як це виправити.","2026-11-06","\u002Fblog\u002Fnull-value-violates-not-null-constraint\u002Fcard-800x500.png",{"path":625,"title":626,"description":627,"published":628,"category":555,"image":629,"draft":559},"\u002Fblog\u002Fauth-session-missing","Supabase \"Auth session missing!\": куди поділася сесія","\"Auth session missing!\" означає, що supabase-js не знайшла жодної сесії й навіть не спитала Supabase. Куди поділася сесія і як це виправити.","2026-11-05","\u002Fblog\u002Fauth-session-missing\u002Fcard-800x500.png",{"path":631,"title":632,"description":633,"published":634,"category":555,"image":635,"draft":559},"\u002Fblog\u002Finvalid-claim-missing-sub-claim","Supabase \"invalid claim: missing sub claim\": ніхто не ввійшов","\"invalid claim: missing sub claim\": Supabase Auth отримав справжній токен без жодного користувача, ключ проєкту замість сесії.","2026-11-04","\u002Fblog\u002Finvalid-claim-missing-sub-claim\u002Fcard-800x500.png",{"path":590,"title":5,"description":558,"published":591,"category":555,"image":579,"draft":559},{"path":638,"title":639,"description":640,"published":641,"category":555,"image":642,"draft":559},"\u002Fblog\u002Fsupabase-auth-redirects-to-localhost","Чому Supabase перенаправляє посилання з листів на localhost:3000","Якщо Supabase перенаправляє на localhost:3000, Site URL так і не змінили або вашої адреси немає в Redirect URLs. Як виправити і якої маски уникати.","2026-11-02","\u002Fblog\u002Fsupabase-auth-redirects-to-localhost\u002Fcard-800x500.png",{"path":644,"title":645,"description":646,"published":647,"category":555,"image":648,"draft":559},"\u002Fblog\u002Femail-link-is-invalid-or-has-expired","Supabase \"Email link is invalid or has expired\" на новому посиланні","На посиланні, надісланому хвилину тому, \"Email link is invalid or has expired\" у Supabase зазвичай означає, що поштовий фільтр використав його першим.","2026-11-01","\u002Fblog\u002Femail-link-is-invalid-or-has-expired\u002Fcard-800x500.png",{"path":650,"title":651,"description":652,"published":653,"category":555,"image":654,"draft":559},"\u002Fblog\u002Fcanceling-statement-due-to-statement-timeout","Supabase \"canceling statement due to statement timeout\": пояснення","Supabase \"canceling statement due to statement timeout\": запит перевищив ліміт своєї ролі, 3 секунди для відвідувача. Спершу перевірте політику RLS.","2026-10-31","\u002Fblog\u002Fcanceling-statement-due-to-statement-timeout\u002Fcard-800x500.png",{"path":656,"title":657,"description":658,"published":659,"category":660,"image":661,"draft":559},"\u002Fblog\u002Fdatabase-error-deleting-user","\"Database error deleting user\" у Supabase і що зникає разом","\"Database error deleting user\" у Supabase: ваша база відмовилася видаляти. Причина в логах, а виправлення вирішує, що ще буде видалено.","2026-10-30","Backups","\u002Fblog\u002Fdatabase-error-deleting-user\u002Fcard-800x500.png",{"path":663,"title":664,"description":665,"published":666,"category":555,"image":667,"draft":559},"\u002Fblog\u002Fstripe-webhook-no-signatures-found","\"No signatures found matching the expected signature\" у Supabase","Вебхук Stripe в Edge Function Supabase пише \"No signatures found matching the expected signature\"? Причин дві. Не вимикайте перевірку.","2026-10-29","\u002Fblog\u002Fstripe-webhook-no-signatures-found\u002Fcard-800x500.png",{"path":669,"title":670,"description":671,"published":672,"category":555,"image":673,"draft":559},"\u002Fblog\u002Fsupabase-invalid-login","\"Invalid login credentials\" у Supabase: три причини","Supabase відповідає \"Invalid login credentials\" на неправильний пароль, невідому пошту й акаунт Google. Як зрозуміти, що саме.","2026-10-28","\u002Fblog\u002Fsupabase-invalid-login\u002Fcard-800x500.png",{"path":60,"title":675,"description":676,"published":677,"category":555,"image":678,"draft":559},"\"Edge Function returned a non-2xx status code\": справжня помилка","\"Edge Function returned a non-2xx status code\" означає, що ваша функція Supabase відповіла помилкою. Ось де прочитати справжню.","2026-10-27","\u002Fblog\u002Fedge-function-returned-a-non-2xx-status-code\u002Fcard-800x500.png",{"path":680,"title":681,"description":682,"published":683,"category":555,"image":684,"draft":559},"\u002Fblog\u002Fcertificate-not-trusted","Сертифікат не довірений: три причини, крім закінчення строку","Сертифікат не довірений, хоча дата в порядку? Він називає іншу адресу, підписав сам себе або в його ланцюжку бракує ланки. Як зрозуміти, що саме.","2026-10-26","\u002Fblog\u002Fcertificate-not-trusted\u002Fcard-800x500.png",{"path":686,"title":687,"description":688,"published":689,"category":555,"image":690,"draft":559},"\u002Fblog\u002Finvalid-input-syntax-for-type-uuid","Supabase \"invalid input syntax for type uuid\": що в лапках","Supabase \"invalid input syntax for type uuid\" означає, що застосунок надіслав щось, що не є id. Прочитайте лапки, притримайте запит, залиште колонку.","2026-10-25","\u002Fblog\u002Finvalid-input-syntax-for-type-uuid\u002Fcard-800x500.png",{"path":692,"title":693,"description":694,"published":695,"category":555,"image":696,"draft":559},"\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint","Supabase \"duplicate key value violates unique constraint\": пояснення","Supabase \"duplicate key value violates unique constraint\": значення вже зайняте. Знайдіть стовпець, оберіть виправлення й сховайте значення від очей.","2026-10-24","\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint\u002Fcard-800x500.png",{"path":698,"title":699,"description":700,"published":701,"category":555,"image":702,"draft":559},"\u002Fblog\u002Fsupabase-relation-does-not-exist","Supabase \"relation does not exist\", хоча таблиця на місці","Supabase \"relation does not exist\" або PGRST205: жодна таблиця не має такої назви в цій схемі. Чотири перевірки, і на що зважити, відкриваючи схему.","2026-10-23","\u002Fblog\u002Fsupabase-relation-does-not-exist\u002Fcard-800x500.png",{"path":704,"title":705,"description":706,"published":707,"category":555,"image":708,"draft":559},"\u002Fblog\u002Fsupabase-function-not-found","Supabase PGRST202: \"Could not find the function in the schema cache\"","Supabase \"Could not find the function in the schema cache\" показує назви аргументів, які надіслав ваш застосунок. Порівняйте їх із функцією.","2026-10-22","\u002Fblog\u002Fsupabase-function-not-found\u002Fcard-800x500.png",{"path":710,"title":711,"description":712,"published":713,"category":555,"image":714,"draft":559},"\u002Fblog\u002Fsupabase-schema-cache-column-error","Supabase PGRST204: \"Could not find the column in the schema cache\"","Supabase \"Could not find the column in the schema cache\" зазвичай означає, що назва не збігається. Чотири перевірки і рядок для перезавантаження.","2026-10-21","\u002Fblog\u002Fsupabase-schema-cache-column-error\u002Fcard-800x500.png",{"path":716,"title":717,"description":718,"published":719,"category":555,"image":720,"draft":559},"\u002Fblog\u002Fsupabase-email-rate-limit-exceeded","Supabase \"Email Rate Limit Exceeded\": два ліміти, один код помилки","Supabase \"Email Rate Limit Exceeded\" обмежує кожен лист вашого проєкту: 2 на годину з вбудованим відправником. Що враховується і як підняти ліміт.","2026-10-20","\u002Fblog\u002Fsupabase-email-rate-limit-exceeded\u002Fcard-800x500.png",{"path":722,"title":723,"description":724,"published":725,"category":555,"image":726,"draft":559},"\u002Fblog\u002Fsupabase-users-logged-out","Supabase \"Invalid Refresh Token: Refresh Token Not Found\": 3 причини","Supabase \"Invalid Refresh Token: Refresh Token Not Found\" виводить ваших користувачів із системи. Три причини і як лог Auth їх розрізняє.","2026-10-19","\u002Fblog\u002Fsupabase-users-logged-out\u002Fcard-800x500.png",{"path":728,"title":729,"description":730,"published":731,"category":555,"image":732,"draft":559},"\u002Fblog\u002Fpermission-denied-for-schema-public","Supabase \"permission denied for schema public\": три причини","Supabase \"permission denied for schema public\" означає, що бракує GRANT на схему. Що його прибрало і який рядок його повертає.","2026-10-18","\u002Fblog\u002Fpermission-denied-for-schema-public\u002Fcard-800x500.png",{"path":734,"title":735,"description":736,"published":737,"category":555,"image":738,"draft":559},"\u002Fblog\u002Fsupabase-bucket-not-found","Supabase \"Bucket not found\": три речі, які це може означати","Supabase \"Bucket not found\" означає одне з трьох: публічне посилання на приватний бакет, неточну назву або не той проєкт.","2026-10-17","\u002Fblog\u002Fsupabase-bucket-not-found\u002Fcard-800x500.png",{"path":740,"title":741,"description":742,"published":743,"category":555,"image":744,"draft":559},"\u002Fblog\u002Fsupabase-invalid-jwt","Supabase invalid JWT: який ключ перестав працювати?","Помилка Supabase invalid JWT — це PGRST301: підписаний токен не пройшов перевірку. У новому проєкті йдеться про сесію, а не про ключ API.","2026-10-16","\u002Fblog\u002Fsupabase-invalid-jwt\u002Fcard-800x500.png",{"path":746,"title":747,"description":748,"published":749,"category":555,"image":750,"draft":559},"\u002Fblog\u002Fsupabase-single-row-error","Supabase \"multiple (or no) rows returned\" майже завжди означає нуль","Supabase повертає PGRST116, \"JSON object requested, multiple (or no) rows returned\", коли запит нічого не знайшов. Прихований рядок має вигляд відсутнього.","2026-10-15","\u002Fblog\u002Fsupabase-single-row-error\u002Fcard-800x500.png",{"path":752,"title":753,"description":754,"published":755,"category":555,"image":756,"draft":559},"\u002Fblog\u002Fdatabase-error-saving-new-user","\"Database error saving new user\" у Supabase: де шукати","\"Database error saving new user\" означає, що щось причеплене до вашої реєстрації в Supabase впало. Справжня помилка лежить у ваших логах.","2026-10-14","\u002Fblog\u002Fdatabase-error-saving-new-user\u002Fcard-800x500.png",{"path":758,"title":759,"description":760,"published":761,"category":555,"image":762,"draft":559},"\u002Fblog\u002Fwhat-is-supabase","Що таке Supabase? База даних, яку обрав ваш білдер","Що таке Supabase: база даних, вхід і сховище файлів за вашим застосунком. Supabase тримає сервери, а правила про те, хто читає ваші рядки, ваші.","2026-10-13","\u002Fblog\u002Fwhat-is-supabase\u002Fcard-800x500.png",{"path":764,"title":765,"description":766,"published":767,"category":555,"image":768,"draft":559},"\u002Fblog\u002Faws-access-key-exposed-in-frontend","Відкритий у фронтенді ключ AWS це не те саме, що ключ OpenAI","Ключ доступу AWS, відкритий у вашому фронтенді, читає і пише ваше сховище. Рішення зазвичай presigned URL, а не новий ключ.","2026-10-12","\u002Fblog\u002Faws-access-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":770,"title":771,"description":772,"published":773,"category":555,"image":774,"draft":559},"\u002Fblog\u002Fstorage-upload-violates-row-level-security","\"Row-level security policy for table objects\" при вивантаженні","\"New row violates row-level security policy for table objects\" означає, що вашому вивантаженню бракує правила insert. Публічний бакет його не додає.","2026-10-11","\u002Fblog\u002Fstorage-upload-violates-row-level-security\u002Fcard-800x500.png",{"path":776,"title":777,"description":778,"published":779,"category":555,"image":780,"draft":559},"\u002Fblog\u002Fbase44-security-scan","Сканування безпеки Base44: те, що бачить лише воно","Сканування безпеки Base44 перевіряє сім видів проблем зсередини. Ось половина, яку читає тільки воно, і половина, на яку воно ніколи не дивиться.","2026-10-10","\u002Fblog\u002Fbase44-security-scan\u002Fcard-800x500.png",{"path":782,"title":783,"description":784,"published":785,"category":555,"image":786,"draft":559},"\u002Fblog\u002Fapi-key-leaked-what-to-do","Ваш API-ключ витік. Порядок дій","API-ключ витік, і ви не знаєте, що робити першим. Не кожен ключ у вашому фронтенді є витоком, а порядок важить більше за швидкість.","2026-10-09","\u002Fblog\u002Fapi-key-leaked-what-to-do\u002Fcard-800x500.png",{"path":788,"title":789,"description":790,"published":791,"category":555,"image":792,"draft":559},"\u002Fblog\u002Finfinite-recursion-in-policy-for-relation","\"Infinite recursion detected in policy\" без вимкнення RLS","\"Infinite recursion detected in policy for relation\" означає, що ваша policy запитала таблицю, яку вона захищає. Ось як розірвати коло.","2026-10-08","\u002Fblog\u002Finfinite-recursion-in-policy-for-relation\u002Fcard-800x500.png",{"path":794,"title":795,"description":796,"published":797,"category":555,"image":798,"draft":559},"\u002Fblog\u002Fno-api-key-found-in-request","\"No API key found in request\" у Supabase, і хибне виправлення","\"No API key found in request\" означає, що запит дійшов до Supabase без ключа. Більшість відповідей натомість вказують на правила вашої бази.","2026-10-07","\u002Fblog\u002Fno-api-key-found-in-request\u002Fcard-800x500.png",{"path":800,"title":801,"description":802,"published":803,"category":555,"image":804,"draft":559},"\u002Fblog\u002Fis-base44-safe","Чи безпечний Base44? Дані 5 442 застосунків","Чи безпечний Base44? Ми прогнали дев’ять перевірок на 5 442 робочих застосунках Base44. Майже всі отримали B з причин, яких власник ніколи не обирав.","2026-10-06","\u002Fblog\u002Fis-base44-safe\u002Fcard-800x500.png",{"path":385,"title":806,"description":807,"published":808,"category":555,"image":809,"draft":559},"Сховати ключ API: перенесіть його в Supabase Edge Function","Сховати ключ API означає прибрати його з браузера, а Supabase Edge Function це найменше місце, куди його покласти. Два кроки поруч важать більше.","2026-10-05","\u002Fblog\u002Fmove-a-secret-to-an-edge-function\u002Fcard-800x500.png",{"path":811,"title":812,"description":813,"published":814,"category":555,"image":815,"draft":559},"\u002Fblog\u002Fenv-file-exposed-on-your-server","Чи відкритий ваш файл .env? Дванадцять шляхів для перевірки","Чи відкритий ваш файл .env на вашому власному вебсервері? Дванадцять адрес скажуть це за хвилину, а влучання означає, що все в ньому вже публічне.","2026-10-04","\u002Fblog\u002Fenv-file-exposed-on-your-server\u002Fcard-800x500.png",{"path":817,"title":818,"description":819,"published":820,"category":555,"image":821,"draft":559},"\u002Fblog\u002Fis-v0-safe","Безпека v0: усі 1 790 просканованих застосунків v0 отримали A","Безпека v0 на 1 790 живих застосунках v0: усі отримали A. Лише 17 згадували базу даних, і саме це здебільшого вимірює оцінка A.","2026-10-03","\u002Fblog\u002Fis-v0-safe\u002Fcard-800x500.png",{"path":823,"title":824,"description":825,"published":826,"category":555,"image":827,"draft":559},"\u002Fblog\u002Fis-bolt-safe","Чи безпечний Bolt? Що показали 1 123 застосунки Bolt","Чи безпечний Bolt? Ми прогнали 1 123 живі застосунки Bolt через дев’ять перевірок. Хостинг був чистим. Знахідки: ключі й відкриті таблиці в самих застосунках.","2026-10-02","\u002Fblog\u002Fis-bolt-safe\u002Fcard-800x500.png",{"path":92,"title":829,"description":830,"published":831,"category":555,"image":832,"draft":559},"Supabase не працює чи проблема у вашому застосунку? Як розрізнити","Supabase не працює чи зламався ваш застосунок? Пауза, повна база й ваша власна помилка виглядають однаково. Чотири перевірки їх розрізняють.","2026-10-01","\u002Fblog\u002Fis-supabase-down-or-your-app\u002Fcard-800x500.png",{"path":834,"title":835,"description":836,"published":837,"category":555,"image":838,"draft":559},"\u002Fblog\u002Fdoes-supabase-encrypt-my-data","Чи шифрує Supabase мої дані? Так. Ось від чого це захищає","Чи шифрує Supabase дані? Так: AES-256 у стані спокою, TLS під час передавання, SOC 2 та ISO 27001. Що покриває кожне і який витік не зупиняє жодне.","2026-09-30","\u002Fblog\u002Fdoes-supabase-encrypt-my-data\u002Fcard-800x500.png",{"path":840,"title":841,"description":842,"published":843,"category":555,"image":844,"draft":559},"\u002Fblog\u002Fsupabase-free-plan-limits","Ліміти безкоштовного тарифу Supabase і що стається на кожному","Ліміти безкоштовного тарифу Supabase: 500 МБ бази, 5 ГБ egress і жодних резервних копій. Що стається на кожній межі і що змінює Pro за $25.","2026-09-29","\u002Fblog\u002Fsupabase-free-plan-limits\u002Fcard-800x500.png",{"path":846,"title":847,"description":848,"published":849,"category":660,"image":850,"draft":559},"\u002Fblog\u002Ftest-your-supabase-backup","Перевірте резервну копію Supabase до того, як вона знадобиться","Як перевірити резервну копію Supabase: відновити в тестовий проєкт, порахувати рядки, увійти й знайти рядок, якого бракує обрізаному файлу.","2026-09-28","\u002Fblog\u002Ftest-your-supabase-backup\u002Fcard-800x500.png",{"path":852,"title":853,"description":854,"published":855,"category":660,"image":856,"draft":559},"\u002Fblog\u002Fsupabase-backup-github-action","Безкоштовна резервна копія Supabase через GitHub Action і підступ","Резервна копія Supabase через GitHub Action безкоштовна й годиться багатьом застосункам. Робочий процес, потрібний рядок підключення та egress.","2026-09-27","\u002Fblog\u002Fsupabase-backup-github-action\u002Fcard-800x500.png",{"path":858,"title":859,"description":860,"published":861,"category":660,"image":862,"draft":559},"\u002Fblog\u002Fdownload-your-supabase-backup","Чому ви не можете завантажити резервну копію Supabase","Завантажити резервну копію Supabase на сучасному проєкті не вийде: щоденна копія там фізична. Як це перевірити і як тримати власну копію.","2026-09-26","\u002Fblog\u002Fdownload-your-supabase-backup\u002Fcard-800x500.png",{"path":864,"title":865,"description":866,"published":861,"category":555,"image":867,"draft":559},"\u002Fblog\u002Fsupabase-permission-denied-for-table","Supabase \"permission denied for table\": бракує GRANT","З 30 жовтня нова таблиця в Supabase відповідає \"permission denied for table\", доки ви не дасте доступ. Лист показує лише половину виправлення.","\u002Fblog\u002Fsupabase-permission-denied-for-table\u002Fcard-800x500.png",{"path":869,"title":870,"description":871,"published":872,"category":555,"image":873,"draft":559},"\u002Fblog\u002Fopen-api-endpoint-exposed","Чи є відкритий API-ендпоінт проблемою безпеки? Читайте JSON","Ваше сканування позначило відкритий API-ендпоінт. Чи це важливо, залежить від того, що повернулося, і більшість знайдених належали платформі.","2026-09-25","\u002Fblog\u002Fopen-api-endpoint-exposed\u002Fcard-800x500.png",{"path":875,"title":876,"description":877,"published":878,"category":555,"image":879,"draft":559},"\u002Fblog\u002Flovable-security-scan","Сканування безпеки Lovable: чого воно не доведе","Сканування безпеки Lovable: що перевіряють Quick scan і Deep scan, коли запускається кожне, і єдине, чого не доведе жодне сканування зсередини.","2026-09-24","\u002Fblog\u002Flovable-security-scan\u002Fcard-800x500.png",{"path":881,"title":882,"description":883,"published":884,"category":660,"image":885,"draft":559},"\u002Fblog\u002Fsupabase-backup-auth-users","Чому у вашому дампі Supabase немає користувачів","Запустіть supabase db dump окремо і отримаєте форму вашої бази та жодного її рядка, без схеми auth, у якій живуть ваші користувачі.","2026-09-23","\u002Fblog\u002Fsupabase-backup-auth-users\u002Fcard-800x500.png",{"path":887,"title":888,"description":889,"published":890,"category":555,"image":891,"draft":559},"\u002Fblog\u002Fdomain-and-certificate-expiry","Домен закінчився, сайт лежить: що буде далі","Ваш домен закінчився і сайт лежить. Ось який годинник уже пішов, чому прострочений сертифікат це менша з двох бід, і як перевірити обидва.","2026-09-22","\u002Fblog\u002Fdomain-and-certificate-expiry\u002Fcard-800x500.png",{"path":893,"title":894,"description":895,"published":896,"category":555,"image":897,"draft":559},"\u002Fblog\u002Fis-lovable-safe","Чи безпечний Lovable? Що показали 18 554 застосунки Lovable","Чи безпечний Lovable? Ми прогнали 18 554 живі застосунки Lovable через дев’ять перевірок. Платформа виявилася найчистішою з п’яти. Знахідки були в застосунках.","2026-09-21","\u002Fblog\u002Fis-lovable-safe\u002Fcard-800x500.png",{"path":899,"title":900,"description":901,"published":902,"category":555,"image":903,"draft":559},"\u002Fblog\u002Fvibe-coded-app-security-checklist","Чек-лист безпеки для vibe coding, у девʼяти перевірках","Чек-лист безпеки для vibe coding із девʼяти пунктів, кожен з яких будь-хто може перевірити у вашому живому застосунку ззовні, і кожен має тест на один рядок.","2026-09-20","\u002Fblog\u002Fvibe-coded-app-security-checklist\u002Fcard-800x500.png",{"path":905,"title":906,"description":907,"published":908,"category":555,"image":909,"draft":559},"\u002Fblog\u002Fstripe-secret-key-in-frontend","Секретний ключ Stripe у фронтенді може рухати гроші","Секретний ключ Stripe, відкритий у вашому фронтенді, може повертати кошти, списувати й читати кожну картку клієнта. А ключ pk_live_ там і має бути.","2026-09-19","\u002Fblog\u002Fstripe-secret-key-in-frontend\u002Fcard-800x500.png",{"path":911,"title":912,"description":913,"published":914,"category":555,"image":915,"draft":559},"\u002Fblog\u002Fvite-and-next-public-env-vars","Змінні оточення Vite відкриті: префікс означає опублікувати це","Змінні оточення Vite відкриті у вашому застосунку, бо так просив префікс. VITE_ і NEXT_PUBLIC_ означають опублікувати це, а ШІ, який їх додав, не знав ціни.","2026-09-18","\u002Fblog\u002Fvite-and-next-public-env-vars\u002Fcard-800x500.png",{"path":917,"title":918,"description":919,"published":920,"category":555,"image":921,"draft":559},"\u002Fblog\u002Fbase44-source-maps","Безпека Base44: що позначає скан і що з цього виправляти вам","Безпека Base44 на 5 438 просканованих застосунках: три знахідки майже на кожному належать платформі, source map є бейджем Base44, а ваша частина коротка.","2026-09-17","\u002Fblog\u002Fbase44-source-maps\u002Fcard-800x500.png",{"path":923,"title":924,"description":925,"published":926,"category":555,"image":927,"draft":559},"\u002Fblog\u002Fis-cursor-ai-safe","Чи безпечний Cursor AI? Редактор, код і опублікований застосунок","Чи безпечний Cursor AI? Три запитання в одному пошуку: що Cursor зберігає, у чому помиляється його код і чи відкритий застосунок, який ви опублікували.","2026-09-16","\u002Fblog\u002Fis-cursor-ai-safe\u002Fcard-800x500.png",{"path":929,"title":930,"description":931,"published":932,"category":555,"image":933,"draft":559},"\u002Fblog\u002Fis-replit-safe","Чи безпечний Replit? Що ми знайшли в 3 042 живих застосунках Replit","Чи безпечний Replit? Ми прогнали 3 042 живі застосунки Replit через дев’ять зовнішніх перевірок. Знахідки були в опублікованому застосунку, а не в хостингу.","2026-09-15","\u002Fblog\u002Fis-replit-safe\u002Fcard-800x500.png",{"path":935,"title":936,"description":937,"published":938,"category":660,"image":939,"draft":559},"\u002Fblog\u002Fsupabase-storage-backup","Supabase storage backup: у копії бази даних немає жодного файлу","Supabase storage backup це окрема робота. Копії бази даних зберігають список ваших файлів і жодного з них, тож після відновлення кожне завантаження зламане.","2026-09-14","\u002Fblog\u002Fsupabase-storage-backup\u002Fcard-800x500.png",{"path":941,"title":942,"description":943,"published":944,"category":660,"image":945,"draft":559},"\u002Fblog\u002Fsupabase-point-in-time-recovery","Point-in-time recovery у Supabase: ціна та межі","Point-in-time recovery у Supabase відмотує базу на будь-яку секунду за останній тиждень. Коштує $100 на місяць понад Pro і покриває лише базу даних.","2026-09-13","\u002Fblog\u002Fsupabase-point-in-time-recovery\u002Fcard-800x500.png",{"path":947,"title":948,"description":949,"published":950,"category":660,"image":951,"draft":559},"\u002Fblog\u002Fsupabase-project-paused-recover","Проєкт Supabase на паузі? Ваші дані нікуди не зникли","Supabase поставив ваш проєкт на паузу після тижня без активності. Нічого не видалено, кнопка Restore стоїть поруч із назвою, і у вас є рік, щоб натиснути.","2026-09-12","\u002Fblog\u002Fsupabase-project-paused-recover\u002Fcard-800x500.png",{"path":953,"title":954,"description":955,"published":956,"category":555,"image":957,"draft":559},"\u002Fblog\u002Fsafest-ai-app-builder","Який білдер застосунків з ШІ найбезпечніший? 30 998 тестів","Який білдер застосунків з ШІ найбезпечніший? Ми просканували 30 998 живих застосунків з Lovable, Base44, Replit, v0 і Bolt. Оцінку вирішує не білдер.","2026-09-11","\u002Fblog\u002Fsafest-ai-app-builder\u002Fcard-800x500.png",{"path":959,"title":960,"description":961,"published":962,"category":555,"image":963,"draft":559},"\u002Fblog\u002Fenable-rls-on-every-supabase-table","Увімкніть Row Level Security на кожній таблиці Supabase і перевірте","Row Level Security у Supabase без політики закриває таблицю повністю. Політика без увімкненого перемикача не робить нічого. Ось SQL, і ось перевірка.","2026-09-10","\u002Fblog\u002Fenable-rls-on-every-supabase-table\u002Fcard-800x500.png",{"path":965,"title":966,"description":967,"published":968,"category":555,"image":969,"draft":559},"\u002Fblog\u002Fsupabase-rls-disabled-in-public","Supabase \"RLS disabled in public\": чого не бачить попередження","Supabase позначає \"RLS disabled in public\" як помилку. Про політику читання, яка лишає вашу таблицю так само відкритою, він не каже нічого.","2026-09-09","\u002Fblog\u002Fsupabase-rls-disabled-in-public\u002Fcard-800x500.png",{"path":971,"title":972,"description":973,"published":974,"category":555,"image":975,"draft":559},"\u002Fblog\u002Frotate-supabase-service-role-key","Як замінити злитий ключ service_role у Supabase","Supabase каже спершу закрити витік. Інші посібники кажуть міняти негайно. Що правильно, залежить від того, куди саме потрапив ваш ключ service_role.","2026-09-08","\u002Fblog\u002Frotate-supabase-service-role-key\u002Fcard-800x500.png",{"path":977,"title":978,"description":979,"published":980,"category":555,"image":981,"draft":559},"\u002Fblog\u002Fvibe-coding-security-scanners-compared","Сканери безпеки для vibe coding у порівнянні, наш теж","Найкращий сканер безпеки для vibe coding залежить від трьох питань, на які не відповідає жоден перелік функцій. Вісім інструментів і ціни.","2026-09-07","\u002Fblog\u002Fvibe-coding-security-scanners-compared\u002Fcard-800x500.png",{"path":983,"title":984,"description":985,"published":986,"category":555,"image":987,"draft":559},"\u002Fblog\u002Fsupabase-security-checker","Supabase security checker: п’ять перевірок, які зробите самі","Supabase security checker читає ваш опублікований застосунок, а не налаштування проєкту. Ось п’ять перевірок і як виконати кожну самостійно.","2026-09-06","\u002Fblog\u002Fsupabase-security-checker\u002Fcard-800x500.png",{"path":989,"title":990,"description":991,"published":992,"category":555,"image":993,"draft":559},"\u002Fblog\u002Fvibe-coding-security-scanner","Сканер безпеки для vibe coding: чого не бачить скан URL","Сканер безпеки для vibe coding читає ваш робочий застосунок ззовні. Що це охоплює, чотири речі, яких він не бачить, і як читати результат.","2026-09-05","\u002Fblog\u002Fvibe-coding-security-scanner\u002Fcard-800x500.png",{"path":995,"title":996,"description":997,"published":998,"category":555,"image":999,"draft":559},"\u002Fblog\u002Fmissing-security-headers","Відсутні заголовки безпеки: коли це справді важливо","Відсутні заголовки безпеки трапляються в наших звітах найчастіше. Від чого вони захищають і коли це найменш терміновий рядок вашого звіту.","2026-09-04","\u002Fblog\u002Fmissing-security-headers\u002Fcard-800x500.png",{"path":1001,"title":1002,"description":1003,"published":1004,"category":555,"image":1005,"draft":559},"\u002Fblog\u002Fopenai-api-key-exposed-in-frontend","Ваш ключ API OpenAI відкритий у фронтенді. Замініть його.","Ключ API OpenAI, відкритий у фронтенді, не можна прив'язати до домену. Замініть його сьогодні, перенесіть виклик на свій сервер і обмежте витрати.","2026-09-03","\u002Fblog\u002Fopenai-api-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":1007,"title":1008,"description":1009,"published":1010,"category":555,"image":1011,"draft":559},"\u002Fblog\u002Fwhat-secrets-leak-from-vibe-coded-apps","Ключ API у фронтенді: що насправді віддають 30 998 застосунків","Ключ API, відкритий у вашому фронтенді, зазвичай є ключем Google Maps. Ми просканували 30 998 vibe-coded застосунків і порахували, що витікає насправді.","2026-09-02","\u002Fblog\u002Fwhat-secrets-leak-from-vibe-coded-apps\u002Fcard-800x500.png",{"path":1013,"title":1014,"description":1015,"published":1016,"category":660,"image":1017,"draft":559},"\u002Fblog\u002Fsupabase-backup-tools-compared","Інструменти резервного копіювання Supabase, і наш серед них","Чотири види інструментів резервного копіювання Supabase, що копіює кожен і коли безкоштовна GitHub Action виграє в будь-якої оплати, нашої теж.","2026-09-01","\u002Fblog\u002Fsupabase-backup-tools-compared\u002Fcard-800x500.png",{"path":1019,"title":1020,"description":1021,"published":1022,"category":555,"image":1023,"draft":559},"\u002Fblog\u002Freplit-secrets-explained","Як користуватися секретами в Replit і що все одно публікується","Як користуватися секретами в Replit: додати, прочитати і усунути дві причини undefined. А ще ключі, які цей інструмент не здатен зберегти в таємниці.","2026-08-31","\u002Fblog\u002Freplit-secrets-explained\u002Fcard-800x500.png",{"path":1025,"title":1026,"description":1027,"published":1028,"category":660,"image":1029,"draft":559},"\u002Fblog\u002Fback-up-supabase-free-tier","Резервні копії на безкоштовному тарифі Supabase без терміналу","На безкоштовному тарифі Supabase резервних копій немає: копію робите ви. Як зробити її з панелі та що втрачає шлях через CSV.","2026-08-30","\u002Fblog\u002Fback-up-supabase-free-tier\u002Fcard-800x500.png",{"path":1031,"title":1032,"description":1033,"published":1034,"category":555,"image":1035,"draft":559},"\u002Fblog\u002Fis-supabase-secure","Чи безпечний Supabase? Так. Ваш проєкт це інше питання","Чи безпечний Supabase? Платформа проходить аудити, шифрує дані й тримає пентести. Їхня документація каже, де це закінчується і починаються ваші налаштування.","2026-08-29","\u002Fblog\u002Fis-supabase-secure\u002Fcard-800x500.png",{"path":1037,"title":1038,"description":1039,"published":1040,"category":555,"image":1041,"draft":559},"\u002Fblog\u002Fwhere-to-find-supabase-api-keys","Де знайти API-ключі Supabase: anon, service_role і URL","URL проєкту, ключ anon і ключ service_role лежать на одній сторінці панелі. Ось де вона, і яке з чотирьох значень має бути у вашому застосунку.","2026-08-28","\u002Fblog\u002Fwhere-to-find-supabase-api-keys\u002Fcard-800x500.png",{"path":1043,"title":1044,"description":1045,"published":1046,"category":555,"image":1047,"draft":559},"\u002Fblog\u002Fnew-row-violates-row-level-security-policy","New row violates row-level security policy в Supabase. Що далі?","\"New row violates row-level security policy\" означає, що Supabase відмовив у записі. Швидке виправлення знову відкриває таблицю для всіх.","2026-08-27","\u002Fblog\u002Fnew-row-violates-row-level-security-policy\u002Fcard-800x500.png",{"path":420,"title":1049,"description":1050,"published":1051,"category":555,"image":1052,"draft":559},"Чи є вайлдкард CORS ризиком для безпеки? Майже ніколи.","Чи є вайлдкард CORS ризиком для безпеки? Зазвичай це типове налаштування вашого білдера, і воно не видає нічого, що ваш сервер і так віддавав кожному.","2026-08-26","\u002Fblog\u002Fcors-wildcard-security-risk\u002Fcard-800x500.png",{"path":1054,"title":1055,"description":1056,"published":1057,"category":660,"image":1058,"draft":559},"\u002Fblog\u002Fsupabase-branching-is-not-a-backup","Branching у Supabase це не бекап. Він іде тільки вперед.","Чому branching у Supabase це не бекап: гілка стартує без ваших даних, а мердж переносить саму лише схему. Для чого він потрібен і що брати замість нього.","2026-08-25","\u002Fblog\u002Fsupabase-branching-is-not-a-backup\u002Fcard-800x500.png",{"path":1060,"title":1061,"description":1062,"published":1063,"category":660,"image":1064,"draft":559},"\u002Fblog\u002Fhow-to-restore-a-supabase-backup","Як відновити резервну копію Supabase, і що ламається потім","Як відновити резервну копію Supabase з панелі або з файлу дампа, що саме замінює відновлення і чому ваш застосунок може лишитися зламаним після нього.","2026-08-24","\u002Fblog\u002Fhow-to-restore-a-supabase-backup\u002Fcard-800x500.png",{"path":1066,"title":1067,"description":1068,"published":1069,"category":555,"image":1070,"draft":559},"\u002Fblog\u002Fsupabase-storage-bucket-public","Ваш бакет Supabase Storage публічний. Чи це проблема?","Публічний бакет Supabase Storage означає, що той, хто має URL файлу, може його відкрити. Він не означає, що хтось може переглянути список вмісту.","2026-08-23","\u002Fblog\u002Fsupabase-storage-bucket-public\u002Fcard-800x500.png",{"path":1072,"title":1073,"description":1074,"published":1075,"category":555,"image":1076,"draft":559},"\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend","Чи є проблемою ключ API Google у вашому браузері?","Ключ API Google у відкритому коді сторінки наш сканер знаходить найчастіше, і зазвичай усе гаразд. Вирішує це одне безкоштовне налаштування.","2026-08-22","\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":1078,"title":1079,"description":1080,"published":1081,"category":660,"image":1082,"draft":559},"\u002Fblog\u002Fai-agent-deleted-my-database","ШІ-агент видалив мої дані в Supabase. Що можна відновити?","ШІ-агент видалив дані з вашої бази. Те, що ви зможете відновити, вирішилося раніше, а наступні хвилини вирішують, скільки з цього вціліє.","2026-08-21","\u002Fblog\u002Fai-agent-deleted-my-database\u002Fcard-800x500.png",{"path":1084,"title":1085,"description":1086,"published":1087,"category":555,"image":1088,"draft":559},"\u002Fblog\u002Fcan-anyone-read-your-supabase-database","Чи може будь-хто прочитати вашу базу Supabase? 3 680 застосунків","Чи може будь-хто прочитати вашу базу даних Supabase без входу? Ми просканували 30 998 живих застосунків, зроблених ШІ-білдерами, і виміряли, як часто так.","2026-08-18","\u002Fblog\u002Fcan-anyone-read-your-supabase-database\u002Fcard-800x500.png",{"path":1090,"title":1091,"description":1092,"published":1087,"category":555,"image":1093,"draft":559},"\u002Fblog\u002Fsource-maps-exposed-in-production","Відкриті source maps: ваш застосунок публікує свій вихідний код","Відкрита source map дає будь-кому прочитати оригінальний код вашого застосунку, разом із коментарями. Перевірка за 30 секунд, і що важить насправді.","\u002Fblog\u002Fsource-maps-exposed-in-production\u002Fcard-800x500.png",{"path":1095,"title":1096,"description":1097,"published":1098,"category":555,"image":1099,"draft":559},"\u002Fblog\u002Fsupabase-new-api-keys","Supabase publishable key: це той самий anon key?","Supabase замінив anon і service_role публічними та секретними ключами. Який із них має бути у вашому застосунку, а який не має бути ніколи?","2026-08-12","\u002Fblog\u002Fsupabase-new-api-keys\u002Fcard-800x500.png",{"path":1101,"title":1102,"description":1103,"published":1104,"category":660,"image":1105,"draft":559},"\u002Fblog\u002Fdoes-supabase-back-up-my-database","Чи робить Supabase резервні копії моєї бази? Залежить від тарифу.","Чи робить Supabase резервні копії вашої бази даних? На платних тарифах щодня, на безкоштовному жодної. Як дізнатися, що у вас насправді є.","2026-08-11","\u002Fblog\u002Fdoes-supabase-back-up-my-database\u002Fcard-800x500.png",{"path":1107,"title":1108,"description":1109,"published":1110,"category":555,"image":1111,"draft":559},"\u002Fblog\u002Fsupabase-rls-on-but-table-still-public","Supabase Row Level Security увімкнено. Таблиця досі публічна.","Увімкнути Supabase Row Level Security не означає захистити таблицю. Захищають політики, а та, що полагодила застосунок, часто впускає всіх.","2026-08-10","\u002Fblog\u002Fsupabase-rls-on-but-table-still-public\u002Fcard-800x500.png",{"path":1113,"title":1114,"description":1115,"published":1110,"category":660,"image":1116,"draft":559},"\u002Fblog\u002Fversion-history-is-not-a-backup","Історія версій не є резервною копією. Вона не поверне таблицю.","Lovable і Bolt ведуть історію версій вашого коду. Ваша база даних працює як окремий сервіс, тож відкат назад не поверне ваші дані.","\u002Fblog\u002Fversion-history-is-not-a-backup\u002Fcard-800x500.png",{"path":1118,"title":1119,"description":1120,"published":1121,"category":660,"image":1122,"draft":559},"\u002Fblog\u002Fthree-ways-to-back-up-a-supabase-database","Три способи зробити резервну копію бази Supabase, і чого бракує","Панель, pg_dump і керований сервіс. Що насправді зберігає кожен спосіб, що тихо лишає осторонь і який із них переживе втрату акаунта.","2026-08-09","\u002Fblog\u002Fthree-ways-to-back-up-a-supabase-database\u002Fcard-800x500.png",{"path":1124,"title":1125,"description":1126,"published":1121,"category":555,"image":1127,"draft":559},"\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend","Які API-ключі безпечні у браузері, а які ні","Ваш ключ anon у Supabase створений, щоб бути публічним. Ключ service_role для цього не створений, і він ігнорує всі правила, які ви задали. Як їх розрізнити.","\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend\u002Fcard-800x500.png",1794213654223]