[{"data":1,"prerenderedAt":1367},["ShallowReactive",2],{"blog-es-canceling-statement-due-to-statement-timeout":3,"blog-index-es":838},{"id":4,"title":5,"body":6,"category":796,"cover":797,"coverAlt":798,"description":799,"draft":800,"extension":801,"faq":802,"image":817,"keywords":818,"meta":826,"navigation":827,"ogTitle":828,"path":829,"published":830,"seo":831,"stem":832,"tldr":833,"updated":830,"__hash__":837},"blog_es\u002Fblog\u002Fcanceling-statement-due-to-statement-timeout.md","Supabase \"canceling statement due to statement timeout\", explicado",{"type":7,"value":8,"toc":782},"minimark",[9,13,24,27,30,35,38,57,60,64,67,70,148,158,161,167,171,178,194,202,223,226,235,239,246,261,267,278,285,288,292,295,298,404,407,414,424,428,431,439,445,465,470,476,494,511,525,531,537,540,546,557,562,566,569,575,582,588,599,603,606,634,656,664,668,674,683,703,712,729,733,775],[10,11,12],"p",{},"La página de tu app carga su lista, el indicador gira unos segundos y luego no\naparece nada. En la consola del navegador, o en un aviso rojo que añadió tu\nbuilder, hay una línea:",[14,15,20],"pre",{"className":16,"code":18,"language":19},[17],"language-text","canceling statement due to statement timeout\n","text",[21,22,18],"code",{"__ignoreMap":23},"",[10,25,26],{},"Abres la misma tabla en Supabase y están todas las filas. Le preguntas al\nbuilder con el que hiciste la app (Lovable, Bolt, v0, Cursor, Claude Code o\nReplit) qué consulta ejecuta esa página, la pegas en el SQL Editor y vuelve en\nun momento.",[10,28,29],{},"El primer consejo que encontrarás para \"canceling statement due to statement\ntimeout\" en Supabase es subir el límite. Ese límite está haciendo su trabajo, y\nlo comparte cada visitante de tu app. Antes de subir nada, mira la política de\nRow Level Security de esa tabla, la regla que Postgres comprueba antes de\nentregar cada fila. En las mediciones de la propia Supabase, esa regla es donde\nuna consulta que debería tardar 7 milisegundos pasa 11 segundos.",[31,32,34],"h2",{"id":33},"qué-significa-canceling-statement-due-to-statement-timeout-en-supabase","¿Qué significa \"canceling statement due to statement timeout\" en Supabase?",[10,36,37],{},"Postgres, la base de datos que hay dentro de tu proyecto de Supabase, detuvo la\nconsulta porque duró más de lo que permite el rol que la envió, y deshizo lo que\nla consulta había hecho.",[10,39,40,41,44,45,48,49,52,53,56],{},"La frase viene del propio Postgres, con el código de error ",[21,42,43],{},"57014",", que Postgres\nllama ",[21,46,47],{},"query_canceled",". Supabase se la pasa a tu app sin cambios, así que el\nerror que recibe tu código lleva ",[21,50,51],{},"code: '57014'"," y esa frase como mensaje.\nPostgres usa el mismo código para ",[21,54,55],{},"canceling statement due to user request",", una\ncancelación que alguien pidió, por eso la frase te dice más que el número.",[10,58,59],{},"Imagina tu base de datos como una biblioteca con un mostrador y un temporizador\nde cocina. Cada vez que alguien pide libros, la bibliotecaria pone el\ntemporizador antes de ir a las estanterías, y cuánto dura depende de quién\npregunta. Cuando suena, cada libro vuelve a su sitio y el visitante recibe una\nnota con esta frase. Si la petición era para cambiar algo, el cambio también se\ndeshace.",[31,61,63],{"id":62},"por-qué-la-consulta-funciona-en-el-sql-editor-y-falla-en-mi-app","¿Por qué la consulta funciona en el SQL Editor y falla en mi app?",[10,65,66],{},"Porque el SQL Editor pregunta como otro visitante, uno sin temporizador propio y\nsin reglas que comprobar.",[10,68,69],{},"Cada petición llega a Postgres con un rol, un nombre que decide lo que la\npetición puede hacer. La guía de timeouts de Supabase da el límite con el que\nempieza cada rol:",[71,72,73,89],"table",{},[74,75,76],"thead",{},[77,78,79,83,86],"tr",{},[80,81,82],"th",{},"Quién pregunta",[80,84,85],{},"Rol",[80,87,88],{},"Tiempo permitido",[90,91,92,106,119,135],"tbody",{},[77,93,94,98,103],{},[95,96,97],"td",{},"Un visitante de tu app sin sesión",[95,99,100],{},[21,101,102],{},"anon",[95,104,105],{},"3 segundos",[77,107,108,111,116],{},[95,109,110],{},"Un usuario de tu app con sesión",[95,112,113],{},[21,114,115],{},"authenticated",[95,117,118],{},"8 segundos",[77,120,121,124,129],{},[95,122,123],{},"Tu servidor, con la clave secreta",[95,125,126],{},[21,127,128],{},"service_role",[95,130,131,132],{},"ninguno propio, así que rigen los 8 segundos del rol ",[21,133,134],{},"authenticator",[77,136,137,140,145],{},[95,138,139],{},"Tú, en el dashboard y en el SQL Editor",[95,141,142],{},[21,143,144],{},"postgres",[95,146,147],{},"ninguno propio, con un tope por defecto de 2 minutos",[10,149,150,151,153,154,157],{},"El rol del editor tiene una segunda ventaja. La guía de Row Level Security de\nSupabase dice que en Supabase ",[21,152,144],{}," tiene ",[21,155,156],{},"bypassrls",", el atributo de\nPostgres que se salta todas las políticas. Así que el editor responde sin leer\nni una regla. La petición de tu app lee cada política de la tabla en cada fila\nque mira, con un temporizador que suena a los tres segundos para un visitante\nsin sesión. El editor midió una consulta distinta de la que envía tu app.",[10,159,160],{},"En la biblioteca tú eres del personal. Entras al depósito sin temporizador y\nnadie te pide el carnet. El visitante tiene tres segundos, y la bibliotecaria\ncomprueba las reglas en cada libro por el camino.",[162,163],"diagram",{"alt":164,"caption":165,"src":166},"La misma consulta dibujada como tres barras sobre una escala de tiempo. Para postgres la barra llega a su final sin marcas y termina con un visto. Para authenticated y anon la barra lleva una marca en cada paso; la de authenticated termina con un visto antes de su línea de 8 segundos, y la de anon queda cortada en rojo en su línea de 3 segundos.","Una consulta, tres roles. El rol del editor se salta la política y no tiene línea que cruzar; un visitante sin sesión pasa cada comprobación y se detiene a los tres segundos.","\u002Fblog\u002Fcanceling-statement-due-to-statement-timeout\u002Fthree-clocks-1600x720.png",[31,168,170],{"id":169},"encuentra-la-consulta-que-se-canceló","Encuentra la consulta que se canceló",[10,172,173,174,177],{},"Busca en los logs de Postgres de tu proyecto las palabras ",[21,175,176],{},"statement timeout"," y\nanota qué rol envió la consulta.",[10,179,180,181,185,186,189,190,193],{},"La guía de timeouts de Supabase da una consulta para esto. En el dashboard, abre\n",[182,183,184],"strong",{},"Explorer",", elige ",[182,187,188],{},"Run SQL",", pon ",[182,191,192],{},"Logs"," como origen, elige un rango de\ntiempo que cubra el fallo y ejecuta esta versión recortada:",[14,195,200],{"className":196,"code":198,"language":199,"meta":23},[197],"language-sql","select timestamp,\n       log_attributes['parsed.user_name'] as user_name,\n       log_attributes['parsed.query'] as query\nfrom logs\nwhere source = 'postgres_logs'\n  and match(event_message, 'statement timeout')\norder by timestamp desc\nlimit 100;\n","sql",[21,201,198],{"__ignoreMap":23},[10,203,204,205,208,209,211,212,215,216,218,219,222],{},"Un ",[21,206,207],{},"user_name"," de ",[21,210,134],{}," significa que la petición llegó por la Data\nAPI, el servidor por el que pasa cada llamada ",[21,213,214],{},"supabase.from(…)"," de tu app. La\ntabla de roles de la guía da ",[21,217,134],{}," como PostgREST, que es ese\nservidor. La columna ",[21,220,221],{},"query"," es el SQL en que se convirtió la petición de tu\napp, y el nombre de tabla que aparece ahí es la tabla que hay que mirar.",[10,224,225],{},"La página Query Performance del dashboard es el otro sitio donde se suele\nmirar, y la guía advierte que solo muestra consultas lentas que terminaron. Una\nconsulta cancelada está en los logs.",[10,227,228,229,234],{},"Si los logs muestran cancelaciones en todas las tablas a la vez, la parte lenta\nno es una sola política, y\n",[230,231,233],"a",{"href":232},"\u002Fblog\u002Fis-supabase-down-or-your-app","distinguir una caída de Supabase de un problema en tu app","\nes por donde empezar.",[31,236,238],{"id":237},"mídela-como-la-ejecuta-tu-app","Mídela como la ejecuta tu app",[10,240,241,242,245],{},"Ejecuta la consulta con el rol de tu app y ",[21,243,244],{},"explain analyze"," delante, y Postgres\nimprime cuánto tardó y cuántas filas rechazó la política.",[10,247,248,249,252,253,256,257,260],{},"Es el método de la guía de Supabase sobre el rendimiento de Row Level Security,\ndentro de ",[21,250,251],{},"begin"," y ",[21,254,255],{},"rollback"," para que el cambio de rol termine con el bloque.\nCopia el id de un usuario real desde ",[182,258,259],{},"Authentication → Users"," en lugar de los\nceros:",[14,262,265],{"className":263,"code":264,"language":199,"meta":23},[197],"begin;\nset local role authenticated;\nset local request.jwt.claims to '{\"role\":\"authenticated\", \"sub\":\"00000000-0000-0000-0000-000000000000\"}';\nexplain analyze select * from public.orders;\nrollback;\n",[21,266,264],{"__ignoreMap":23},[10,268,269,270,273,274,277],{},"Dos líneas de la salida importan. ",[21,271,272],{},"Execution Time"," es el número que comparas con\n3 y 8 segundos. ",[21,275,276],{},"Rows Removed by Filter"," cuenta las filas que Postgres leyó y la\npolítica rechazó, y la guía de Supabase la llama la señal a vigilar: una\npolítica que descarta la mayor parte de la tabla en cada lectura es una política\ncuya columna de filtro necesita un índice.",[10,279,280,281,284],{},"Para medir lo que recibe un visitante sin sesión, escribe ",[21,282,283],{},"set local role anon;","\ny quita la línea de los claims.",[10,286,287],{},"La misma guía sugiere ejecutar la consulta una vez con Row Level Security\nactivada y otra desactivada, para ver si el coste está en la política. También\ndice que eso se haga solo fuera de producción, porque desactivarla expone cada\nfila. En una app en producción, mide la política contra su versión reescrita.",[31,289,291],{"id":290},"cómo-puede-una-política-costar-segundos","¿Cómo puede una política costar segundos?",[10,293,294],{},"Postgres comprueba la política en cada fila que lee la consulta, y una llamada a\nfunción dentro de la política se vuelve a ejecutar por cada una de esas filas.",[10,296,297],{},"Supabase publicó mediciones de esto, sobre una tabla de 100.000 filas. Cuatro de\nellas, la forma lenta y la rápida:",[71,299,300,318],{},[74,301,302],{},[77,303,304,307,310,313,316],{},[80,305,306],{},"Qué comprueba la política",[80,308,309],{},"Escrita como",[80,311,312],{},"Tardó",[80,314,315],{},"Reescrita como",[80,317,312],{},[90,319,320,341,362,383],{},[77,321,322,325,330,333,338],{},[95,323,324],{},"Filas que pertenecen al usuario con sesión",[95,326,327],{},[21,328,329],{},"auth.uid() = user_id",[95,331,332],{},"179 ms",[95,334,335],{},[21,336,337],{},"(select auth.uid()) = user_id",[95,339,340],{},"9 ms",[77,342,343,346,350,353,359],{},[95,344,345],{},"La misma regla, sin índice en la columna",[95,347,348],{},[21,349,329],{},[95,351,352],{},"171 ms",[95,354,355,356],{},"la misma, con un índice en ",[21,357,358],{},"user_id",[95,360,361],{},"menos de 0,1 ms",[77,363,364,367,372,375,380],{},[95,365,366],{},"Una comprobación de admin que lee otra tabla",[95,368,369],{},[21,370,371],{},"is_admin()",[95,373,374],{},"11.000 ms",[95,376,377],{},[21,378,379],{},"(select is_admin())",[95,381,382],{},"7 ms",[77,384,385,388,393,396,401],{},[95,386,387],{},"Un rol buscado en otra tabla",[95,389,390],{},[21,391,392],{},"has_role() = role",[95,394,395],{},"178.000 ms",[95,397,398],{},[21,399,400],{},"(select has_role()) = role",[95,402,403],{},"12 ms",[10,405,406],{},"Las dos primeras son lentas y aun así quedan por debajo de tres segundos en esa\ntabla. Las dos últimas superan cualquier límite que tenga una petición de una\napp. Una función helper que lee una segunda tabla, del tipo que se escribe para\nsaber si alguien es admin, se llama una vez por cada fila de la primera, así que\nen esa tabla leyó la segunda 100.000 veces.",[10,408,409,410,413],{},"En la biblioteca, ese helper es la bibliotecaria yendo a la oficina de la\nentrada a preguntar si eres del personal, una vez por cada libro de la\nestantería. Envolverlo en ",[21,411,412],{},"(select …)"," es preguntar una vez y apuntar la\nrespuesta en la nota.",[10,415,416,417],{},"El Performance Advisor de Supabase detecta una parte de esto. Su regla llamada\nAuth RLS Initialization Plan señala una política que \"re-evaluates\ncurrent_setting() or auth.",[418,419,420,421,423],"function",{},"() for each row\". Un helper tuyo, como\n",[21,422,371],{},", queda fuera de esa descripción, así que una página del advisor\nsin avisos no ha revisado tus helpers. Lee tú mismo las políticas de la tabla\nbuscando cualquier llamada a función que no esté envuelta.",[31,425,427],{"id":426},"arréglalo-en-este-orden","Arréglalo en este orden",[10,429,430],{},"Envuelve las llamadas a funciones, indexa la columna que compara la política y\nluego nombra el rol. Cada paso es un trozo corto de SQL que puedes ejecutar en el\neditor o pasarle a tu builder.",[10,432,433],{},[182,434,435,436,438],{},"1. Envuelve cada llamada a función en ",[21,437,412],{},".",[14,440,443],{"className":441,"code":442,"language":199,"meta":23},[197],"alter policy \"read own orders\" on public.orders\n  using ( (select auth.uid()) = user_id );\n",[21,444,442],{"__ignoreMap":23},[10,446,447,450,451,454,455,459,460,464],{},[21,448,449],{},"alter policy"," conserva el nombre y el rol de la política y sustituye la\ncondición. Usa el nombre de tu política y la columna de tu tabla, y haz lo mismo\ncon un helper: ",[21,452,453],{},"(select private.is_admin())",". Supabase pone una condición: solo\nfunciona con un valor que no cambia de una fila a otra. Por qué los paréntesis\nmarcan la diferencia está en\n",[230,456,458],{"href":457},"\u002Fblog\u002Fenable-rls-on-every-supabase-table","la guía para activar Row Level Security",",\ny un helper escrito para\n",[230,461,463],{"href":462},"\u002Fblog\u002Finfinite-recursion-in-policy-for-relation","resolver un error de recursión infinita","\nes justo del tipo que los necesita.",[10,466,467],{},[182,468,469],{},"2. Indexa la columna que compara la política.",[14,471,474],{"className":472,"code":473,"language":199,"meta":23},[197],"create index orders_user_id_idx on public.orders (user_id);\n",[21,475,473],{"__ignoreMap":23},[10,477,478,479,482,483,485,486,489,490,493],{},"La guía de Supabase añade una pega. Una columna solo cuenta como indexada cuando\nva primera en el índice, así que una tabla cuya clave primaria es\n",[21,480,481],{},"(team_id, user_id)"," no tiene índice en ",[21,484,358],{},". En una tabla grande en la que\nse escribe mientras trabajas, la documentación de Postgres dice que un\n",[21,487,488],{},"create index"," normal bloquea las escrituras en la tabla hasta que termina.\n",[21,491,492],{},"create index concurrently"," lo construye sin ese bloqueo, y tiene que ejecutarse\ncomo una sentencia aparte, fuera de una transacción.",[10,495,496,499,500,503,504,507,508,510],{},[182,497,498],{},"3. Nombra el rol al que va dirigida la política."," Una política escrita\n",[21,501,502],{},"to authenticated"," nunca se ejecuta para un visitante sin sesión. En las\nmediciones de Supabase, una petición sin sesión contra una política sin ",[21,505,506],{},"to","\ntardó 170 ms, y con ",[21,509,502],{}," menos de 0,1 ms.",[10,512,513,516,517,520,521,524],{},[182,514,515],{},"4. Filtra también en la petición."," Una política funciona como un ",[21,518,519],{},"where","\ninvisible en cada consulta, y la guía de Supabase recomienda repetirlo en la\npetición de tu app, ",[21,522,523],{},".eq('user_id', userId)",", porque Postgres puede planificar\nuna consulta más rápida con él. En sus mediciones eso llevó una consulta de 171\nms a 9.",[10,526,527,530],{},[182,528,529],{},"5. Saca el join de una política de equipos."," Una política que compara cada\nfila con una tabla de miembros hace esa búsqueda por fila:",[14,532,535],{"className":533,"code":534,"language":199,"meta":23},[197],"using ( (select auth.uid()) in (\n  select user_id from team_user where team_user.team_id = projects.team_id\n) )\n",[21,536,534],{"__ignoreMap":23},[10,538,539],{},"La guía de Supabase la reescribe para que lea los equipos una vez y compare con\nesa lista:",[14,541,544],{"className":542,"code":543,"language":199,"meta":23},[197],"using ( team_id in (\n  select team_id from team_user where user_id = (select auth.uid())\n) )\n",[21,545,543],{"__ignoreMap":23},[10,547,548,549,551,552,252,554,556],{},"Después vuelve a ejecutar el ",[21,550,244],{}," de antes y compara\n",[21,553,272],{},[21,555,276],{}," con lo que tenías.",[162,558],{"alt":559,"caption":560,"src":561},"Dos estanterías con las mismas doce filas. A la izquierda, una línea de búsqueda pasa por cada fila, cada una lleva una marca de visto y tres están iluminadas. A la derecha, una ficha de índice con tres pestañas apunta directamente a esas mismas tres filas iluminadas, y el resto no lleva marca.","Sin índice, la política lee cada fila para encontrar las pocas que son tuyas. Con uno, Postgres va directo a ellas.","\u002Fblog\u002Fcanceling-statement-due-to-statement-timeout\u002Fevery-row-or-the-few-1600x700.png",[31,563,565],{"id":564},"debería-aumentar-el-statement-timeout","¿Debería aumentar el statement timeout?",[10,567,568],{},"Solo para una consulta que has medido, reescrito y sigues necesitando, y\nentonces para esa función concreta, no para un rol entero.",[10,570,571,572,574],{},"Los límites son deliberados. La guía de importación de datos de Supabase dice\nque los aplica para repartir los recursos de forma justa y evitar que las\nconsultas largas afecten al sistema entero. El límite de ",[21,573,102],{}," lo comparten\ntodos los visitantes sin sesión, y cualquiera puede enviar esa petición con la\nclave publicable que va dentro de tu app. Subirlo de tres segundos a treinta da\na cada una de esas peticiones treinta segundos de tu base de datos.",[10,576,577,578,581],{},"Cuando un trabajo de verdad tarda más, dale el límite a la función que lo\nejecuta. La guía de Supabase describe esto para funciones recurrentes que\nnecesitan una excepción en su tiempo de ejecución, y funciona para una función\nque tu app llama con ",[21,579,580],{},"supabase.rpc(…)",":",[14,583,586],{"className":584,"code":585,"language":199,"meta":23},[197],"alter function public.monthly_totals() set statement_timeout to '20s';\n",[21,587,585],{"__ignoreMap":23},[10,589,590,591,594,595,598],{},"Si cambias un rol de todas formas, los comandos de la guía son\n",[21,592,593],{},"alter role authenticated set statement_timeout = '15s';"," y después\n",[21,596,597],{},"notify pgrst, 'reload config';",", para que la Data API recoja el valor nuevo.\nSupabase limita a 60 segundos las consultas de sus librerías cliente, pongas lo\nque pongas.",[31,600,602],{"id":601},"dos-arreglos-que-quitan-el-mensaje-y-abren-la-tabla","Dos arreglos que quitan el mensaje y abren la tabla",[10,604,605],{},"Desactivar Row Level Security en la tabla, y enviar la petición con la clave\nsecreta. Los dos hacen la consulta rápida porque los dos se saltan la política,\ny los dos entregan las filas a gente que nunca debió tenerlas.",[10,607,608,611,612,615,616,619,620,623,624,252,627,630,631,633],{},[182,609,610],{},"Desactivarla."," Sin política que ejecutar, no se comprueba nada por fila y el\ntimeout desaparece. Supabase imprime su propia advertencia junto a la prueba\nque lo sugiere: desactivar Row Level Security expone cada fila de la tabla a\ncualquier rol con el permiso correspondiente. Su guía también dice que, en\nproyectos existentes, una tabla nueva en ",[21,613,614],{},"public"," empieza con ",[21,617,618],{},"select",",\n",[21,621,622],{},"insert",", ",[21,625,626],{},"update",[21,628,629],{},"delete"," concedidos a ",[21,632,102],{},", el rol de un visitante sin\nsesión. Sin la política, esos permisos son lo único que hay entre ese visitante\ny la tabla.",[10,635,636,639,640,642,643,645,646,650,651,653,654,438],{},[182,637,638],{},"La clave secreta."," La guía de Supabase dice que el rol ",[21,641,128],{}," que\nhay detrás también tiene ",[21,644,156],{},", así que no se ejecuta ninguna política.\nEsa clave va en un servidor, y una enviada desde el navegador es una clave que\ncualquiera puede leer en tu sitio;\n",[230,647,649],{"href":648},"\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend","qué claves son seguras en un frontend","\nrepasa el par. Tampoco da mucho tiempo, porque la guía le da a ",[21,652,128],{},"\nlos mismos 8 segundos que a ",[21,655,134],{},[10,657,658,659,663],{},"Si alguna tabla tuya responde a una petición sin ningún inicio de sesión es una\nde las nueve comprobaciones del ",[230,660,662],{"href":661},"\u002Fsecurity-scanner","escaneo gratuito de Reeve",".\nLee tu sitio en producción como lo ve un visitante, tarda unos 20 segundos y no\nnecesita cuenta.",[31,665,667],{"id":666},"que-la-política-siga-rápida-y-la-tabla-cerrada","Que la política siga rápida y la tabla cerrada",[10,669,670,673],{},[182,671,672],{},"Una política que arreglaste hoy tendrá una versión siguiente."," Tu builder\nescribe políticas cada vez que una función nueva necesita una, así que la\nsiguiente puede volver a llamar a una función por fila, o abrir la tabla para\nque cargue una pantalla.",[10,675,676],{},[182,677,678,682],{},[230,679,681],{"href":680},"\u002Fwebsite-monitoring","Reeve Monitor"," revisa tu app desde fuera, cada hora:",[684,685,686,690,693,700],"ul",{},[687,688,689],"li",{},"las nueve comprobaciones cada hora, incluida si alguna tabla responde a una petición sin inicio de sesión",[687,691,692],{},"si la aplicación responde, cada 60 segundos",[687,694,695,696,699],{},"un mensaje cuando un resultado ",[182,697,698],{},"cambia",", para que una tabla que empieza a responder a desconocidos no espere a que mires",[687,701,702],{},"un informe mensual de lo que vio",[10,704,705],{},[182,706,707,711],{},[230,708,710],{"href":709},"\u002Fsupabase-backups","Reeve Care"," guarda una copia de tu base de datos de\nSupabase de antes de tu cambio:",[684,713,714,717,720,723,726],{},[687,715,716],{},"una copia cifrada según un calendario, guardada en el almacenamiento propio de Reeve en la UE, fuera de tu cuenta de Supabase",[687,718,719],{},"cada copia verificada antes de contar, contando las filas de cada tabla",[687,721,722],{},"una restauración en un clic que guarda lo que hay ahora antes de reemplazarlo",[687,724,725],{},"también tus archivos subidos, en cuanto conectes una credencial de Storage",[687,727,728],{},"todo lo que hace Monitor",[31,730,732],{"id":731},"qué-hacer-hoy","Qué hacer hoy",[734,735,736],"key-takeaways",{},[684,737,738,744,760,766,772],{},[687,739,740,741,743],{},"Busca ",[21,742,176],{}," en los logs de Postgres y anota la consulta y el rol que la envió.",[687,745,746,747,749,750,752,753,755,756,252,758,438],{},"Mide esa consulta con ",[21,748,244],{}," como ",[21,751,102],{}," o ",[21,754,115],{},", los roles que usa tu app, y lee ",[21,757,272],{},[21,759,276],{},[687,761,762,763,765],{},"Envuelve en ",[21,764,412],{}," cada llamada a función de las políticas de esa tabla, incluidos tus propios helpers que el Performance Advisor no señala.",[687,767,768,769,771],{},"Indexa la columna que compara cada política, y añade ",[21,770,502],{}," a cada política pensada solo para usuarios con sesión.",[687,773,774],{},"Sube un límite al final, en la única función que lo necesita, y deja Row Level Security activada.",[10,776,777,778,781],{},"Empieza por la tabla que hay detrás de la página que falló. Si todavía no tiene\npolíticas propias,\n",[230,779,780],{"href":457},"activar Row Level Security en cada tabla","\ntiene las tres formas que necesitan la mayoría de las tablas.",{"title":23,"searchDepth":783,"depth":783,"links":784},3,[785,787,788,789,790,791,792,793,794,795],{"id":33,"depth":786,"text":34},2,{"id":62,"depth":786,"text":63},{"id":169,"depth":786,"text":170},{"id":237,"depth":786,"text":238},{"id":290,"depth":786,"text":291},{"id":426,"depth":786,"text":427},{"id":564,"depth":786,"text":565},{"id":601,"depth":786,"text":602},{"id":666,"depth":786,"text":667},{"id":731,"depth":786,"text":732},"Fundamentos de seguridad","\u002Fblog\u002Fcanceling-statement-due-to-statement-timeout\u002Fcover-1200x630.png","Un cronómetro con la aguja pasada de su marca, delante de un estante largo de filas en el que solo las primeras llevan una marca de visto.","Supabase \"canceling statement due to statement timeout\": una consulta superó el límite de su rol, 3 segundos para un visitante. Revisa antes la política RLS.",false,"md",[803,805,808,811,814],{"q":34,"a":804},"Postgres detuvo tu consulta porque duró más de lo que permite el rol que la envió, y deshizo lo que había hecho. Supabase da 3 segundos a un visitante sin sesión y 8 a un usuario con sesión. El error lleva el código 57014, que Postgres usa también para una consulta cancelada a petición, así que lee la frase además del número.",{"q":806,"a":807},"¿Por qué mi consulta funciona en el SQL Editor pero da timeout en mi app?","Porque el SQL Editor trabaja con el rol postgres y tu app no. Supabase no le da a postgres un límite propio, y el rol tiene bypassrls, así que el editor nunca ejecuta tus políticas de Row Level Security. Tu app funciona como anon o authenticated, con un límite de 3 u 8 segundos y cada política comprobada en cada fila que lee la consulta.",{"q":809,"a":810},"¿Puede Row Level Security causar un statement timeout?","Sí. Postgres comprueba la política en cada fila que lee la consulta, y una llamada a función dentro de la política se ejecuta otra vez por cada una. En mediciones que Supabase publicó para una tabla de 100.000 filas, una política que llamaba a un helper is_admin() tardó 11.000 milisegundos, y la misma política con la llamada envuelta como (select is_admin()) tardó 7.",{"q":812,"a":813},"¿Cómo aumento el statement timeout en Supabase?","Para un trabajo lento concreto, ponlo en la función que lo ejecuta con alter function … set statement_timeout. Para un rol entero, ejecuta alter role con el nuevo statement_timeout y después notify pgrst, 'reload config' para que la Data API lo recoja. Supabase limita a 60 segundos las consultas de sus librerías cliente. Arregla antes la política, porque el límite de anon lo comparten todos los visitantes sin sesión.",{"q":815,"a":816},"¿Debería desactivar RLS para que la consulta vaya más rápido?","No. La consulta va más rápido porque no se ejecuta ninguna política, y la tabla queda abierta para cualquier rol con permiso sobre ella. La guía de Supabase dice que, en proyectos existentes, una tabla nueva en public empieza con select, insert, update y delete concedidos a anon, el rol de un visitante sin sesión. Envuelve las llamadas a funciones y añade un índice.","\u002Fblog\u002Fcanceling-statement-due-to-statement-timeout\u002Fcard-800x500.png",[819,820,821,822,823,824,825],"canceling statement due to statement timeout supabase","canceling statement due to statement timeout","supabase statement timeout","57014 supabase","supabase timeout consulta anon","supabase rls lento","supabase aumentar statement timeout",{},true,"Supabase \"canceling statement due to statement timeout\"","\u002Fblog\u002Fcanceling-statement-due-to-statement-timeout","2026-10-31",{"title":5,"description":799},"blog\u002Fcanceling-statement-due-to-statement-timeout",[834,835,836],"\"canceling statement due to statement timeout\" en Supabase significa que Postgres detuvo una consulta porque duró más de lo que permite el rol que la envió: 3 segundos para un visitante sin sesión, 8 para uno con sesión. No se conserva nada de lo que la consulta estaba haciendo.","La misma consulta funciona en el SQL Editor porque el editor trabaja con el rol postgres, que no tiene límite propio y se salta por completo tus políticas de Row Level Security. Tu app aplica cada política a cada fila que lee.","Arregla la política antes de tocar el límite: envuelve las llamadas a funciones en (select …), indexa la columna que compara la política y nombra el rol. Un límite más alto para visitantes da a cualquier desconocido más tiempo con tu base de datos.","ELfNe_r68tlQqbevP3uB35n3u3RcUQkcfpGiSIl5OZA",[839,845,851,857,863,869,875,881,887,893,894,901,907,913,919,925,931,937,943,949,955,961,967,973,979,985,991,997,1003,1009,1015,1021,1027,1032,1038,1044,1050,1056,1062,1068,1073,1079,1085,1091,1097,1103,1108,1114,1120,1126,1132,1138,1144,1150,1156,1162,1168,1174,1180,1186,1192,1198,1203,1209,1215,1221,1227,1233,1239,1245,1251,1257,1263,1269,1275,1281,1287,1293,1299,1305,1311,1317,1323,1329,1334,1340,1346,1352,1357,1363],{"path":840,"title":841,"description":842,"published":843,"category":796,"image":844,"draft":800},"\u002Fblog\u002Fmax-client-connections-reached","Supabase \"max client connections reached\", explicado","Supabase \"max client connections reached\": el pooler no tiene sitio para un cliente más. El puerto de tu cadena de conexión decide el límite.","2026-11-09","\u002Fblog\u002Fmax-client-connections-reached\u002Fcard-800x500.png",{"path":846,"title":847,"description":848,"published":849,"category":796,"image":850,"draft":800},"\u002Fblog\u002Funsupported-provider-provider-is-not-enabled","Error de Supabase: \"Unsupported provider: provider is not enabled\"","\"Unsupported provider: provider is not enabled\": Google está desactivado en tu proyecto Supabase. Cómo activarlo y arreglar redirect_uri_mismatch.","2026-11-08","\u002Fblog\u002Funsupported-provider-provider-is-not-enabled\u002Fcard-800x500.png",{"path":852,"title":853,"description":854,"published":855,"category":796,"image":856,"draft":800},"\u002Fblog\u002Fviolates-foreign-key-constraint","Error de Supabase \"violates foreign key constraint\" al insertar","Supabase \"insert or update on table violates foreign key constraint\": tu fila apunta a una fila que no existe. Cuál, por qué, y cómo arreglarlo.","2026-11-07","\u002Fblog\u002Fviolates-foreign-key-constraint\u002Fcard-800x500.png",{"path":858,"title":859,"description":860,"published":861,"category":796,"image":862,"draft":800},"\u002Fblog\u002Fnull-value-violates-not-null-constraint","Error de Supabase \"null value in column violates not-null constraint\"","Supabase \"null value in column violates not-null constraint\": una columna obligatoria llegó vacía. De dónde vino el null, y cómo arreglarlo.","2026-11-06","\u002Fblog\u002Fnull-value-violates-not-null-constraint\u002Fcard-800x500.png",{"path":864,"title":865,"description":866,"published":867,"category":796,"image":868,"draft":800},"\u002Fblog\u002Fauth-session-missing","Supabase \"Auth session missing!\": adónde fue la sesión","\"Auth session missing!\" es supabase-js sin encontrar ninguna sesión iniciada y sin llegar a preguntar a Supabase. Adónde fue la sesión, y el arreglo.","2026-11-05","\u002Fblog\u002Fauth-session-missing\u002Fcard-800x500.png",{"path":870,"title":871,"description":872,"published":873,"category":796,"image":874,"draft":800},"\u002Fblog\u002Finvalid-claim-missing-sub-claim","Supabase \"invalid claim: missing sub claim\": no hay nadie conectado","\"invalid claim: missing sub claim\": Supabase Auth recibió un token auténtico sin ningún usuario, la clave del proyecto en lugar de una sesión.","2026-11-04","\u002Fblog\u002Finvalid-claim-missing-sub-claim\u002Fcard-800x500.png",{"path":876,"title":877,"description":878,"published":879,"category":796,"image":880,"draft":800},"\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function","\"Failed to send a request to the Edge Function\": la causa real","\"Failed to send a request to the Edge Function\": el navegador no recibió una respuesta legible. Su consola dice por qué y si tu función se ejecutó.","2026-11-03","\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function\u002Fcard-800x500.png",{"path":882,"title":883,"description":884,"published":885,"category":796,"image":886,"draft":800},"\u002Fblog\u002Fsupabase-auth-redirects-to-localhost","Por qué los enlaces de email de Supabase redirigen a localhost:3000","Si Supabase redirige a localhost:3000, la Site URL nunca se cambió o tu dirección falta en Redirect URLs. La solución, y el comodín que debes evitar.","2026-11-02","\u002Fblog\u002Fsupabase-auth-redirects-to-localhost\u002Fcard-800x500.png",{"path":888,"title":889,"description":890,"published":891,"category":796,"image":892,"draft":800},"\u002Fblog\u002Femail-link-is-invalid-or-has-expired","Supabase \"Email link is invalid or has expired\" con un enlace nuevo","Con un enlace enviado hace un minuto, \"Email link is invalid or has expired\" en Supabase suele indicar que un filtro de correo lo usó antes. Cómo arreglarlo.","2026-11-01","\u002Fblog\u002Femail-link-is-invalid-or-has-expired\u002Fcard-800x500.png",{"path":829,"title":5,"description":799,"published":830,"category":796,"image":817,"draft":800},{"path":895,"title":896,"description":897,"published":898,"category":899,"image":900,"draft":800},"\u002Fblog\u002Fdatabase-error-deleting-user","\"Database error deleting user\" en Supabase, y qué se va con él","\"Database error deleting user\" en Supabase: tu base de datos rechazó el borrado. La causa está en tus logs y el arreglo decide qué más se borra.","2026-10-30","Backups","\u002Fblog\u002Fdatabase-error-deleting-user\u002Fcard-800x500.png",{"path":902,"title":903,"description":904,"published":905,"category":796,"image":906,"draft":800},"\u002Fblog\u002Fstripe-webhook-no-signatures-found","\"No signatures found matching the expected signature\" en Supabase","¿Tu webhook de Stripe en una Edge Function de Supabase dice \"No signatures found matching the expected signature\"? Tiene dos causas. Sigue verificando.","2026-10-29","\u002Fblog\u002Fstripe-webhook-no-signatures-found\u002Fcard-800x500.png",{"path":908,"title":909,"description":910,"published":911,"category":796,"image":912,"draft":800},"\u002Fblog\u002Fsupabase-invalid-login","\"Invalid login credentials\" en Supabase: las tres causas","Supabase dice \"Invalid login credentials\" ante una contraseña errónea, un email desconocido y una cuenta de Google. Cómo saber cuál es.","2026-10-28","\u002Fblog\u002Fsupabase-invalid-login\u002Fcard-800x500.png",{"path":914,"title":915,"description":916,"published":917,"category":796,"image":918,"draft":800},"\u002Fblog\u002Fedge-function-returned-a-non-2xx-status-code","\"Edge Function returned a non-2xx status code\": el error real","\"Edge Function returned a non-2xx status code\" significa que tu función de Supabase respondió con un error. Aquí ves dónde leer el real.","2026-10-27","\u002Fblog\u002Fedge-function-returned-a-non-2xx-status-code\u002Fcard-800x500.png",{"path":920,"title":921,"description":922,"published":923,"category":796,"image":924,"draft":800},"\u002Fblog\u002Fcertificate-not-trusted","Certificado no confiable: tres causas aparte de la caducidad","¿Certificado no confiable aunque la fecha está bien? Nombra otra dirección, se firmó a sí mismo o a su cadena le falta un eslabón. Cómo saber cuál.","2026-10-26","\u002Fblog\u002Fcertificate-not-trusted\u002Fcard-800x500.png",{"path":926,"title":927,"description":928,"published":929,"category":796,"image":930,"draft":800},"\u002Fblog\u002Finvalid-input-syntax-for-type-uuid","Supabase \"invalid input syntax for type uuid\": lee las comillas","Supabase \"invalid input syntax for type uuid\" significa que tu app envió algo que no es un id. Lee las comillas, frena la consulta, conserva la columna.","2026-10-25","\u002Fblog\u002Finvalid-input-syntax-for-type-uuid\u002Fcard-800x500.png",{"path":932,"title":933,"description":934,"published":935,"category":796,"image":936,"draft":800},"\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint","Supabase \"duplicate key value violates unique constraint\", explicado","Supabase \"duplicate key value violates unique constraint\": el valor ya está ocupado. Encuentra la columna, elige un arreglo y aparta el valor de la pantalla.","2026-10-24","\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint\u002Fcard-800x500.png",{"path":938,"title":939,"description":940,"published":941,"category":796,"image":942,"draft":800},"\u002Fblog\u002Fsupabase-relation-does-not-exist","Supabase \"relation does not exist\" aunque la tabla está ahí","Supabase \"relation does not exist\" o PGRST205: ninguna tabla tiene ese nombre en ese esquema. Cuatro comprobaciones, y lo que abre exponer un esquema.","2026-10-23","\u002Fblog\u002Fsupabase-relation-does-not-exist\u002Fcard-800x500.png",{"path":944,"title":945,"description":946,"published":947,"category":796,"image":948,"draft":800},"\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\" muestra los nombres de argumentos que envió tu app. Compáralos con la función.","2026-10-22","\u002Fblog\u002Fsupabase-function-not-found\u002Fcard-800x500.png",{"path":950,"title":951,"description":952,"published":953,"category":796,"image":954,"draft":800},"\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\" suele ser un nombre que no coincide. Cuatro comprobaciones y la línea que recarga.","2026-10-21","\u002Fblog\u002Fsupabase-schema-cache-column-error\u002Fcard-800x500.png",{"path":956,"title":957,"description":958,"published":959,"category":796,"image":960,"draft":800},"\u002Fblog\u002Fsupabase-email-rate-limit-exceeded","Supabase \"Email Rate Limit Exceeded\": dos límites, un solo código","Supabase \"Email Rate Limit Exceeded\" limita cada email que envía tu proyecto: 2 por hora con el remitente integrado. Qué cuenta y cómo subirlo.","2026-10-20","\u002Fblog\u002Fsupabase-email-rate-limit-exceeded\u002Fcard-800x500.png",{"path":962,"title":963,"description":964,"published":965,"category":796,"image":966,"draft":800},"\u002Fblog\u002Fsupabase-users-logged-out","Supabase \"Invalid Refresh Token: Refresh Token Not Found\": 3 causas","Supabase \"Invalid Refresh Token: Refresh Token Not Found\" cierra la sesión de tus usuarios. Tres causas, y cómo las separa el log de Auth.","2026-10-19","\u002Fblog\u002Fsupabase-users-logged-out\u002Fcard-800x500.png",{"path":968,"title":969,"description":970,"published":971,"category":796,"image":972,"draft":800},"\u002Fblog\u002Fpermission-denied-for-schema-public","Supabase \"permission denied for schema public\": tres causas","Supabase \"permission denied for schema public\" es un grant que falta en el esquema. Qué lo quitó y la línea que lo devuelve.","2026-10-18","\u002Fblog\u002Fpermission-denied-for-schema-public\u002Fcard-800x500.png",{"path":974,"title":975,"description":976,"published":977,"category":796,"image":978,"draft":800},"\u002Fblog\u002Fsupabase-bucket-not-found","Supabase \"Bucket not found\": las tres cosas que significa","Supabase \"Bucket not found\" es una de tres cosas: un enlace público a un bucket privado, un nombre que no coincide exactamente o el proyecto equivocado.","2026-10-17","\u002Fblog\u002Fsupabase-bucket-not-found\u002Fcard-800x500.png",{"path":980,"title":981,"description":982,"published":983,"category":796,"image":984,"draft":800},"\u002Fblog\u002Fsupabase-invalid-jwt","Supabase invalid JWT: ¿qué clave dejó de funcionar?","Un error Supabase invalid JWT es PGRST301: un token firmado no pasó la verificación. En un proyecto nuevo se trata de la sesión, no de la clave de API.","2026-10-16","\u002Fblog\u002Fsupabase-invalid-jwt\u002Fcard-800x500.png",{"path":986,"title":987,"description":988,"published":989,"category":796,"image":990,"draft":800},"\u002Fblog\u002Fsupabase-single-row-error","Supabase \"multiple (or no) rows returned\" casi siempre es ninguna","Supabase devuelve PGRST116, \"JSON object requested, multiple (or no) rows returned\", cuando tu consulta no encontró nada. Una fila oculta parece ausente.","2026-10-15","\u002Fblog\u002Fsupabase-single-row-error\u002Fcard-800x500.png",{"path":992,"title":993,"description":994,"published":995,"category":796,"image":996,"draft":800},"\u002Fblog\u002Fdatabase-error-saving-new-user","\"Database error saving new user\" en Supabase: dónde mirar","\"Database error saving new user\" significa que algo unido a tu registro de Supabase falló y se deshizo entero. El error real está en tus logs.","2026-10-14","\u002Fblog\u002Fdatabase-error-saving-new-user\u002Fcard-800x500.png",{"path":998,"title":999,"description":1000,"published":1001,"category":796,"image":1002,"draft":800},"\u002Fblog\u002Fwhat-is-supabase","¿Qué es Supabase? La base de datos que eligió tu builder","Qué es Supabase: la base de datos, el login y el almacenamiento de archivos de tu app. Supabase lleva los servidores; las reglas sobre tus filas son tuyas.","2026-10-13","\u002Fblog\u002Fwhat-is-supabase\u002Fcard-800x500.png",{"path":1004,"title":1005,"description":1006,"published":1007,"category":796,"image":1008,"draft":800},"\u002Fblog\u002Faws-access-key-exposed-in-frontend","Una clave AWS expuesta en tu frontend no es como una clave OpenAI","Una clave de acceso de AWS expuesta en tu frontend lee y escribe tu almacenamiento. La solución suele ser una presigned URL, no una clave nueva.","2026-10-12","\u002Fblog\u002Faws-access-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":1010,"title":1011,"description":1012,"published":1013,"category":796,"image":1014,"draft":800},"\u002Fblog\u002Fstorage-upload-violates-row-level-security","\"Row-level security policy for table objects\" al subir","\"New row violates row-level security policy for table objects\" significa que tu subida no tiene regla insert. Hacer público el bucket no añade ninguna.","2026-10-11","\u002Fblog\u002Fstorage-upload-violates-row-level-security\u002Fcard-800x500.png",{"path":1016,"title":1017,"description":1018,"published":1019,"category":796,"image":1020,"draft":800},"\u002Fblog\u002Fbase44-security-scan","Escaneo de seguridad de Base44: lo que solo él ve","El escaneo de seguridad de Base44 revisa siete tipos de problema desde dentro. Esta es la mitad que lee y nadie más puede leer, y la mitad que nunca mira.","2026-10-10","\u002Fblog\u002Fbase44-security-scan\u002Fcard-800x500.png",{"path":1022,"title":1023,"description":1024,"published":1025,"category":796,"image":1026,"draft":800},"\u002Fblog\u002Fapi-key-leaked-what-to-do","Tu clave API se filtró. El orden en el que hay que actuar","Una clave API se filtró y no sabes por dónde empezar. No todas las claves de tu frontend lo son, y el orden importa más que la velocidad.","2026-10-09","\u002Fblog\u002Fapi-key-leaked-what-to-do\u002Fcard-800x500.png",{"path":462,"title":1028,"description":1029,"published":1030,"category":796,"image":1031,"draft":800},"\"Infinite recursion detected in policy\" sin desactivar RLS","\"Infinite recursion detected in policy for relation\" significa que tu policy preguntó a la tabla que protege. Así se rompe el círculo.","2026-10-08","\u002Fblog\u002Finfinite-recursion-in-policy-for-relation\u002Fcard-800x500.png",{"path":1033,"title":1034,"description":1035,"published":1036,"category":796,"image":1037,"draft":800},"\u002Fblog\u002Fno-api-key-found-in-request","\"No API key found in request\" en Supabase, y el arreglo equivocado","\"No API key found in request\" significa que tu petición llegó a Supabase sin clave. Casi todas las respuestas apuntan a las reglas de tu base de datos.","2026-10-07","\u002Fblog\u002Fno-api-key-found-in-request\u002Fcard-800x500.png",{"path":1039,"title":1040,"description":1041,"published":1042,"category":796,"image":1043,"draft":800},"\u002Fblog\u002Fis-base44-safe","¿Es seguro Base44? Lo que vimos en 5.442 apps Base44","¿Es seguro Base44? Pasamos nueve comprobaciones a 5.442 apps Base44 en producción. Casi todas sacaron B por motivos que su dueño nunca eligió.","2026-10-06","\u002Fblog\u002Fis-base44-safe\u002Fcard-800x500.png",{"path":1045,"title":1046,"description":1047,"published":1048,"category":796,"image":1049,"draft":800},"\u002Fblog\u002Fmove-a-secret-to-an-edge-function","Ocultar una clave API: muévela a una Edge Function de Supabase","Ocultar una clave API significa sacarla del navegador, y una Edge Function de Supabase es el sitio más pequeño donde ponerla. Dos pasos importan más.","2026-10-05","\u002Fblog\u002Fmove-a-secret-to-an-edge-function\u002Fcard-800x500.png",{"path":1051,"title":1052,"description":1053,"published":1054,"category":796,"image":1055,"draft":800},"\u002Fblog\u002Fenv-file-exposed-on-your-server","¿Tu archivo .env está expuesto? Las doce rutas a revisar","¿Tu archivo .env está expuesto en tu propio servidor web? Doce direcciones te lo dicen, y un acierto significa que todo lo que contiene ya es público.","2026-10-04","\u002Fblog\u002Fenv-file-exposed-on-your-server\u002Fcard-800x500.png",{"path":1057,"title":1058,"description":1059,"published":1060,"category":796,"image":1061,"draft":800},"\u002Fblog\u002Fis-v0-safe","Seguridad de v0: las 1.790 aplicaciones escaneadas sacaron una A","Seguridad de v0, medida en 1.790 aplicaciones v0 en vivo: todas sacaron A. Solo 17 nombraban una base de datos, y eso es casi todo lo que mide la A.","2026-10-03","\u002Fblog\u002Fis-v0-safe\u002Fcard-800x500.png",{"path":1063,"title":1064,"description":1065,"published":1066,"category":796,"image":1067,"draft":800},"\u002Fblog\u002Fis-bolt-safe","¿Es seguro Bolt? Lo que vimos en 1.123 aplicaciones Bolt","¿Es seguro Bolt? Pasamos 1.123 aplicaciones Bolt en vivo por nueve comprobaciones. El hosting salió limpio. Los hallazgos eran claves y tablas abiertas dentro.","2026-10-02","\u002Fblog\u002Fis-bolt-safe\u002Fcard-800x500.png",{"path":232,"title":1069,"description":1070,"published":1071,"category":796,"image":1072,"draft":800},"¿Supabase está caído o es tu app? Cómo distinguirlo","¿Supabase está caído o es tu app? Un proyecto pausado, una base de datos llena y un fallo tuyo parecen lo mismo. Cuatro comprobaciones los separan.","2026-10-01","\u002Fblog\u002Fis-supabase-down-or-your-app\u002Fcard-800x500.png",{"path":1074,"title":1075,"description":1076,"published":1077,"category":796,"image":1078,"draft":800},"\u002Fblog\u002Fdoes-supabase-encrypt-my-data","¿Supabase cifra mis datos? Sí. Esto es lo que detiene","¿Supabase cifra los datos? Sí: AES-256 en reposo, TLS en tránsito, SOC 2 e ISO 27001. Qué cubre cada uno, y la fuga que ninguno de ellos detiene.","2026-09-30","\u002Fblog\u002Fdoes-supabase-encrypt-my-data\u002Fcard-800x500.png",{"path":1080,"title":1081,"description":1082,"published":1083,"category":796,"image":1084,"draft":800},"\u002Fblog\u002Fsupabase-free-plan-limits","¿Cuánto cuesta Supabase? Los límites del plan gratuito","Los límites del plan gratuito de Supabase: 500 MB de base de datos, 5 GB de egress y ningún backup. Qué pasa en cada uno y qué cambia Pro por $25.","2026-09-29","\u002Fblog\u002Fsupabase-free-plan-limits\u002Fcard-800x500.png",{"path":1086,"title":1087,"description":1088,"published":1089,"category":899,"image":1090,"draft":800},"\u002Fblog\u002Ftest-your-supabase-backup","Prueba tu backup de Supabase antes del día que lo necesites","Cómo probar tu backup de Supabase: restaurarlo en un proyecto de prueba, contar filas, iniciar sesión y buscar la línea que le falta a un archivo cortado.","2026-09-28","\u002Fblog\u002Ftest-your-supabase-backup\u002Fcard-800x500.png",{"path":1092,"title":1093,"description":1094,"published":1095,"category":899,"image":1096,"draft":800},"\u002Fblog\u002Fsupabase-backup-github-action","Backup gratis de Supabase con una GitHub Action, y la trampa","Un backup de Supabase con GitHub Action es gratis y sirve a muchas apps. El workflow, el connection string correcto y el egress de cada ejecución.","2026-09-27","\u002Fblog\u002Fsupabase-backup-github-action\u002Fcard-800x500.png",{"path":1098,"title":1099,"description":1100,"published":1101,"category":899,"image":1102,"draft":800},"\u002Fblog\u002Fdownload-your-supabase-backup","Por qué no puedes descargar tu backup de Supabase","No puedes descargar tu backup de Supabase en un proyecto actual: la copia diaria es una instantánea física. Cómo comprobarlo y tener tu propia copia.","2026-09-26","\u002Fblog\u002Fdownload-your-supabase-backup\u002Fcard-800x500.png",{"path":1104,"title":1105,"description":1106,"published":1101,"category":796,"image":1107,"draft":800},"\u002Fblog\u002Fsupabase-permission-denied-for-table","Supabase \"permission denied for table\": el grant que falta","Desde el 30 de octubre, una tabla nueva de Supabase responde \"permission denied for table\" hasta que le das acceso. El grant del email es la mitad del arreglo.","\u002Fblog\u002Fsupabase-permission-denied-for-table\u002Fcard-800x500.png",{"path":1109,"title":1110,"description":1111,"published":1112,"category":796,"image":1113,"draft":800},"\u002Fblog\u002Fopen-api-endpoint-exposed","¿Un endpoint de API abierto es un problema? Mira el JSON","Tu escaneo señaló un endpoint de API abierto. Que importe depende de lo que devolvió, y la mayoría de los que encontramos eran de la plataforma.","2026-09-25","\u002Fblog\u002Fopen-api-endpoint-exposed\u002Fcard-800x500.png",{"path":1115,"title":1116,"description":1117,"published":1118,"category":796,"image":1119,"draft":800},"\u002Fblog\u002Flovable-security-scan","Escaneo de seguridad de Lovable: lo que no puede probar","Escaneo de seguridad de Lovable: qué revisan el Quick scan y el Deep scan, cuándo corre cada uno, y lo único que ningún escaneo desde dentro puede probar.","2026-09-24","\u002Fblog\u002Flovable-security-scan\u002Fcard-800x500.png",{"path":1121,"title":1122,"description":1123,"published":1124,"category":899,"image":1125,"draft":800},"\u002Fblog\u002Fsupabase-backup-auth-users","Por qué tu dump de Supabase no tiene usuarios dentro","Ejecuta supabase db dump por sí solo y obtienes la forma de tu base de datos y ninguna de sus filas, sin el esquema auth donde viven tus usuarios.","2026-09-23","\u002Fblog\u002Fsupabase-backup-auth-users\u002Fcard-800x500.png",{"path":1127,"title":1128,"description":1129,"published":1130,"category":796,"image":1131,"draft":800},"\u002Fblog\u002Fdomain-and-certificate-expiry","Dominio caducado, web caída: qué pasa a partir de ahora","Tu dominio caducó y tu web está caída. Aquí tienes el reloj que acaba de arrancar, por qué el certificado es el menor problema, y cómo comprobar ambos.","2026-09-22","\u002Fblog\u002Fdomain-and-certificate-expiry\u002Fcard-800x500.png",{"path":1133,"title":1134,"description":1135,"published":1136,"category":796,"image":1137,"draft":800},"\u002Fblog\u002Fis-lovable-safe","¿Es seguro Lovable? Lo que vimos en 18.554 aplicaciones Lovable","¿Es seguro Lovable? Pasamos 18.554 aplicaciones Lovable por nueve comprobaciones. La plataforma fue la más limpia de cinco. Los hallazgos estaban dentro.","2026-09-21","\u002Fblog\u002Fis-lovable-safe\u002Fcard-800x500.png",{"path":1139,"title":1140,"description":1141,"published":1142,"category":796,"image":1143,"draft":800},"\u002Fblog\u002Fvibe-coded-app-security-checklist","La lista de seguridad del vibe coding, en nueve comprobaciones","Una lista de seguridad del vibe coding con nueve puntos, cada uno verificable en tu app desde fuera por cualquiera, y cada uno con una prueba de una línea.","2026-09-20","\u002Fblog\u002Fvibe-coded-app-security-checklist\u002Fcard-800x500.png",{"path":1145,"title":1146,"description":1147,"published":1148,"category":796,"image":1149,"draft":800},"\u002Fblog\u002Fstripe-secret-key-in-frontend","Una clave secreta de Stripe en tu frontend puede mover dinero","Una clave secreta de Stripe expuesta en tu frontend puede reembolsar, cobrar y leer cada ficha de cliente que tienes. Tu clave pk_live_ sí debe estar ahí.","2026-09-19","\u002Fblog\u002Fstripe-secret-key-in-frontend\u002Fcard-800x500.png",{"path":1151,"title":1152,"description":1153,"published":1154,"category":796,"image":1155,"draft":800},"\u002Fblog\u002Fvite-and-next-public-env-vars","Variables de entorno de Vite expuestas: el prefijo dice publica esto","Variables de entorno de Vite expuestas en tu app: hicieron lo que pedía el prefijo. VITE_ y NEXT_PUBLIC_ significan publica esto, y la IA nunca supo el coste.","2026-09-18","\u002Fblog\u002Fvite-and-next-public-env-vars\u002Fcard-800x500.png",{"path":1157,"title":1158,"description":1159,"published":1160,"category":796,"image":1161,"draft":800},"\u002Fblog\u002Fbase44-source-maps","Seguridad en Base44: qué marca un escaneo y qué te toca arreglar a ti","Seguridad en Base44 en 5.438 apps escaneadas: tres hallazgos en casi todas son de la plataforma, el source map es la insignia de Base44 y lo tuyo es poco.","2026-09-17","\u002Fblog\u002Fbase44-source-maps\u002Fcard-800x500.png",{"path":1163,"title":1164,"description":1165,"published":1166,"category":796,"image":1167,"draft":800},"\u002Fblog\u002Fis-cursor-ai-safe","¿Es seguro Cursor AI? El editor, el código y la app que publicaste","¿Es seguro Cursor AI? Tres preguntas en una búsqueda: qué guarda Cursor, en qué falla el código que escribe y si la app que publicaste está abierta.","2026-09-16","\u002Fblog\u002Fis-cursor-ai-safe\u002Fcard-800x500.png",{"path":1169,"title":1170,"description":1171,"published":1172,"category":796,"image":1173,"draft":800},"\u002Fblog\u002Fis-replit-safe","¿Es seguro Replit? Lo que vimos en 3.042 aplicaciones Replit en vivo","¿Es seguro Replit? Pasamos 3.042 aplicaciones Replit en vivo por nueve comprobaciones externas. Los hallazgos estaban en la aplicación publicada, no en el host.","2026-09-15","\u002Fblog\u002Fis-replit-safe\u002Fcard-800x500.png",{"path":1175,"title":1176,"description":1177,"published":1178,"category":899,"image":1179,"draft":800},"\u002Fblog\u002Fsupabase-storage-backup","Supabase storage backup: tu copia de la base no tiene archivos","Un Supabase storage backup es una tarea aparte. Las copias de la base guardan la lista de tus archivos y ninguno de ellos, así que cada upload queda roto.","2026-09-14","\u002Fblog\u002Fsupabase-storage-backup\u002Fcard-800x500.png",{"path":1181,"title":1182,"description":1183,"published":1184,"category":899,"image":1185,"draft":800},"\u002Fblog\u002Fsupabase-point-in-time-recovery","Point-in-time recovery de Supabase: precio y límites","El point-in-time recovery de Supabase rebobina tu base de datos a cualquier segundo de la última semana. Cuesta $100 al mes sobre Pro y solo cubre la base.","2026-09-13","\u002Fblog\u002Fsupabase-point-in-time-recovery\u002Fcard-800x500.png",{"path":1187,"title":1188,"description":1189,"published":1190,"category":899,"image":1191,"draft":800},"\u002Fblog\u002Fsupabase-project-paused-recover","¿Proyecto de Supabase pausado? Tus datos siguen ahí","Supabase pausó tu proyecto tras una semana sin actividad. No se ha borrado nada, Restaurar está junto al nombre y tienes un año antes de que eso cambie.","2026-09-12","\u002Fblog\u002Fsupabase-project-paused-recover\u002Fcard-800x500.png",{"path":1193,"title":1194,"description":1195,"published":1196,"category":796,"image":1197,"draft":800},"\u002Fblog\u002Fsafest-ai-app-builder","¿Qué builder de apps con IA es más seguro? 30.998 apps escaneadas","¿Qué builder de apps con IA es más seguro? Escaneamos 30.998 apps activas de Lovable, Base44, Replit, v0 y Bolt. El builder no es lo que decide tu nota.","2026-09-11","\u002Fblog\u002Fsafest-ai-app-builder\u002Fcard-800x500.png",{"path":457,"title":1199,"description":1200,"published":1201,"category":796,"image":1202,"draft":800},"Activa Row Level Security en cada tabla de Supabase y compruébalo","Activar Row Level Security en Supabase sin política cierra la tabla del todo. Una política sin el interruptor no hace nada. Aquí tienes el SQL y la prueba.","2026-09-10","\u002Fblog\u002Fenable-rls-on-every-supabase-table\u002Fcard-800x500.png",{"path":1204,"title":1205,"description":1206,"published":1207,"category":796,"image":1208,"draft":800},"\u002Fblog\u002Fsupabase-rls-disabled-in-public","Supabase \"RLS disabled in public\": lo que el aviso no ve","Supabase marca \"RLS disabled in public\" como error. No dice nada de la política de lectura que deja tu tabla igual de abierta a cualquiera.","2026-09-09","\u002Fblog\u002Fsupabase-rls-disabled-in-public\u002Fcard-800x500.png",{"path":1210,"title":1211,"description":1212,"published":1213,"category":796,"image":1214,"draft":800},"\u002Fblog\u002Frotate-supabase-service-role-key","Cómo rotar una clave service_role de Supabase filtrada","Supabase dice que primero cierres la fuga. Otras guías dicen que rotes ya. Cuál es lo correcto depende de dónde se filtró tu clave service_role.","2026-09-08","\u002Fblog\u002Frotate-supabase-service-role-key\u002Fcard-800x500.png",{"path":1216,"title":1217,"description":1218,"published":1219,"category":796,"image":1220,"draft":800},"\u002Fblog\u002Fvibe-coding-security-scanners-compared","Escáneres de seguridad vibe coding comparados, el nuestro incluido","El mejor escáner de seguridad para vibe coding depende de tres preguntas que ninguna lista de funciones responde. Ocho herramientas, con precios.","2026-09-07","\u002Fblog\u002Fvibe-coding-security-scanners-compared\u002Fcard-800x500.png",{"path":1222,"title":1223,"description":1224,"published":1225,"category":796,"image":1226,"draft":800},"\u002Fblog\u002Fsupabase-security-checker","Supabase security checker: haz tú mismo las cinco comprobaciones","Un Supabase security checker lee tu app publicada en lugar de la configuración de tu proyecto. Estas son las cinco comprobaciones y cómo hacer cada una.","2026-09-06","\u002Fblog\u002Fsupabase-security-checker\u002Fcard-800x500.png",{"path":1228,"title":1229,"description":1230,"published":1231,"category":796,"image":1232,"draft":800},"\u002Fblog\u002Fvibe-coding-security-scanner","Escáner de seguridad para vibe coding: qué se le escapa","Un escáner de seguridad para vibe coding lee tu app en vivo desde fuera. Qué cubre eso, las cuatro cosas que no puede ver y cómo leer el resultado.","2026-09-05","\u002Fblog\u002Fvibe-coding-security-scanner\u002Fcard-800x500.png",{"path":1234,"title":1235,"description":1236,"published":1237,"category":796,"image":1238,"draft":800},"\u002Fblog\u002Fmissing-security-headers","Faltan cabeceras de seguridad: cuándo importa de verdad","Que falten cabeceras de seguridad es el hallazgo que más imprime nuestro escáner. Contra qué protegen y cuándo son la línea menos urgente de tu informe.","2026-09-04","\u002Fblog\u002Fmissing-security-headers\u002Fcard-800x500.png",{"path":1240,"title":1241,"description":1242,"published":1243,"category":796,"image":1244,"draft":800},"\u002Fblog\u002Fopenai-api-key-exposed-in-frontend","Tu clave de API de OpenAI está expuesta en tu frontend. Rótala.","Una clave de API de OpenAI expuesta en tu frontend no se puede atar a un dominio. Rótala hoy, mueve la llamada a tu servidor y limita el gasto.","2026-09-03","\u002Fblog\u002Fopenai-api-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":1246,"title":1247,"description":1248,"published":1249,"category":796,"image":1250,"draft":800},"\u002Fblog\u002Fwhat-secrets-leak-from-vibe-coded-apps","Clave de API expuesta en el frontend: qué llevan 30.998 apps","Una clave de API expuesta en tu frontend suele ser una clave de Google Maps. Escaneamos 30.998 apps vibe-coded y contamos qué secretos se filtran de verdad.","2026-09-02","\u002Fblog\u002Fwhat-secrets-leak-from-vibe-coded-apps\u002Fcard-800x500.png",{"path":1252,"title":1253,"description":1254,"published":1255,"category":899,"image":1256,"draft":800},"\u002Fblog\u002Fsupabase-backup-tools-compared","Herramientas de backup de Supabase comparadas, la nuestra incluida","Cuatro tipos de herramienta de backup de Supabase, qué copia cada una y cuándo una GitHub Action gratuita le gana a pagar a cualquiera, nosotros incluidos.","2026-09-01","\u002Fblog\u002Fsupabase-backup-tools-compared\u002Fcard-800x500.png",{"path":1258,"title":1259,"description":1260,"published":1261,"category":796,"image":1262,"draft":800},"\u002Fblog\u002Freplit-secrets-explained","Cómo usar secrets en Replit, y qué se publica de todos modos","Cómo usar secrets en Replit: añadir uno, leerlo y arreglar las dos causas del undefined. Y las claves que la herramienta Secrets no puede mantener privadas.","2026-08-31","\u002Fblog\u002Freplit-secrets-explained\u002Fcard-800x500.png",{"path":1264,"title":1265,"description":1266,"published":1267,"category":899,"image":1268,"draft":800},"\u002Fblog\u002Fback-up-supabase-free-tier","Backups del plan gratuito de Supabase: cómo hacer uno sin terminal","No hay backups en el plan gratuito de Supabase: la copia tienes que hacerla tú. Cómo sacarla desde el panel y qué deja fuera la vía CSV.","2026-08-30","\u002Fblog\u002Fback-up-supabase-free-tier\u002Fcard-800x500.png",{"path":1270,"title":1271,"description":1272,"published":1273,"category":796,"image":1274,"draft":800},"\u002Fblog\u002Fis-supabase-secure","¿Supabase es seguro? Sí. Tu proyecto es otra pregunta","¿Supabase es seguro? La plataforma está auditada, cifrada y sometida a pentests. Su propia documentación dice dónde termina eso y empiezan tus ajustes.","2026-08-29","\u002Fblog\u002Fis-supabase-secure\u002Fcard-800x500.png",{"path":1276,"title":1277,"description":1278,"published":1279,"category":796,"image":1280,"draft":800},"\u002Fblog\u002Fwhere-to-find-supabase-api-keys","Dónde están tus claves API de Supabase: anon, service_role y URL","La URL del proyecto, la clave anon y la clave service_role están en una sola página del panel. Aquí tienes dónde está, y cuál de los cuatro va en tu app.","2026-08-28","\u002Fblog\u002Fwhere-to-find-supabase-api-keys\u002Fcard-800x500.png",{"path":1282,"title":1283,"description":1284,"published":1285,"category":796,"image":1286,"draft":800},"\u002Fblog\u002Fnew-row-violates-row-level-security-policy","New row violates row-level security policy en Supabase. ¿Y ahora?","\"New row violates row-level security policy\" significa que Supabase rechazó una escritura. El arreglo rápido vuelve a abrir la tabla para cualquiera.","2026-08-27","\u002Fblog\u002Fnew-row-violates-row-level-security-policy\u002Fcard-800x500.png",{"path":1288,"title":1289,"description":1290,"published":1291,"category":796,"image":1292,"draft":800},"\u002Fblog\u002Fcors-wildcard-security-risk","¿Es un comodín CORS un riesgo de seguridad? Casi nunca.","¿Es un comodín CORS un riesgo de seguridad? Casi siempre es el ajuste por defecto de tu builder y no entrega nada que tu servidor no diera ya a cualquiera.","2026-08-26","\u002Fblog\u002Fcors-wildcard-security-risk\u002Fcard-800x500.png",{"path":1294,"title":1295,"description":1296,"published":1297,"category":899,"image":1298,"draft":800},"\u002Fblog\u002Fsupabase-branching-is-not-a-backup","Supabase branching no es un backup. Solo va hacia adelante.","Por qué el branching de Supabase no es un backup: una rama arranca sin tus datos y un merge solo mueve el esquema. Para qué sirve y qué usar en su lugar.","2026-08-25","\u002Fblog\u002Fsupabase-branching-is-not-a-backup\u002Fcard-800x500.png",{"path":1300,"title":1301,"description":1302,"published":1303,"category":899,"image":1304,"draft":800},"\u002Fblog\u002Fhow-to-restore-a-supabase-backup","Cómo restaurar un backup de Supabase, y qué se rompe después","Cómo restaurar un backup de Supabase desde el panel o desde un archivo de volcado, qué reemplaza la restauración y por qué tu app puede seguir rota al terminar.","2026-08-24","\u002Fblog\u002Fhow-to-restore-a-supabase-backup\u002Fcard-800x500.png",{"path":1306,"title":1307,"description":1308,"published":1309,"category":796,"image":1310,"draft":800},"\u002Fblog\u002Fsupabase-storage-bucket-public","Tu bucket de Supabase Storage es público. ¿Es un problema?","Un bucket público de Supabase Storage significa que quien tenga la URL de un archivo puede abrirlo. No significa que alguien pueda listar lo que hay dentro.","2026-08-23","\u002Fblog\u002Fsupabase-storage-bucket-public\u002Fcard-800x500.png",{"path":1312,"title":1313,"description":1314,"published":1315,"category":796,"image":1316,"draft":800},"\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend","¿Es un problema una clave de API de Google en tu frontend?","Una clave de API de Google en tu frontend es la clave que más encuentra nuestro escáner, y casi siempre no pasa nada. Un ajuste gratuito decide cuál es cuál.","2026-08-22","\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":1318,"title":1319,"description":1320,"published":1321,"category":899,"image":1322,"draft":800},"\u002Fblog\u002Fai-agent-deleted-my-database","Un agente de IA borró mis datos de Supabase. ¿Qué puedo recuperar?","Un agente de IA borró datos de tu base. Lo que puedes recuperar ya estaba decidido antes de que se ejecutara, y los próximos minutos deciden el resto.","2026-08-21","\u002Fblog\u002Fai-agent-deleted-my-database\u002Fcard-800x500.png",{"path":1324,"title":1325,"description":1326,"published":1327,"category":796,"image":1328,"draft":800},"\u002Fblog\u002Fcan-anyone-read-your-supabase-database","¿Puede cualquiera leer tu base de datos Supabase? 3.680 apps","¿Puede cualquiera leer tu base de datos Supabase sin iniciar sesión? Escaneamos 30.998 apps activas hechas con builders de IA y medimos cuántas dicen que sí.","2026-08-18","\u002Fblog\u002Fcan-anyone-read-your-supabase-database\u002Fcard-800x500.png",{"path":1330,"title":1331,"description":1332,"published":1327,"category":796,"image":1333,"draft":800},"\u002Fblog\u002Fsource-maps-exposed-in-production","Source maps expuestos: tu app está publicando su código original","Un source map expuesto deja que cualquiera lea el código original de tu app, comentarios incluidos. La comprobación de 30 segundos y lo que de verdad importa.","\u002Fblog\u002Fsource-maps-exposed-in-production\u002Fcard-800x500.png",{"path":1335,"title":1336,"description":1337,"published":1338,"category":796,"image":1339,"draft":800},"\u002Fblog\u002Fsupabase-new-api-keys","Supabase publishable key: ¿es la misma que la anon key?","Supabase sustituyó anon y service_role por claves publicables y secretas. ¿Cuál pertenece a tu app y cuál no debe estar nunca?","2026-08-12","\u002Fblog\u002Fsupabase-new-api-keys\u002Fcard-800x500.png",{"path":1341,"title":1342,"description":1343,"published":1344,"category":899,"image":1345,"draft":800},"\u002Fblog\u002Fdoes-supabase-back-up-my-database","¿Supabase respalda mi base de datos? Depende de tu plan.","¿Supabase respalda tu base de datos? A diario en los planes de pago, y nada en el gratuito. Cómo saber cuál tienes y qué es lo que esa copia no cubre.","2026-08-11","\u002Fblog\u002Fdoes-supabase-back-up-my-database\u002Fcard-800x500.png",{"path":1347,"title":1348,"description":1349,"published":1350,"category":796,"image":1351,"draft":800},"\u002Fblog\u002Fsupabase-rls-on-but-table-still-public","Supabase Row Level Security está activado. Tu tabla sigue pública.","Activar Supabase Row Level Security no protege una tabla. Lo hacen tus políticas, y la que arregló tu app rota puede dejar entrar a cualquiera.","2026-08-10","\u002Fblog\u002Fsupabase-rls-on-but-table-still-public\u002Fcard-800x500.png",{"path":1353,"title":1354,"description":1355,"published":1350,"category":899,"image":1356,"draft":800},"\u002Fblog\u002Fversion-history-is-not-a-backup","El historial de versiones no es un backup. No recupera una tabla.","Lovable y Bolt guardan un historial de versiones de tu código. Tu base de datos es un servicio aparte, y volver atrás no te devuelve tus datos.","\u002Fblog\u002Fversion-history-is-not-a-backup\u002Fcard-800x500.png",{"path":1358,"title":1359,"description":1360,"published":1361,"category":899,"image":1362,"draft":800},"\u002Fblog\u002Fthree-ways-to-back-up-a-supabase-database","Tres formas de respaldar una base Supabase, y qué falta en cada una","El panel, pg_dump y un servicio gestionado. Qué guarda de verdad cada uno, qué deja fuera sin avisar y cuál sobrevive a perder la cuenta.","2026-08-09","\u002Fblog\u002Fthree-ways-to-back-up-a-supabase-database\u002Fcard-800x500.png",{"path":648,"title":1364,"description":1365,"published":1361,"category":796,"image":1366,"draft":800},"Qué claves de API son seguras en el navegador y cuáles no","Tu clave anon de Supabase está pensada para ser pública. Tu clave service_role no, y se salta todas las reglas que pongas. Cómo distinguirlas.","\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend\u002Fcard-800x500.png",1794213644927]