[{"data":1,"prerenderedAt":1197},["ShallowReactive",2],{"blog-uk-email-link-is-invalid-or-has-expired":3,"blog-index-uk":670},{"id":4,"title":5,"body":6,"category":625,"cover":626,"coverAlt":627,"description":628,"draft":629,"extension":630,"faq":631,"image":650,"keywords":651,"meta":658,"navigation":659,"ogTitle":660,"path":661,"published":662,"seo":663,"stem":664,"tldr":665,"updated":662,"__hash__":669},"blog_uk\u002Fblog\u002Femail-link-is-invalid-or-has-expired.md","Supabase \"Email link is invalid or has expired\" на новому посиланні",{"type":7,"value":8,"toc":612},"minimark",[9,13,24,27,30,38,43,46,61,85,92,96,99,113,116,122,125,141,145,148,160,176,187,191,194,203,206,212,219,223,230,237,243,254,318,326,330,333,339,345,378,384,390,401,405,408,418,424,427,433,439,452,455,461,468,487,492,495,501,507,511,514,523,537,546,566,570,601],[10,11,12],"p",{},"Новий користувач реєструється, відкриває лист із підтвердженням хвилиною\nпізніше й натискає кнопку. Ваш застосунок відкривається на головній сторінці,\nдосі без входу, а адресний рядок закінчується так:",[14,15,20],"pre",{"className":16,"code":18,"language":19},[17],"language-text","#error=access_denied&error_code=otp_expired&error_description=Email+link+is+invalid+or+has+expired\n","text",[21,22,18],"code",{"__ignoreMap":23},"",[10,25,26],{},"Ви реєструєтеся зі своєю адресою, щоб перевірити. Посилання працює з першого\nразу і з кожного наступного.",[10,28,29],{},"Supabase каже \"Email link is invalid or has expired\", коли токен у посиланні з\nлиста вже використали, замінили або коли минула його година. Якщо посилання\nприйшло хвилину тому, звична причина в тому, що щось використало його раніше за\nвашого користувача: поштовий фільтр, який відкриває посилання, щоб їх\nперевірити. Ваш застосунок із Lovable, Bolt, v0, Cursor, Claude Code або Replit\nпрацює так, як його збудовано, а лист, який він надсилає, можна змінити так,\nщоб фільтру не було чого витрачати.",[10,31,32,33,37],{},"Ось у чому помиляється звична порада: ",[34,35,36],"strong",{},"\"попросіть спробувати ще раз\" і\n\"зробіть так, щоб посилання діяло довше\" обидва не працюють проти фільтра.","\nДруге посилання проходить через той самий фільтр і закінчується так само, хоч\nби скільки воно мало діяти.",[39,40,42],"h2",{"id":41},"що-означає-email-link-is-invalid-or-has-expired-у-supabase","Що означає \"Email link is invalid or has expired\" у Supabase?",[10,44,45],{},"Це означає, що Supabase Auth, частина Supabase, яка впускає людей, не знайшла\nза посиланням токена, який вона погодилася б прийняти.",[10,47,48,49,56,57,60],{},"Повідомлення записане в коді самого сервера Auth. У\n",[50,51,55],"a",{"href":52,"rel":53},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fauth\u002Fblob\u002Fmaster\u002Finternal\u002Fapi\u002Fverify.go",[54],"nofollow","функції, яка перевіряє посилання з листа",",\nвоно повертається у двох місцях: коли жоден токен, що чекає, не збігається з\nтокеном у посиланні, і коли токен, що збігається, надіслали раніше, ніж дозволяє\nваш проєкт. Обидві відповіді мають HTTP 403 і код ",[21,58,59],{},"otp_expired",", тож саме\nповідомлення не каже, котрий із двох випадків стався.",[10,62,63,64,67,68,71,72,75,76,79,80,84],{},"Ваш користувач ніколи не бачить 403. Посилання в листі веде на власну адресу\nвашого проєкту Supabase, яка перевіряє токен і потім відправляє браузер до\nвашого застосунку. Коли перевірка не проходить, Supabase додає три речі в\nчастину адреси вашого застосунку після ",[21,65,66],{},"#",": ",[21,69,70],{},"error=access_denied",",\n",[21,73,74],{},"error_code=otp_expired"," і саме повідомлення. Якщо ваш застосунок налаштований\nіз потоком PKCE, вони є ще й у частині після ",[21,77,78],{},"?",". Поки жодна сторінка вашого\nзастосунку їх не читає, людина бачить те, що ваш застосунок показує\nвідвідувачу без входу, і жодного слова про причину. Сторінка скидання пароля,\nяка все одно показує форму, під час збереження падає з «Auth session missing!»,\nбо посилання нікого не впустило, і\n",[50,81,83],{"href":82},"\u002Fblog\u002Fauth-session-missing","про це повідомлення є окрема стаття",".",[10,86,87,88,84],{},"Уявіть посилання як квиток, який пропускає одну людину, один раз. Хто віддасть\nйого першим, той проходить, а квиток пробивають компостером. \"Email link is\ninvalid or has expired\" означає, що людина біля дверей тримає квиток, у якому\nвже є дірка, або квиток, чий час минув. Якщо ваші користувачі заходять, а\nпізніше їх викидає, це інша помилка, у якої є\n",[50,89,91],{"href":90},"\u002Fblog\u002Fsupabase-users-logged-out","своя сторінка",[39,93,95],{"id":94},"чому-посилання-надіслане-хвилину-тому-вже-використане","Чому посилання, надіслане хвилину тому, вже використане",[10,97,98],{},"Бо щось відкрило його раніше за вашого користувача, а відкрити посилання з\nлиста Supabase означає те саме, що використати його.",[10,100,101,106,107,112],{},[50,102,105],{"href":103,"rel":104},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fauth\u002Fauth-email-templates",[54],"Посібник із шаблонів листів","\nSupabase зараховує це до своїх обмежень: деякі поштові провайдери мають\nвиявлення спаму чи інші функції безпеки, які заздалегідь відкривають посилання\nу вхідних листах. Приклад, який він наводить, це Safe Links у Microsoft\nDefender for Office 365.\n",[50,108,111],{"href":109,"rel":110},"https:\u002F\u002Fsupabase.com\u002Fkb\u002Ftroubleshooting\u002Fotp-verification-failures-token-has-expired-or-otp_expired-errors",[54],"Сторінка Supabase з усунення цієї помилки","\nназиває таке завчасне відкриття найпоширенішою причиною того, що токен\nвиглядає простроченим або недійсним ще до того, як користувач узагалі може\nним скористатися.",[10,114,115],{},"У типовому посиланні немає жодного кроку між відкриттям і використанням. Його\nадреса веде на точку перевірки вашого проєкту, яка перевіряє токен і витрачає\nйого під час першого візиту, хоч би хто його зробив. Тож фільтр віддає квиток,\nпоки лист надходить, і проходить. Ваш користувач натискає хвилиною пізніше, і\nйого не пускають біля дверей.",[117,118],"diagram",{"alt":119,"caption":120,"src":121},"Лист із квитком усередині. Від нього до тих самих дверей ведуть два шляхи. Перший, від лупи, яка позначає поштовий фільтр, одразу доходить до дверей і отримує галочку. Другий, від людини, приходить пізніше й отримує червоний хрестик, бо в квитку тепер пробита дірка.","Перший візит використовує посилання. Поштовий фільтр, який відкриває його для перевірки, приходить раніше за людину.","\u002Fblog\u002Femail-link-is-invalid-or-has-expired\u002Fwho-used-it-first-1600x720.png",[10,123,124],{},"Із цього випливають дві речі.",[126,127,128,135],"ul",{},[129,130,131,134],"li",{},[34,132,133],{},"Ви самі цього не побачите."," Це трапляється з людьми, чия пошта проходить\nчерез такий фільтр, і ваша власна скринька може ніколи до них не належати.\nВаші тестові реєстрації й далі працюють, поки користувачі вам пишуть.",[129,136,137,140],{},[34,138,139],{},"Обліковий запис зазвичай уже підтверджений."," Під час підтвердження\nреєстрації візит фільтра зараховується: Supabase підтверджує адресу того, хто\nвідкриє посилання. Якщо його відкрив фільтр, обліковий запис уже\nпідтверджений, коли ваш користувач бачить помилку, і він може увійти з\nпаролем, який обрав.",[39,142,144],{"id":143},"три-інші-причини","Три інші причини",[10,146,147],{},"Кожна з них дає те саме повідомлення, і кожна вимагає від вас чогось іншого.",[10,149,150,153,154,159],{},[34,151,152],{},"Посилання вже використали."," Ваш користувач натиснув його один раз, можливо,\nна телефоні, а тепер натискає той самий лист на ноутбуці. Коли посилання\nспрацьовує, Supabase видаляє всі посилання, які ще чекали для цього облікового\nзапису. Його\n",[50,155,158],{"href":156,"rel":157},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fauth\u002Fblob\u002Fmaster\u002Finternal\u002Fmodels\u002Fuser.go",[54],"код облікових записів","\nробить те саме прибирання після підтвердження й після посилання для входу, тож\nдруге натискання нічого не знаходить. Квиток пробили під час першого візиту.",[10,161,162,165,166,171,172,84],{},[34,163,164],{},"Його замінив новіший лист."," Натискання \"надіслати ще раз\" створює нове\nпосилання, і Supabase видаляє старе того самого типу, перш ніж зберегти нове.\nЦе відбувається в\n",[50,167,170],{"href":168,"rel":169},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fauth\u002Fblob\u002Fmaster\u002Finternal\u002Fmodels\u002Fone_time_token.go",[54],"коді, який створює токен",".\nПерший лист і далі лежить у скриньці й виглядає звичайно, а його посилання не\nпрацює. Працює лише найновіший лист. Supabase також дозволяє одній адресі\nпросити такий лист лише раз на 60 секунд, і це докладно описує\n",[50,173,175],{"href":174},"\u002Fblog\u002Fsupabase-email-rate-limit-exceeded","ліміт листів",[10,177,178,181,186],{},[34,179,180],{},"Воно справді прострочене.",[50,182,185],{"href":183,"rel":184},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fauth\u002Fauth-email-passwordless",[54],"Посібник із входу без пароля","\nSupabase каже, що magic links типово спливають через 1 годину. На тій самій\nсторінці сказано, що налаштування Email OTP Expiration регулює також строк дії\nmagic links та інших посилань із листів, зокрема для підтвердження, відновлення\nпароля, зміни пошти й запрошення. Хто реєструється ввечері й відкриває лист\nнаступного ранку, побачить це повідомлення, хоч би що робив його поштовий\nсервіс.",[39,188,190],{"id":189},"що-відбувається-коли-посилання-відкривають-на-іншому-пристрої","Що відбувається, коли посилання відкривають на іншому пристрої?",[10,192,193],{},"Якщо ваш застосунок використовує потік PKCE, інший пристрій спершу видає інше\nповідомлення, а це приходить слідом.",[10,195,196,197,202],{},"PKCE розділяє квиток надвоє.\n",[50,198,201],{"href":199,"rel":200},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fauth\u002Fsessions\u002Fpkce-flow",[54],"Посібник із PKCE","\nSupabase пояснює, що code verifier створюється й зберігається локально, коли\nпочинається потік Auth, і тому обмін коду треба запускати в тому самому\nбраузері й на тому самому пристрої, де потік почався. Лист несе одну половину.\nБраузер, у якому ваш користувач попросив посилання, зберігає корінець.",[10,204,205],{},"Відкрите на телефоні, посилання все одно проходить перевірку Supabase, яка його\nвитрачає. Потім ваш застосунок шукає корінець на телефоні й не знаходить. Клієнт\nJavaScript від Supabase повідомляє про це власними словами:",[14,207,210],{"className":208,"code":209,"language":19},[17],"PKCE code verifier not found in storage. This can happen if the auth flow was initiated in a different browser or device, or if the storage was cleared.\n",[21,211,209],{"__ignoreMap":23},[10,213,214,215,218],{},"Тож людина повертається до ноутбука й натискає там той самий лист, і Supabase\nвідповідає \"Email link is invalid or has expired\", бо телефон уже дав пробити\nквиток. Якщо адреса, на якій відкривається ваш застосунок після робочого\nпосилання, містить ",[21,216,217],{},"?code=",", ваш застосунок використовує PKCE, і цей випадок\nстосується вас.",[39,220,222],{"id":221},"як-дізнатися-котра-це-була-причина","Як дізнатися, котра це була причина?",[10,224,225,226,229],{},"Запитайте власну базу даних. Кожен обліковий запис у вашому проєкті є рядком у\nтаблиці ",[21,227,228],{},"auth.users",", і цей рядок записує, коли надіслали кожен тип листа і\nколи адресу підтвердили.",[10,231,232,233,236],{},"Відкрийте ",[34,234,235],{},"SQL Editor"," у панелі Supabase й виконайте це з адресою, яку дала\nлюдина:",[14,238,241],{"className":239,"code":240,"language":19},[17],"select email,\n       confirmation_sent_at,\n       email_confirmed_at,\n       recovery_sent_at\nfrom auth.users\nwhere lower(email) = lower('person@example.com');\n",[21,242,240],{"__ignoreMap":23},[10,244,245,246,249,250,253],{},"Запит читає один рядок і нічого не змінює. ",[21,247,248],{},"confirmation_sent_at"," показує, коли\nпішло останнє підтвердження реєстрації. ",[21,251,252],{},"recovery_sent_at"," показує, коли пішов\nостанній magic link чи останнє скидання пароля на обліковий запис, який уже\nіснував. Саме від цих моментів Supabase відраховує годину, у тій самій функції,\nяка повертає помилку. Порівняйте їх із часом листа, який натиснула людина.",[255,256,257,270],"table",{},[258,259,260],"thead",{},[261,262,263,267],"tr",{},[264,265,266],"th",{},"Що ви бачите",[264,268,269],{},"Що це означає",[271,272,273,286,297,310],"tbody",{},[261,274,275,283],{},[276,277,278,279,282],"td",{},"Посилання для підтвердження не спрацювало з першого натискання, а ",[21,280,281],{},"email_confirmed_at"," заповнене",[276,284,285],{},"Щось відкрило посилання раніше за людину. Обліковий запис підтверджений, тож вона може увійти зі своїм паролем",[261,287,288,294],{},[276,289,290,291,293],{},"Посилання для підтвердження не спрацювало, ",[21,292,281],{}," порожнє, а натиснули його через годину з гаком після надсилання",[276,295,296],{},"Воно прострочене. Надішліть нове підтвердження",[261,298,299,307],{},[276,300,301,303,304,306],{},[21,302,248],{}," або ",[21,305,252],{}," пізніше за лист, який натиснула людина",[276,308,309],{},"Його замінив новіший лист. Працює лише посилання з найновішого",[261,311,312,315],{},[276,313,314],{},"Magic link із найновішого листа не спрацював з першого натискання, у межах години",[276,316,317],{},"Щось відкрило його першим: поштовий фільтр або сама людина на іншому пристрої",[10,319,320,321,325],{},"Якщо рядок показує адресу підтвердженою, а людина все одно не може увійти з\nпаролем, ",[50,322,324],{"href":323},"\u002Fblog\u002Fsupabase-invalid-login","Invalid login credentials"," є наступним\nповідомленням, про яке варто прочитати.",[39,327,329],{"id":328},"що-не-змінювати-поки-ви-це-виправляєте","Що не змінювати, поки ви це виправляєте",[10,331,332],{},"Строк дії, перемикач Confirm email і те, хто має право підтверджувати\nкористувача. Кожен з очевидних обхідних шляхів тут зупиняє повідомлення й щось\nвідкриває.",[10,334,335,338],{},[34,336,337],{},"Лишіть строк дії на годині."," Фільтр відкриває посилання, поки лист\nнадходить, тож навіть посилання, яке діє тиждень, він використає першим.\nПосібник Supabase із входу без пароля наполегливо не радить ставити більше ніж\n86 400 секунд, тобто одну добу. Посилання для входу впускає будь-кого, хто його\nвідкриє. Що довше воно працює, то довше пересланий лист, спільна скринька чи\nстаре повідомлення в чиємусь архіві залишаються шляхом у цей обліковий запис.",[10,340,341,344],{},[34,342,343],{},"Лишіть Confirm email увімкненим."," Якщо вимкнути його, реєстрації взагалі\nперестануть надсилати посилання, і ця помилка зникне. Але це також означає, що\nбудь-хто може створити обліковий запис з адресою, яку ніколи не контролював.",[10,346,347,350,351,354,355,360,361,364,365,368,369,373,374,84],{},[34,348,349],{},"Не підтверджуйте користувачів із браузера."," Попросіть асистента \"просто\nпозначити їх підтвердженими\", і він може взятися за адміністративні функції\nSupabase, які вміють ставити ",[21,352,353],{},"email_confirm"," на обліковому записі.\n",[50,356,359],{"href":357,"rel":358},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Freference\u002Fjavascript\u002Fadmin-api",[54],"Довідник з адміністрування","\nSupabase каже, що будь-який метод у ",[21,362,363],{},"supabase.auth.admin"," потребує ключа\n",[21,366,367],{},"secret"," і що цей ключ ніколи не можна показувати в браузері. Секретний ключ у\nвашому фронтенді читає й записує кожну таблицю вашої бази даних, хоч би що\nказали ваші правила, для кожного, хто відкриє код вашого сайту. Це той ключ,\nякий ",[50,370,372],{"href":371},"\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend","ця стаття про API-ключі","\nрадить замінити. Якщо не впевнені, що жоден не просочився, наш безплатний скан\nчитає код, який ваш робочий застосунок надсилає кожному відвідувачу, і позначає\nсекретні ключі, які там знаходить. Це займає близько 20 секунд і не потребує\nоблікового запису: ",[50,375,377],{"href":376},"\u002Fsecurity-scanner","проскануйте свій застосунок",[10,379,380,383],{},[34,381,382],{},"Перевіряйте кожну адресу, яку передає ваша сторінка підтвердження."," Друге\nрішення, яке пропонує Supabase, це ваша власна сторінка з кнопкою, на яку веде\nпосилання такого вигляду:",[14,385,388],{"className":386,"code":387,"language":19},[17],"\u003Ca href=\"{{ .SiteURL }}\u002Fconfirm-signup?confirmation_url={{ .ConfirmationURL }}\">\n",[21,389,387],{"__ignoreMap":23},[10,391,392,393,396,397,400],{},"Кнопка потім веде туди, куди каже ",[21,394,395],{},"confirmation_url",". Зроблена без перевірки,\nтака сторінка відправить будь-кого, хто перейде за підробленим посиланням, на\nбудь-яку адресу під назвою вашого застосунку. Або перевіряйте, що адреса\nпочинається з адреси вашого власного проєкту,\n",[21,398,399],{},"https:\u002F\u002F\u003Cproject-ref>.supabase.co\u002Fauth\u002Fv1\u002Fverify",", або скористайтеся варіантом\nіз токеном із наступного розділу, який узагалі не пропускає адресу через\nсторінку.",[39,402,404],{"id":403},"як-надіслати-посилання-яке-поштовий-фільтр-не-зможе-витратити","Як надіслати посилання, яке поштовий фільтр не зможе витратити",[10,406,407],{},"Зробіть так, щоб відкриття адреси нічого не робило, і тоді увійде лише людина,\nяка вводить код або натискає кнопку. Supabase документує обидва варіанти, а\nзмінюються вони в шаблонах листів, у розділі Authentication вашої панелі.\nРедагувати треба два: Confirm signup і Magic Link.",[10,409,410,413,414,417],{},[34,411,412],{},"Надсилайте код."," Додайте ",[21,415,416],{},"{{ .Token }}"," у шаблон. Посібник Supabase описує\nйого як одноразовий пароль із 6 цифр, яким можна скористатися замість\nпосилання. Ваша сторінка просить адресу пошти й код, а потім викликає:",[14,419,422],{"className":420,"code":421,"language":19},[17],"const { error } = await supabase.auth.verifyOtp({ email, token, type: 'email' })\n",[21,423,421],{"__ignoreMap":23},[10,425,426],{},"Фільтр може відкрити лист. Ввести код на вашій сторінці він не може.",[10,428,429,432],{},[34,430,431],{},"Ведіть на сторінку з кнопкою."," Нехай посилання веде на вашу сторінку й несе\nхеш токена, щоб жодна адреса не мала проходити через неї:",[14,434,437],{"className":435,"code":436,"language":19},[17],"\u003Ca href=\"{{ .SiteURL }}\u002Fconfirm?token_hash={{ .TokenHash }}&type=email\">Confirm your email\u003C\u002Fa>\n",[21,438,436],{"__ignoreMap":23},[10,440,441,444,445,71,448,84],{},[21,442,443],{},"{{ .SiteURL }}"," підставляє Site URL зі сторінки URL Configuration вашого проєкту.\nЯкщо там досі ",[21,446,447],{},"http:\u002F\u002Flocalhost:3000",[50,449,451],{"href":450},"\u002Fblog\u002Fsupabase-auth-redirects-to-localhost","спершу виправте це",[10,453,454],{},"Сторінка показує одну кнопку, і роботу робить лише кнопка:",[14,456,459],{"className":457,"code":458,"language":19},[17],"const params = new URLSearchParams(window.location.search)\n\nasync function onConfirmClick() {\n  const { error } = await supabase.auth.verifyOtp({\n    token_hash: params.get('token_hash'),\n    type: 'email',\n  })\n  if (error?.code === 'otp_expired') showSendNewLink()\n}\n",[21,460,458],{"__ignoreMap":23},[10,462,463,464,467],{},"Тип ",[21,465,466],{},"email"," підходить для обох шаблонів: перевірка Supabase шукає хеш і серед\nпідтверджень реєстрації, і серед посилань для входу.",[10,469,470,477,482,483,486],{},[34,471,472,473,476],{},"Сторінка не повинна викликати ",[21,474,475],{},"verifyOtp"," під час завантаження.",[50,478,481],{"href":479,"rel":480},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fauth\u002Fpasswords",[54],"Посібник Supabase про паролі","\nбудує своє посилання для підтвердження як\n",[21,484,485],{},"{{ .SiteURL }}\u002Fauth\u002Fconfirm?token_hash={{ .TokenHash }}&type=email&next={{ .RedirectTo }}","\nі перевіряє його в ту мить, коли цю адресу відкривають. Це знову типове\nпосилання за новою адресою, і фільтр відкриває його так само легко.",[117,488],{"alt":489,"caption":490,"src":491},"Два листи поруч. Ліворуч посилання веде просто до дверей: лупа відкриває його першою й отримує галочку, а людина за нею отримує червоний хрестик. Праворуч посилання відкриває сторінку з кнопкою: лупа відкриває сторінку, і нічого не відбувається, квиток лишається цілим, а людина натискає кнопку й отримує галочку.","З типовим посиланням уже відкриття впускає. З кнопкою чи введеним кодом це робить лише дія людини.","\u002Fblog\u002Femail-link-is-invalid-or-has-expired\u002Fopening-is-not-using-1600x760.png",[10,493,494],{},"Хоч би що ви змінили, дехто й далі натраплятиме на старе посилання, тож дайте\nїм вихід. Сторінка, на яку приходять ваші посилання, може прочитати помилку й\nзапропонувати новий лист:",[14,496,499],{"className":497,"code":498,"language":19},[17],"const hash = new URLSearchParams(window.location.hash.slice(1))\nif (hash.get('error_code') === 'otp_expired') showSendNewLink()\n",[21,500,498],{"__ignoreMap":23},[10,502,503,504,506],{},"Для реєстрації додайте рядок про те, що людина, можливо, вже підтверджена й\nможе спробувати увійти. Якщо ваші сторінки входу пише білдер, попросіть у нього\nваріант обох шаблонів із кодом або кнопкою, сторінку, яка читає ",[21,505,59],{}," і\nпропонує нове посилання, і жодного адміністративного ключа ніде в застосунку.",[39,508,510],{"id":509},"дізнаватися-про-проблеми-раніше-ніж-напишуть-користувачі","Дізнаватися про проблеми раніше, ніж напишуть користувачі",[10,512,513],{},"Ця помилка доходить до ваших користувачів і ніколи до вас, і інші збої теж\nдоходять спершу до них: застосунок, що падає вночі, чи пізніший промпт, який\nдодає ключ у код вашого застосунку.",[10,515,516],{},[34,517,518,522],{},[50,519,521],{"href":520},"\u002Fwebsite-monitoring","Reeve Monitor"," і далі перевіряє ваш робочий застосунок ззовні:",[126,524,525,528,531,534],{},[129,526,527],{},"чи він доступний, кожні 60 секунд, щоб \"посилання не працює\" можна було відрізнити від \"сайт лежить\"",[129,529,530],{},"усі девʼять перевірок щогодини, до трьох застосунків, разом із секретними ключами в коді, який ваш застосунок надсилає відвідувачам",[129,532,533],{},"повідомлення, коли результат змінюється, щоб ключ, який пішов із пізнішим деплоєм, дійшов до вас без того, щоб ви самі йшли дивитися",[129,535,536],{},"щомісячний звіт простою мовою про те, що він бачив",[10,538,539],{},[34,540,541,545],{},[50,542,544],{"href":543},"\u002Fsupabase-backups","Reeve Care"," тримає копію вашої бази даних Supabase разом\nз обліковими записами:",[126,547,548,551,554,557,560,563],{},[129,549,550],{},"зашифрована копія за розкладом, у власному сховищі Reeve в ЄС, поза вашим обліковим записом Supabase",[129,552,553],{},"схема auth, де живуть облікові записи ваших користувачів, щоб вони повернулися разом з усім іншим",[129,555,556],{},"кожна копія перевірена, перш ніж зарахуватися, підрахунком рядків у кожній таблиці",[129,558,559],{},"ваші вивантажені файли теж, щойно ви підключите доступ до Storage",[129,561,562],{},"відновлення в один клік, із безпечною копією поточного стану, зробленою заздалегідь",[129,564,565],{},"усе, що робить Monitor",[39,567,569],{"id":568},"що-зробити-зараз","Що зробити зараз",[571,572,573],"key-takeaways",{},[126,574,575,584,587,592,598],{},[129,576,577,578,580,581,583],{},"Прочитайте рядок людини в ",[21,579,228],{},". Заповнене ",[21,582,281],{}," при підтвердженні, яке не спрацювало, означає, що посилання відкрив фільтр і людина може увійти.",[129,585,586],{},"Скажіть людям користуватися найновішим листом, а якщо ваш застосунок використовує PKCE, то відкривати його на пристрої, де вони його попросили.",[129,588,589,590,84],{},"Переведіть шаблони Confirm signup і Magic Link на код або на сторінку, де кнопка викликає ",[21,591,475],{},[129,593,594,595,597],{},"Нехай сторінка, на яку приходять ваші посилання, читає ",[21,596,74],{}," і пропонує нове посилання.",[129,599,600],{},"Лишіть строк дії на годині, лишіть Confirm email увімкненим і тримайте секретний ключ поза браузером.",[10,602,603,604,606,607,611],{},"Якщо листи взагалі не доходять, ",[50,605,175],{"href":174},"\nварто виключити першим. ",[50,608,610],{"href":609},"\u002Fchecklist","10-хвилинний чекліст безпеки"," охоплює те,\nщо зазвичай лишається відкритим у щойно запущеному застосунку.",{"title":23,"searchDepth":613,"depth":613,"links":614},3,[615,617,618,619,620,621,622,623,624],{"id":41,"depth":616,"text":42},2,{"id":94,"depth":616,"text":95},{"id":143,"depth":616,"text":144},{"id":189,"depth":616,"text":190},{"id":221,"depth":616,"text":222},{"id":328,"depth":616,"text":329},{"id":403,"depth":616,"text":404},{"id":509,"depth":616,"text":510},{"id":568,"depth":616,"text":569},"Основи безпеки","\u002Fblog\u002Femail-link-is-invalid-or-has-expired\u002Fcover-1200x630.png","Відкритий конверт, з якого наполовину стирчить квиток, і квиток уже пробитий компостером.","На посиланні, надісланому хвилину тому, \"Email link is invalid or has expired\" у Supabase зазвичай означає, що поштовий фільтр використав його першим.",false,"md",[632,635,638,641,644,647],{"q":633,"a":634},"Чому magic link від Supabase каже, що він прострочений, одразу після надсилання?","Зазвичай тому, що щось відкрило його раніше за людину. Деякі поштові сервіси відкривають посилання у вхідному листі, щоб їх перевірити, і Supabase наводить як приклад Safe Links у Microsoft Defender for Office 365. Відкрити посилання з листа Supabase означає використати його, тож коли людина натискає хвилиною пізніше, токена вже немає, і Supabase відповідає \"Email link is invalid or has expired\".",{"q":636,"a":637},"Чи підтверджений обліковий запис, хоча посилання казало, що воно прострочене?","Якщо поштовий фільтр першим відкрив посилання для підтвердження реєстрації, то так. Supabase підтверджує адресу для того, хто відкриє посилання, тож візит фільтра її підтвердив. Подивіться email_confirmed_at в auth.users: якщо поле заповнене, людина може увійти з паролем, який вона обрала.",{"q":639,"a":640},"Скільки часу діє посилання з листа Supabase?","Типово одну годину. Це регулює налаштування Email OTP Expiration, і те саме налаштування діє для magic links, підтверджень реєстрації, скидання паролів, зміни пошти й запрошень. Supabase наполегливо не радить ставити більше ніж 86 400 секунд, тобто одну добу.",{"q":642,"a":643},"Чи варто зробити так, щоб посилання діяли довше?","Ні. Поштовий фільтр відкриває посилання, щойно лист надходить, тож довший строк дії не завадить йому скористатися посиланням першим. Він лише довше тримає робочим кожне невикористане посилання в кожній скриньці, а посилання впускає будь-кого, хто його відкриє, зокрема людину, якій лист переслали.",{"q":645,"a":646},"Чому посилання працює на моєму компʼютері й не працює на телефоні?","Якщо ваш застосунок використовує потік PKCE, браузер, у якому людина попросила посилання, зберігає половину того, що потрібно для входу, тож відкриття листа на іншому пристрої завершується помилкою \"PKCE code verifier not found in storage\". Ця спроба ще й витрачає посилання, і тому повторне натискання на першому пристрої потім каже \"Email link is invalid or has expired\".",{"q":648,"a":649},"Що означає error_code=otp_expired в адресі мого застосунку?","Так Supabase Auth повідомляє про посилання з листа, яке не спрацювало. Він відправляє людину до вашого застосунку з error=access_denied, error_code=otp_expired і повідомленням у частині адреси після знака #. Ваш застосунок може прочитати там error_code і показати сторінку, яка пропонує надіслати нове посилання.","\u002Fblog\u002Femail-link-is-invalid-or-has-expired\u002Fcard-800x500.png",[652,653,654,655,656,657,74],"email link is invalid or has expired supabase","email link is invalid or has expired","otp_expired supabase","supabase magic link одразу прострочений","посилання підтвердження supabase не працює","outlook safe links magic link",{},true,"\"Email link is invalid or has expired\" у Supabase","\u002Fblog\u002Femail-link-is-invalid-or-has-expired","2026-11-01",{"title":5,"description":628},"blog\u002Femail-link-is-invalid-or-has-expired",[666,667,668],"Supabase показує \"Email link is invalid or has expired\", коли токен у посиланні для входу чи підтвердження вже використали, його замінив новіший лист або йому більше години. Код помилки otp_expired.","Якщо людина щойно отримала посилання, звична причина в поштовому фільтрі, який відкрив посилання, щоб його перевірити, і цим самим його витратив. Власна сторінка Supabase з усунення несправностей називає це найпоширенішою причиною.","Довший строк дії нічого не змінює. Змініть лист так, щоб відкриття адреси нічого не робило: надсилайте код, який треба ввести, або посилання на сторінку, де людину впускає кнопка.","N9nCEHb4Ko9v4fen1wp7lsvbgIpz3rBF7lvw4HiZrgY",[671,677,683,689,695,700,706,712,717,718,724,731,737,742,748,754,760,766,772,778,784,789,794,800,806,812,818,824,830,836,842,848,854,860,866,872,878,884,890,896,902,908,914,920,926,932,937,943,949,955,961,967,973,979,985,991,997,1003,1009,1015,1021,1027,1033,1039,1045,1051,1057,1063,1069,1075,1081,1087,1093,1099,1105,1111,1117,1123,1129,1135,1141,1147,1153,1159,1164,1170,1176,1182,1187,1193],{"path":672,"title":673,"description":674,"published":675,"category":625,"image":676,"draft":629},"\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":678,"title":679,"description":680,"published":681,"category":625,"image":682,"draft":629},"\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":684,"title":685,"description":686,"published":687,"category":625,"image":688,"draft":629},"\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":690,"title":691,"description":692,"published":693,"category":625,"image":694,"draft":629},"\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":82,"title":696,"description":697,"published":698,"category":625,"image":699,"draft":629},"Supabase \"Auth session missing!\": куди поділася сесія","\"Auth session missing!\" означає, що supabase-js не знайшла жодної сесії й навіть не спитала Supabase. Куди поділася сесія і як це виправити.","2026-11-05","\u002Fblog\u002Fauth-session-missing\u002Fcard-800x500.png",{"path":701,"title":702,"description":703,"published":704,"category":625,"image":705,"draft":629},"\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":707,"title":708,"description":709,"published":710,"category":625,"image":711,"draft":629},"\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function","\"Failed to send a request to the Edge Function\": справжня причина","\"Failed to send a request to the Edge Function\": браузер не отримав відповіді, яку можна прочитати. Його консоль каже, чому, і чи ваша функція запускалася.","2026-11-03","\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function\u002Fcard-800x500.png",{"path":450,"title":713,"description":714,"published":715,"category":625,"image":716,"draft":629},"Чому Supabase перенаправляє посилання з листів на localhost:3000","Якщо Supabase перенаправляє на localhost:3000, Site URL так і не змінили або вашої адреси немає в Redirect URLs. Як виправити і якої маски уникати.","2026-11-02","\u002Fblog\u002Fsupabase-auth-redirects-to-localhost\u002Fcard-800x500.png",{"path":661,"title":5,"description":628,"published":662,"category":625,"image":650,"draft":629},{"path":719,"title":720,"description":721,"published":722,"category":625,"image":723,"draft":629},"\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":725,"title":726,"description":727,"published":728,"category":729,"image":730,"draft":629},"\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":732,"title":733,"description":734,"published":735,"category":625,"image":736,"draft":629},"\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":323,"title":738,"description":739,"published":740,"category":625,"image":741,"draft":629},"\"Invalid login credentials\" у Supabase: три причини","Supabase відповідає \"Invalid login credentials\" на неправильний пароль, невідому пошту й акаунт Google. Як зрозуміти, що саме.","2026-10-28","\u002Fblog\u002Fsupabase-invalid-login\u002Fcard-800x500.png",{"path":743,"title":744,"description":745,"published":746,"category":625,"image":747,"draft":629},"\u002Fblog\u002Fedge-function-returned-a-non-2xx-status-code","\"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":749,"title":750,"description":751,"published":752,"category":625,"image":753,"draft":629},"\u002Fblog\u002Fcertificate-not-trusted","Сертифікат не довірений: три причини, крім закінчення строку","Сертифікат не довірений, хоча дата в порядку? Він називає іншу адресу, підписав сам себе або в його ланцюжку бракує ланки. Як зрозуміти, що саме.","2026-10-26","\u002Fblog\u002Fcertificate-not-trusted\u002Fcard-800x500.png",{"path":755,"title":756,"description":757,"published":758,"category":625,"image":759,"draft":629},"\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":761,"title":762,"description":763,"published":764,"category":625,"image":765,"draft":629},"\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":767,"title":768,"description":769,"published":770,"category":625,"image":771,"draft":629},"\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":773,"title":774,"description":775,"published":776,"category":625,"image":777,"draft":629},"\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":779,"title":780,"description":781,"published":782,"category":625,"image":783,"draft":629},"\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":174,"title":785,"description":786,"published":787,"category":625,"image":788,"draft":629},"Supabase \"Email Rate Limit Exceeded\": два ліміти, один код помилки","Supabase \"Email Rate Limit Exceeded\" обмежує кожен лист вашого проєкту: 2 на годину з вбудованим відправником. Що враховується і як підняти ліміт.","2026-10-20","\u002Fblog\u002Fsupabase-email-rate-limit-exceeded\u002Fcard-800x500.png",{"path":90,"title":790,"description":791,"published":792,"category":625,"image":793,"draft":629},"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":795,"title":796,"description":797,"published":798,"category":625,"image":799,"draft":629},"\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":801,"title":802,"description":803,"published":804,"category":625,"image":805,"draft":629},"\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":807,"title":808,"description":809,"published":810,"category":625,"image":811,"draft":629},"\u002Fblog\u002Fsupabase-invalid-jwt","Supabase invalid JWT: який ключ перестав працювати?","Помилка Supabase invalid JWT — це PGRST301: підписаний токен не пройшов перевірку. У новому проєкті йдеться про сесію, а не про ключ API.","2026-10-16","\u002Fblog\u002Fsupabase-invalid-jwt\u002Fcard-800x500.png",{"path":813,"title":814,"description":815,"published":816,"category":625,"image":817,"draft":629},"\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":819,"title":820,"description":821,"published":822,"category":625,"image":823,"draft":629},"\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":825,"title":826,"description":827,"published":828,"category":625,"image":829,"draft":629},"\u002Fblog\u002Fwhat-is-supabase","Що таке Supabase? База даних, яку обрав ваш білдер","Що таке Supabase: база даних, вхід і сховище файлів за вашим застосунком. Supabase тримає сервери, а правила про те, хто читає ваші рядки, ваші.","2026-10-13","\u002Fblog\u002Fwhat-is-supabase\u002Fcard-800x500.png",{"path":831,"title":832,"description":833,"published":834,"category":625,"image":835,"draft":629},"\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":837,"title":838,"description":839,"published":840,"category":625,"image":841,"draft":629},"\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":843,"title":844,"description":845,"published":846,"category":625,"image":847,"draft":629},"\u002Fblog\u002Fbase44-security-scan","Сканування безпеки Base44: те, що бачить лише воно","Сканування безпеки Base44 перевіряє сім видів проблем зсередини. Ось половина, яку читає тільки воно, і половина, на яку воно ніколи не дивиться.","2026-10-10","\u002Fblog\u002Fbase44-security-scan\u002Fcard-800x500.png",{"path":849,"title":850,"description":851,"published":852,"category":625,"image":853,"draft":629},"\u002Fblog\u002Fapi-key-leaked-what-to-do","Ваш API-ключ витік. Порядок дій","API-ключ витік, і ви не знаєте, що робити першим. Не кожен ключ у вашому фронтенді є витоком, а порядок важить більше за швидкість.","2026-10-09","\u002Fblog\u002Fapi-key-leaked-what-to-do\u002Fcard-800x500.png",{"path":855,"title":856,"description":857,"published":858,"category":625,"image":859,"draft":629},"\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":861,"title":862,"description":863,"published":864,"category":625,"image":865,"draft":629},"\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":867,"title":868,"description":869,"published":870,"category":625,"image":871,"draft":629},"\u002Fblog\u002Fis-base44-safe","Чи безпечний Base44? Дані 5 442 застосунків","Чи безпечний Base44? Ми прогнали дев’ять перевірок на 5 442 робочих застосунках Base44. Майже всі отримали B з причин, яких власник ніколи не обирав.","2026-10-06","\u002Fblog\u002Fis-base44-safe\u002Fcard-800x500.png",{"path":873,"title":874,"description":875,"published":876,"category":625,"image":877,"draft":629},"\u002Fblog\u002Fmove-a-secret-to-an-edge-function","Сховати ключ API: перенесіть його в Supabase Edge Function","Сховати ключ API означає прибрати його з браузера, а Supabase Edge Function це найменше місце, куди його покласти. Два кроки поруч важать більше.","2026-10-05","\u002Fblog\u002Fmove-a-secret-to-an-edge-function\u002Fcard-800x500.png",{"path":879,"title":880,"description":881,"published":882,"category":625,"image":883,"draft":629},"\u002Fblog\u002Fenv-file-exposed-on-your-server","Чи відкритий ваш файл .env? Дванадцять шляхів для перевірки","Чи відкритий ваш файл .env на вашому власному вебсервері? Дванадцять адрес скажуть це за хвилину, а влучання означає, що все в ньому вже публічне.","2026-10-04","\u002Fblog\u002Fenv-file-exposed-on-your-server\u002Fcard-800x500.png",{"path":885,"title":886,"description":887,"published":888,"category":625,"image":889,"draft":629},"\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":891,"title":892,"description":893,"published":894,"category":625,"image":895,"draft":629},"\u002Fblog\u002Fis-bolt-safe","Чи безпечний Bolt? Що показали 1 123 застосунки Bolt","Чи безпечний Bolt? Ми прогнали 1 123 живі застосунки Bolt через дев’ять перевірок. Хостинг був чистим. Знахідки: ключі й відкриті таблиці в самих застосунках.","2026-10-02","\u002Fblog\u002Fis-bolt-safe\u002Fcard-800x500.png",{"path":897,"title":898,"description":899,"published":900,"category":625,"image":901,"draft":629},"\u002Fblog\u002Fis-supabase-down-or-your-app","Supabase не працює чи проблема у вашому застосунку? Як розрізнити","Supabase не працює чи зламався ваш застосунок? Пауза, повна база й ваша власна помилка виглядають однаково. Чотири перевірки їх розрізняють.","2026-10-01","\u002Fblog\u002Fis-supabase-down-or-your-app\u002Fcard-800x500.png",{"path":903,"title":904,"description":905,"published":906,"category":625,"image":907,"draft":629},"\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":909,"title":910,"description":911,"published":912,"category":625,"image":913,"draft":629},"\u002Fblog\u002Fsupabase-free-plan-limits","Ліміти безкоштовного тарифу Supabase і що стається на кожному","Ліміти безкоштовного тарифу Supabase: 500 МБ бази, 5 ГБ egress і жодних резервних копій. Що стається на кожній межі і що змінює Pro за $25.","2026-09-29","\u002Fblog\u002Fsupabase-free-plan-limits\u002Fcard-800x500.png",{"path":915,"title":916,"description":917,"published":918,"category":729,"image":919,"draft":629},"\u002Fblog\u002Ftest-your-supabase-backup","Перевірте резервну копію Supabase до того, як вона знадобиться","Як перевірити резервну копію Supabase: відновити в тестовий проєкт, порахувати рядки, увійти й знайти рядок, якого бракує обрізаному файлу.","2026-09-28","\u002Fblog\u002Ftest-your-supabase-backup\u002Fcard-800x500.png",{"path":921,"title":922,"description":923,"published":924,"category":729,"image":925,"draft":629},"\u002Fblog\u002Fsupabase-backup-github-action","Безкоштовна резервна копія Supabase через GitHub Action і підступ","Резервна копія Supabase через GitHub Action безкоштовна й годиться багатьом застосункам. Робочий процес, потрібний рядок підключення та egress.","2026-09-27","\u002Fblog\u002Fsupabase-backup-github-action\u002Fcard-800x500.png",{"path":927,"title":928,"description":929,"published":930,"category":729,"image":931,"draft":629},"\u002Fblog\u002Fdownload-your-supabase-backup","Чому ви не можете завантажити резервну копію Supabase","Завантажити резервну копію Supabase на сучасному проєкті не вийде: щоденна копія там фізична. Як це перевірити і як тримати власну копію.","2026-09-26","\u002Fblog\u002Fdownload-your-supabase-backup\u002Fcard-800x500.png",{"path":933,"title":934,"description":935,"published":930,"category":625,"image":936,"draft":629},"\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":938,"title":939,"description":940,"published":941,"category":625,"image":942,"draft":629},"\u002Fblog\u002Fopen-api-endpoint-exposed","Чи є відкритий API-ендпоінт проблемою безпеки? Читайте JSON","Ваше сканування позначило відкритий API-ендпоінт. Чи це важливо, залежить від того, що повернулося, і більшість знайдених належали платформі.","2026-09-25","\u002Fblog\u002Fopen-api-endpoint-exposed\u002Fcard-800x500.png",{"path":944,"title":945,"description":946,"published":947,"category":625,"image":948,"draft":629},"\u002Fblog\u002Flovable-security-scan","Сканування безпеки Lovable: чого воно не доведе","Сканування безпеки Lovable: що перевіряють Quick scan і Deep scan, коли запускається кожне, і єдине, чого не доведе жодне сканування зсередини.","2026-09-24","\u002Fblog\u002Flovable-security-scan\u002Fcard-800x500.png",{"path":950,"title":951,"description":952,"published":953,"category":729,"image":954,"draft":629},"\u002Fblog\u002Fsupabase-backup-auth-users","Чому у вашому дампі Supabase немає користувачів","Запустіть supabase db dump окремо і отримаєте форму вашої бази та жодного її рядка, без схеми auth, у якій живуть ваші користувачі.","2026-09-23","\u002Fblog\u002Fsupabase-backup-auth-users\u002Fcard-800x500.png",{"path":956,"title":957,"description":958,"published":959,"category":625,"image":960,"draft":629},"\u002Fblog\u002Fdomain-and-certificate-expiry","Домен закінчився, сайт лежить: що буде далі","Ваш домен закінчився і сайт лежить. Ось який годинник уже пішов, чому прострочений сертифікат це менша з двох бід, і як перевірити обидва.","2026-09-22","\u002Fblog\u002Fdomain-and-certificate-expiry\u002Fcard-800x500.png",{"path":962,"title":963,"description":964,"published":965,"category":625,"image":966,"draft":629},"\u002Fblog\u002Fis-lovable-safe","Чи безпечний Lovable? Що показали 18 554 застосунки Lovable","Чи безпечний Lovable? Ми прогнали 18 554 живі застосунки Lovable через дев’ять перевірок. Платформа виявилася найчистішою з п’яти. Знахідки були в застосунках.","2026-09-21","\u002Fblog\u002Fis-lovable-safe\u002Fcard-800x500.png",{"path":968,"title":969,"description":970,"published":971,"category":625,"image":972,"draft":629},"\u002Fblog\u002Fvibe-coded-app-security-checklist","Чек-лист безпеки для vibe coding, у девʼяти перевірках","Чек-лист безпеки для vibe coding із девʼяти пунктів, кожен з яких будь-хто може перевірити у вашому живому застосунку ззовні, і кожен має тест на один рядок.","2026-09-20","\u002Fblog\u002Fvibe-coded-app-security-checklist\u002Fcard-800x500.png",{"path":974,"title":975,"description":976,"published":977,"category":625,"image":978,"draft":629},"\u002Fblog\u002Fstripe-secret-key-in-frontend","Секретний ключ Stripe у фронтенді може рухати гроші","Секретний ключ Stripe, відкритий у вашому фронтенді, може повертати кошти, списувати й читати кожну картку клієнта. А ключ pk_live_ там і має бути.","2026-09-19","\u002Fblog\u002Fstripe-secret-key-in-frontend\u002Fcard-800x500.png",{"path":980,"title":981,"description":982,"published":983,"category":625,"image":984,"draft":629},"\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":986,"title":987,"description":988,"published":989,"category":625,"image":990,"draft":629},"\u002Fblog\u002Fbase44-source-maps","Безпека Base44: що позначає скан і що з цього виправляти вам","Безпека Base44 на 5 438 просканованих застосунках: три знахідки майже на кожному належать платформі, source map є бейджем Base44, а ваша частина коротка.","2026-09-17","\u002Fblog\u002Fbase44-source-maps\u002Fcard-800x500.png",{"path":992,"title":993,"description":994,"published":995,"category":625,"image":996,"draft":629},"\u002Fblog\u002Fis-cursor-ai-safe","Чи безпечний Cursor AI? Редактор, код і опублікований застосунок","Чи безпечний Cursor AI? Три запитання в одному пошуку: що Cursor зберігає, у чому помиляється його код і чи відкритий застосунок, який ви опублікували.","2026-09-16","\u002Fblog\u002Fis-cursor-ai-safe\u002Fcard-800x500.png",{"path":998,"title":999,"description":1000,"published":1001,"category":625,"image":1002,"draft":629},"\u002Fblog\u002Fis-replit-safe","Чи безпечний Replit? Що ми знайшли в 3 042 живих застосунках Replit","Чи безпечний Replit? Ми прогнали 3 042 живі застосунки Replit через дев’ять зовнішніх перевірок. Знахідки були в опублікованому застосунку, а не в хостингу.","2026-09-15","\u002Fblog\u002Fis-replit-safe\u002Fcard-800x500.png",{"path":1004,"title":1005,"description":1006,"published":1007,"category":729,"image":1008,"draft":629},"\u002Fblog\u002Fsupabase-storage-backup","Supabase storage backup: у копії бази даних немає жодного файлу","Supabase storage backup це окрема робота. Копії бази даних зберігають список ваших файлів і жодного з них, тож після відновлення кожне завантаження зламане.","2026-09-14","\u002Fblog\u002Fsupabase-storage-backup\u002Fcard-800x500.png",{"path":1010,"title":1011,"description":1012,"published":1013,"category":729,"image":1014,"draft":629},"\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":1016,"title":1017,"description":1018,"published":1019,"category":729,"image":1020,"draft":629},"\u002Fblog\u002Fsupabase-project-paused-recover","Проєкт Supabase на паузі? Ваші дані нікуди не зникли","Supabase поставив ваш проєкт на паузу після тижня без активності. Нічого не видалено, кнопка Restore стоїть поруч із назвою, і у вас є рік, щоб натиснути.","2026-09-12","\u002Fblog\u002Fsupabase-project-paused-recover\u002Fcard-800x500.png",{"path":1022,"title":1023,"description":1024,"published":1025,"category":625,"image":1026,"draft":629},"\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":1028,"title":1029,"description":1030,"published":1031,"category":625,"image":1032,"draft":629},"\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":1034,"title":1035,"description":1036,"published":1037,"category":625,"image":1038,"draft":629},"\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":1040,"title":1041,"description":1042,"published":1043,"category":625,"image":1044,"draft":629},"\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":1046,"title":1047,"description":1048,"published":1049,"category":625,"image":1050,"draft":629},"\u002Fblog\u002Fvibe-coding-security-scanners-compared","Сканери безпеки для vibe coding у порівнянні, наш теж","Найкращий сканер безпеки для vibe coding залежить від трьох питань, на які не відповідає жоден перелік функцій. Вісім інструментів і ціни.","2026-09-07","\u002Fblog\u002Fvibe-coding-security-scanners-compared\u002Fcard-800x500.png",{"path":1052,"title":1053,"description":1054,"published":1055,"category":625,"image":1056,"draft":629},"\u002Fblog\u002Fsupabase-security-checker","Supabase security checker: п’ять перевірок, які зробите самі","Supabase security checker читає ваш опублікований застосунок, а не налаштування проєкту. Ось п’ять перевірок і як виконати кожну самостійно.","2026-09-06","\u002Fblog\u002Fsupabase-security-checker\u002Fcard-800x500.png",{"path":1058,"title":1059,"description":1060,"published":1061,"category":625,"image":1062,"draft":629},"\u002Fblog\u002Fvibe-coding-security-scanner","Сканер безпеки для vibe coding: чого не бачить скан URL","Сканер безпеки для vibe coding читає ваш робочий застосунок ззовні. Що це охоплює, чотири речі, яких він не бачить, і як читати результат.","2026-09-05","\u002Fblog\u002Fvibe-coding-security-scanner\u002Fcard-800x500.png",{"path":1064,"title":1065,"description":1066,"published":1067,"category":625,"image":1068,"draft":629},"\u002Fblog\u002Fmissing-security-headers","Відсутні заголовки безпеки: коли це справді важливо","Відсутні заголовки безпеки трапляються в наших звітах найчастіше. Від чого вони захищають і коли це найменш терміновий рядок вашого звіту.","2026-09-04","\u002Fblog\u002Fmissing-security-headers\u002Fcard-800x500.png",{"path":1070,"title":1071,"description":1072,"published":1073,"category":625,"image":1074,"draft":629},"\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":1076,"title":1077,"description":1078,"published":1079,"category":625,"image":1080,"draft":629},"\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":1082,"title":1083,"description":1084,"published":1085,"category":729,"image":1086,"draft":629},"\u002Fblog\u002Fsupabase-backup-tools-compared","Інструменти резервного копіювання Supabase, і наш серед них","Чотири види інструментів резервного копіювання Supabase, що копіює кожен і коли безкоштовна GitHub Action виграє в будь-якої оплати, нашої теж.","2026-09-01","\u002Fblog\u002Fsupabase-backup-tools-compared\u002Fcard-800x500.png",{"path":1088,"title":1089,"description":1090,"published":1091,"category":625,"image":1092,"draft":629},"\u002Fblog\u002Freplit-secrets-explained","Як користуватися секретами в Replit і що все одно публікується","Як користуватися секретами в Replit: додати, прочитати і усунути дві причини undefined. А ще ключі, які цей інструмент не здатен зберегти в таємниці.","2026-08-31","\u002Fblog\u002Freplit-secrets-explained\u002Fcard-800x500.png",{"path":1094,"title":1095,"description":1096,"published":1097,"category":729,"image":1098,"draft":629},"\u002Fblog\u002Fback-up-supabase-free-tier","Резервні копії на безкоштовному тарифі Supabase без терміналу","На безкоштовному тарифі Supabase резервних копій немає: копію робите ви. Як зробити її з панелі та що втрачає шлях через CSV.","2026-08-30","\u002Fblog\u002Fback-up-supabase-free-tier\u002Fcard-800x500.png",{"path":1100,"title":1101,"description":1102,"published":1103,"category":625,"image":1104,"draft":629},"\u002Fblog\u002Fis-supabase-secure","Чи безпечний Supabase? Так. Ваш проєкт це інше питання","Чи безпечний Supabase? Платформа проходить аудити, шифрує дані й тримає пентести. Їхня документація каже, де це закінчується і починаються ваші налаштування.","2026-08-29","\u002Fblog\u002Fis-supabase-secure\u002Fcard-800x500.png",{"path":1106,"title":1107,"description":1108,"published":1109,"category":625,"image":1110,"draft":629},"\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":1112,"title":1113,"description":1114,"published":1115,"category":625,"image":1116,"draft":629},"\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":1118,"title":1119,"description":1120,"published":1121,"category":625,"image":1122,"draft":629},"\u002Fblog\u002Fcors-wildcard-security-risk","Чи є вайлдкард CORS ризиком для безпеки? Майже ніколи.","Чи є вайлдкард CORS ризиком для безпеки? Зазвичай це типове налаштування вашого білдера, і воно не видає нічого, що ваш сервер і так віддавав кожному.","2026-08-26","\u002Fblog\u002Fcors-wildcard-security-risk\u002Fcard-800x500.png",{"path":1124,"title":1125,"description":1126,"published":1127,"category":729,"image":1128,"draft":629},"\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":1130,"title":1131,"description":1132,"published":1133,"category":729,"image":1134,"draft":629},"\u002Fblog\u002Fhow-to-restore-a-supabase-backup","Як відновити резервну копію Supabase, і що ламається потім","Як відновити резервну копію Supabase з панелі або з файлу дампа, що саме замінює відновлення і чому ваш застосунок може лишитися зламаним після нього.","2026-08-24","\u002Fblog\u002Fhow-to-restore-a-supabase-backup\u002Fcard-800x500.png",{"path":1136,"title":1137,"description":1138,"published":1139,"category":625,"image":1140,"draft":629},"\u002Fblog\u002Fsupabase-storage-bucket-public","Ваш бакет Supabase Storage публічний. Чи це проблема?","Публічний бакет Supabase Storage означає, що той, хто має URL файлу, може його відкрити. Він не означає, що хтось може переглянути список вмісту.","2026-08-23","\u002Fblog\u002Fsupabase-storage-bucket-public\u002Fcard-800x500.png",{"path":1142,"title":1143,"description":1144,"published":1145,"category":625,"image":1146,"draft":629},"\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":1148,"title":1149,"description":1150,"published":1151,"category":729,"image":1152,"draft":629},"\u002Fblog\u002Fai-agent-deleted-my-database","ШІ-агент видалив мої дані в Supabase. Що можна відновити?","ШІ-агент видалив дані з вашої бази. Те, що ви зможете відновити, вирішилося раніше, а наступні хвилини вирішують, скільки з цього вціліє.","2026-08-21","\u002Fblog\u002Fai-agent-deleted-my-database\u002Fcard-800x500.png",{"path":1154,"title":1155,"description":1156,"published":1157,"category":625,"image":1158,"draft":629},"\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":1160,"title":1161,"description":1162,"published":1157,"category":625,"image":1163,"draft":629},"\u002Fblog\u002Fsource-maps-exposed-in-production","Відкриті source maps: ваш застосунок публікує свій вихідний код","Відкрита source map дає будь-кому прочитати оригінальний код вашого застосунку, разом із коментарями. Перевірка за 30 секунд, і що важить насправді.","\u002Fblog\u002Fsource-maps-exposed-in-production\u002Fcard-800x500.png",{"path":1165,"title":1166,"description":1167,"published":1168,"category":625,"image":1169,"draft":629},"\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":1171,"title":1172,"description":1173,"published":1174,"category":729,"image":1175,"draft":629},"\u002Fblog\u002Fdoes-supabase-back-up-my-database","Чи робить Supabase резервні копії моєї бази? Залежить від тарифу.","Чи робить Supabase резервні копії вашої бази даних? На платних тарифах щодня, на безкоштовному жодної. Як дізнатися, що у вас насправді є.","2026-08-11","\u002Fblog\u002Fdoes-supabase-back-up-my-database\u002Fcard-800x500.png",{"path":1177,"title":1178,"description":1179,"published":1180,"category":625,"image":1181,"draft":629},"\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":1183,"title":1184,"description":1185,"published":1180,"category":729,"image":1186,"draft":629},"\u002Fblog\u002Fversion-history-is-not-a-backup","Історія версій не є резервною копією. Вона не поверне таблицю.","Lovable і Bolt ведуть історію версій вашого коду. Ваша база даних працює як окремий сервіс, тож відкат назад не поверне ваші дані.","\u002Fblog\u002Fversion-history-is-not-a-backup\u002Fcard-800x500.png",{"path":1188,"title":1189,"description":1190,"published":1191,"category":729,"image":1192,"draft":629},"\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":371,"title":1194,"description":1195,"published":1191,"category":625,"image":1196,"draft":629},"Які API-ключі безпечні у браузері, а які ні","Ваш ключ anon у Supabase створений, щоб бути публічним. Ключ service_role для цього не створений, і він ігнорує всі правила, які ви задали. Як їх розрізнити.","\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend\u002Fcard-800x500.png",1794213654243]