<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tori TIC</title>
    <description>The latest articles on DEV Community by Tori TIC (@toritic).</description>
    <link>https://dev.to/toritic</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4026651%2F9ed75e82-c112-4675-a912-f4bb2827d250.png</url>
      <title>DEV Community: Tori TIC</title>
      <link>https://dev.to/toritic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/toritic"/>
    <language>en</language>
    <item>
      <title>How We Replaced 50/mo Social Growth Subscriptions with a Local Desktop Tool</title>
      <dc:creator>Tori TIC</dc:creator>
      <pubDate>Sun, 26 Jul 2026 22:29:08 +0000</pubDate>
      <link>https://dev.to/toritic/how-we-replaced-50mo-social-growth-subscriptions-with-a-local-desktop-tool-1836</link>
      <guid>https://dev.to/toritic/how-we-replaced-50mo-social-growth-subscriptions-with-a-local-desktop-tool-1836</guid>
      <description>&lt;p&gt;If you are a solo developer or indie hacker, you know the monthly subscription stack adds up fast.&lt;/p&gt;

&lt;p&gt;Every tool wants $30 to $60 every single month just to run basic repetitive loops like audience targeting, account auditing, and following/unfollowing non-responders.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Cloud SaaS for Personal Workflows
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Monthly Subscription Tax&lt;/strong&gt;: Paying $400 to $600 per year for simple automation loops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security &amp;amp; Credential Risk&lt;/strong&gt;: Giving third-party servers your session cookies or password.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overkill Complexity&lt;/strong&gt;: Massive web dashboards when all you need is a reliable local script or app.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Local Desktop Alternative
&lt;/h2&gt;

&lt;p&gt;We built &lt;strong&gt;XDB&lt;/strong&gt; (for X/Twitter) and &lt;strong&gt;IDB&lt;/strong&gt; (for Instagram) as standalone, local-first Windows desktop tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero Cloud Dependence&lt;/strong&gt;: Everything runs on your own machine. Your credentials never touch a external server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safety Governor&lt;/strong&gt;: Built-in human pacing delays prevent account blocks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-Time Purchase&lt;/strong&gt;: Pay once ($29), keep it forever.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can inspect the tools and local documentation here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://xdb.ticassociation.com" rel="noopener noreferrer"&gt;XDB - X Audience Toolkit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://idb.ticassociation.com" rel="noopener noreferrer"&gt;IDB - Instagram Drive-By&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Built by the team at TIC Association.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>webdev</category>
      <category>tools</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Next.js App Shows a White Screen After Vercel Deploy: 5 Real Causes</title>
      <dc:creator>Tori TIC</dc:creator>
      <pubDate>Mon, 20 Jul 2026 19:48:58 +0000</pubDate>
      <link>https://dev.to/toritic/nextjs-app-shows-a-white-screen-after-vercel-deploy-5-real-causes-4fh6</link>
      <guid>https://dev.to/toritic/nextjs-app-shows-a-white-screen-after-vercel-deploy-5-real-causes-4fh6</guid>
      <description>&lt;p&gt;A Next.js app that works perfectly on your machine and shows a blank white page the moment Vercel deploys it is one of the most common ways an AI-built app breaks in production. It is also one of the most misleading, because "white screen" is not a cause. It is what five or six different causes all look like from the outside.&lt;/p&gt;

&lt;p&gt;Here is how to actually narrow it down, in the order that catches the most cases fastest.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Open the browser console before anything else
&lt;/h2&gt;

&lt;p&gt;A white screen almost always means the client-side JavaScript threw an error before it could render anything, and that error is sitting in the browser console even when nothing shows up in the Vercel deploy log. This single step splits the problem in half: if there is an error here, you are debugging client-side code. If the console is silent, the failure is happening before the client ever runs, which points at the next two checks instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Check for a missing environment variable
&lt;/h2&gt;

&lt;p&gt;This is the single most common cause. Your &lt;code&gt;.env.local&lt;/code&gt; file exists on your machine and never gets deployed, by design. If your code reads &lt;code&gt;process.env.SOMETHING&lt;/code&gt; and that variable was never added in the Vercel project's Environment Variables settings, the value is &lt;code&gt;undefined&lt;/code&gt; in production, and depending on how it is used, that can fail completely silently, especially if it is passed into a client SDK's constructor with no error handling around it. Check the Vercel deploy's Function Logs, not just the build log. A build can succeed while the actual request still fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Check whether the build genuinely succeeded, not just that it finished
&lt;/h2&gt;

&lt;p&gt;Vercel will sometimes complete a build and still ship something broken if a data-fetching call at build time (&lt;code&gt;generateStaticParams&lt;/code&gt;, a server component's own fetch) silently failed and got swallowed instead of throwing. The page "builds," but the HTML that gets served is empty or wrong. Look at the actual deployed HTML source (view source on the live URL, not the rendered page) and check whether meaningful content is even there before the JavaScript runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Check the output configuration if you changed frameworks or moved directories
&lt;/h2&gt;

&lt;p&gt;If the project was migrated, restructured, or built with an AI tool that changed the framework preset partway through, Vercel can end up looking in the wrong output directory, or running a build command that no longer matches the actual project structure. This usually shows up as a successful-looking build with nothing meaningful in the output, which again reads as a plain white screen once deployed.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Node version mismatches
&lt;/h2&gt;

&lt;p&gt;Code that relies on a newer JavaScript or Node feature than the one Vercel's build environment is using can fail in ways that do not always throw a clean, readable error, particularly inside a dependency rather than your own code. Pin the Node version explicitly in your project settings rather than leaving it on whatever the platform default happens to be, and confirm it matches what you built and tested locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern underneath all five
&lt;/h2&gt;

&lt;p&gt;Every one of these fails in a way that looks identical from the outside (a blank page) while being caused by something that only shows up if you look in a specific, different place: the browser console, the environment variables panel, the raw HTML source, the output directory, or the build environment's Node version. Checking the symptom again does not help. Checking each of those five places, in order, usually does.&lt;/p&gt;

&lt;p&gt;If you have already gone through this and the app still will not come back, we do free diagnosis on exactly this class of problem, then a fixed-price repair only if the fix is clear: &lt;a href="https://rescue.ticassociation.com" rel="noopener noreferrer"&gt;https://rescue.ticassociation.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>vercel</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to Check Supabase Row Level Security Holes (Free SQL Audit)</title>
      <dc:creator>Tori TIC</dc:creator>
      <pubDate>Mon, 20 Jul 2026 19:45:45 +0000</pubDate>
      <link>https://dev.to/toritic/how-to-check-supabase-row-level-security-holes-free-sql-audit-2b7k</link>
      <guid>https://dev.to/toritic/how-to-check-supabase-row-level-security-holes-free-sql-audit-2b7k</guid>
      <description>&lt;p&gt;Row level security in Supabase fails quietly. There is no crash, no red error banner, nothing in your logs that says "this table is wide open." The app keeps working in the demo. The first sign anything is wrong is usually someone else's data showing up where it shouldn't, and by then it already happened.&lt;/p&gt;

&lt;p&gt;Here is how to check for the five holes that cause almost all of it, using nothing but a read-only SQL query against your own database's catalog.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five holes, in order of how often they actually happen
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. RLS switched off entirely.&lt;/strong&gt; The single most common leak. It usually happens to a table created after row security was already turned on for the rest of the schema, and the new table never got the same treatment. Anyone with your anon key can read and write it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. RLS is on, but zero policies exist.&lt;/strong&gt; This is the most common cause of the &lt;code&gt;42501&lt;/code&gt; error ("new row violates row-level security policy"), and it goes both ways: depending on the default deny/allow behavior for the operation, the table either blocks everything (your own app breaks) or exposes everything (your data leaks), and it is easy to not notice which one happened.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. A write policy with no &lt;code&gt;WITH CHECK&lt;/code&gt;.&lt;/strong&gt; A policy can correctly gate who is allowed to write, using &lt;code&gt;USING&lt;/code&gt;, while leaving the actual row values unchecked. That lets an authenticated user insert or update a row that claims to belong to someone else, because nothing verified what they wrote, only that they were allowed to write.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. A policy that resolves to &lt;code&gt;true&lt;/code&gt; for everyone.&lt;/strong&gt; This one is sneaky because it looks like security. There is a policy. It has a name. It shows up in your dashboard as "protected." But the condition inside it evaluates to true for every role, so it protects nothing while giving every appearance of doing so.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. The &lt;code&gt;anon&lt;/code&gt; role holds a direct grant.&lt;/strong&gt; Someone ran a &lt;code&gt;GRANT&lt;/code&gt; statement straight against the anonymous role at some point, usually while debugging, and it bypasses your row-level policies entirely regardless of how carefully those policies are written.&lt;/p&gt;

&lt;h2&gt;
  
  
  The query
&lt;/h2&gt;

&lt;p&gt;You can check for most of these directly from Supabase's own SQL editor, because the answers live in Postgres's own catalog tables (&lt;code&gt;pg_policies&lt;/code&gt;, &lt;code&gt;pg_class&lt;/code&gt;, &lt;code&gt;information_schema&lt;/code&gt;), not in anything Supabase adds. A useful starting query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt;
  &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="k"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relrowsecurity&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;rls_enabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;polname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;policy_count&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pg_class&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;pg_namespace&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relnamespace&lt;/span&gt;
&lt;span class="k"&gt;left&lt;/span&gt; &lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;pg_policies&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tablename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;schemaname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relkind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;
&lt;span class="k"&gt;group&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relrowsecurity&lt;/span&gt;
&lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;rls_enabled&lt;/span&gt; &lt;span class="k"&gt;asc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy_count&lt;/span&gt; &lt;span class="k"&gt;asc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any row with &lt;code&gt;rls_enabled = false&lt;/code&gt; is hole #1. Any row with &lt;code&gt;rls_enabled = true&lt;/code&gt; and &lt;code&gt;policy_count = 0&lt;/code&gt; is hole #2. Holes #3 through #5 need a look at the actual policy definitions (&lt;code&gt;pg_policies.qual&lt;/code&gt; and &lt;code&gt;.with_check&lt;/code&gt;) and at &lt;code&gt;information_schema.role_table_grants&lt;/code&gt; for direct grants to &lt;code&gt;anon&lt;/code&gt;, which is more than fits cleanly in one query.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is worth checking even if nothing looks broken
&lt;/h2&gt;

&lt;p&gt;The whole point of these five is that none of them produce an error under normal use. Your own testing, as the table's owner or as an authenticated user hitting your own rows, will not surface a policy that is technically present but too permissive, or a grant that quietly overrides your policies. The only way to know is to check the catalog directly, which is exactly what this kind of query is for.&lt;/p&gt;

&lt;p&gt;We built a free, one-file version of this check that runs the fuller version of the above (read-only, checks all five holes, plain-language output with the fix for each finding) and never asks for your project URL or your keys, because it runs inside your own SQL editor. It is a SQL file, so you can read exactly what it does before you run it: &lt;a href="https://ticassociation.com/supabase-rls-audit" rel="noopener noreferrer"&gt;https://ticassociation.com/supabase-rls-audit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nothing here is a substitute for reading your own policies. It is a fast way to find out where to start looking.&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>postgres</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Get Your Brand Cited by ChatGPT and Perplexity (Measured, Not Guessed)</title>
      <dc:creator>Tori TIC</dc:creator>
      <pubDate>Sun, 19 Jul 2026 00:42:44 +0000</pubDate>
      <link>https://dev.to/toritic/how-to-get-your-brand-cited-by-chatgpt-and-perplexity-measured-not-guessed-2g74</link>
      <guid>https://dev.to/toritic/how-to-get-your-brand-cited-by-chatgpt-and-perplexity-measured-not-guessed-2g74</guid>
      <description>&lt;p&gt;To get your brand cited by ChatGPT and Perplexity, you need four things working together: answer-first content that matches the exact questions your buyers ask an answer engine, phrased the way they phrase them; a third-party footprint on the places these engines actually pull from (Reddit, Stack Overflow, dev.to, directories, listicles); entity and schema signals so the engine trusts who you are; and, the part almost everyone skips, measurement, because citations are decided per question and shift over time, so you cannot improve what you never test. That last point is the whole game, and it is why this guide leads with data instead of theory.&lt;/p&gt;

&lt;p&gt;This also answers the broader question, "answer engine optimization guide": the method below is the guide, and you can run the core of it yourself in about ten minutes without buying anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "SEO" is not the answer here
&lt;/h2&gt;

&lt;p&gt;The instinct is to treat this as SEO with a new coat of paint. It is not. Being ranked in Google's top ten and being cited by an answer engine are different outcomes with different winners. Across the queries people have studied, the overlap between the pages Google ranks on page one and the sources an answer engine actually cites tends to land somewhere around 8 to 12 percent. In other words, most of what ranks does not get cited, and plenty of what gets cited does not rank.&lt;/p&gt;

&lt;p&gt;The reason is mechanical. A search engine returns a list and lets you choose. An answer engine writes a single answer and cites the few sources it leaned on while writing it. Those are not the same job, so the pages that win them are not the same pages. You can be number one on Google for a term and be completely absent from the answer a buyer reads on Perplexity. Optimizing only for rank leaves that gap wide open.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing nobody tells you: it is per-question
&lt;/h2&gt;

&lt;p&gt;Here is the finding that changes how you should think about this, taken straight from a live audit we ran through Perplexity's citation data.&lt;/p&gt;

&lt;p&gt;A brand gets cited for the exact narrow question it has content for, and disappears the moment the question widens or the wording shifts.&lt;/p&gt;

&lt;p&gt;We saw this on our own site. For the precise query "supabase error 42501 row level security fix", our page shows up as a cited source. Good. But for the broader question a buyer actually asks first, "who fixes broken AI-built apps", we were cited zero times. Every slot went to other players (afterbuildlabs.com, kovil.ai) and to community threads on Reddit and Stack Overflow. Same brand, same night, two very different outcomes, decided entirely by which question got asked.&lt;/p&gt;

&lt;p&gt;Then it got sharper. Take that same query we were cited for, "supabase error 42501 row level security fix", and add one word: "supabase error 42501 row level security POLICY fix". One word. We went from cited to absent. The engine treated it as a different question and pulled a different set of sources.&lt;/p&gt;

&lt;p&gt;Sit with that for a second, because it reframes the whole problem. "Am I cited by AI?" is not one yes-or-no question with one answer. It is a different answer for every phrasing your buyers might use. A single lucky citation on one exact string tells you almost nothing about the twenty nearby questions where your buyers are quietly reading someone else's name. Which means the only honest way to know where you stand is to test the actual questions, in the actual words, one by one.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to actually measure it (free, 10 minutes)
&lt;/h2&gt;

&lt;p&gt;You do not need a tool or a budget to start. You need a list of real questions and fifteen minutes of honesty. Here is the whole method.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Write down 5 to 10 questions your buyers would type into ChatGPT or Perplexity where you would want to be the answer. Use their words, not your marketing words. Mix narrow ones ("how to fix [specific error]") with the broad head questions they ask first ("who does [the thing you sell]"). The broad ones matter most and are where most brands are absent.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open Perplexity, or ChatGPT, and ask each question exactly as written, in a fresh chat so previous questions do not bias the answer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For each question, record three things: were you named in the answer, who was named instead, and which sources got cited underneath. A plain table is enough:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question asked&lt;/th&gt;
&lt;th&gt;Were you cited?&lt;/th&gt;
&lt;th&gt;Who was cited instead&lt;/th&gt;
&lt;th&gt;Source types (blog, Reddit, directory, forum)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;who fixes broken AI-built apps&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;competitor sites, Reddit, Stack Overflow&lt;/td&gt;
&lt;td&gt;forums, competitor blogs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;how to fix supabase error 42501&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;you plus one forum&lt;/td&gt;
&lt;td&gt;your docs, forum&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Add one deliberate variation of your best question, changing or adding a single word, and ask that too. This is how you catch the phrasing sensitivity from the section above on your own queries. It is usually the most sobering row in the table.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Re-run the same list in two weeks. Citations shift as engines re-crawl and as content changes, so a single snapshot is a starting point, not a verdict. The pattern over time is the signal.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That table is genuinely useful on its own. It tells you exactly which questions you already win, which ones you are invisible for, and who is eating the answers you want. You can act on it whether or not anyone helps you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four fixes, in priority order
&lt;/h2&gt;

&lt;p&gt;Once you have the table, the work is not mysterious. Fix in this order, because the order is roughly the return on effort.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Write answer-first pages for the questions you lost.&lt;/strong&gt; Take the exact questions where you were absent and publish content that answers them in the first two sentences, in the buyer's phrasing, before any preamble. Answer engines quote the part that directly answers the question, so put that part first and make it self-contained.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Seed the off-site sources the engine already cited.&lt;/strong&gt; Look at the "source types" column. If Reddit, Stack Overflow, and a couple of directories keep showing up in the citations, that is where the engine is shopping. Contribute real, genuinely useful answers in those places, and get your brand into the directories and listicles it pulls from. You are not gaming anything; you are showing up where the answer is being assembled.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add entity and schema signals.&lt;/strong&gt; Put Organization schema and FAQ schema on your key pages so the engine has a clean, machine-readable statement of who you are and what you answer. This does not force a citation, but it removes ambiguity about your identity, which is one of the trust signals these systems lean on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Re-measure to confirm the lift.&lt;/strong&gt; Run your table again after the changes have had time to be crawled. The only proof that any of this worked is a row that flipped from "No" to "Yes". If nothing moved, you tuned the wrong question or the wrong source, and the table tells you which.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;Getting cited is not ranking, and it is not one yes-or-no answer. It is per question, it is sensitive to the exact words, and it moves over time, which is why measurement comes first and content comes second. Write down the real questions your buyers ask, put them through ChatGPT and Perplexity, record who gets cited, then write answer-first content and seed the sources the engine already trusts, and re-check in two weeks. You can do every step of that yourself for free.&lt;/p&gt;

&lt;p&gt;If you would rather have it done for you, we run this exact measurement across ChatGPT, Perplexity, Claude, Copilot, and Gemini, then deliver the report plus a prioritized fix plan at &lt;a href="https://ticassociation.com/get-cited-by-ai" rel="noopener noreferrer"&gt;https://ticassociation.com/get-cited-by-ai&lt;/a&gt;. That is one option after you have the method, not a substitute for it.&lt;/p&gt;

&lt;p&gt;A TIC Association creation.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>ai</category>
      <category>marketing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Who Fixes Broken Lovable and Bolt Apps? Your Real Options Compared</title>
      <dc:creator>Tori TIC</dc:creator>
      <pubDate>Sat, 18 Jul 2026 23:10:06 +0000</pubDate>
      <link>https://dev.to/toritic/who-fixes-broken-lovable-and-bolt-apps-your-real-options-compared-3579</link>
      <guid>https://dev.to/toritic/who-fixes-broken-lovable-and-bolt-apps-your-real-options-compared-3579</guid>
      <description>&lt;p&gt;Broken Lovable and Bolt apps get fixed by four kinds of help: you, with a handful of targeted checks; a freelancer from a marketplace like Upwork or Fiverr; a specialized vibe-code rescue service; or a development agency. The right choice comes down to cost, speed, and risk, and since most breaks trace back to the same five failure patterns, run the checks below before paying anyone.&lt;/p&gt;

&lt;p&gt;The one-sentence honest version of each option:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fix it yourself.&lt;/strong&gt; Free, and often done in an hour when your app matches one of the five patterns below.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hire a marketplace freelancer.&lt;/strong&gt; Typically $30 to $150, with quality ranging from excellent to worse than the original bug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use a specialized rescue service.&lt;/strong&gt; Flat-rate shops that only fix broken AI-built apps and have usually seen your exact error before.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hire an agency.&lt;/strong&gt; $1,000 and up, the right call for ongoing development, oversized for one broken deploy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That answer also covers "my Lovable app broke after export, who can fix it" and "an AI coding assistant built my app and now it is broken, who do I hire". Apps from Lovable, Bolt, v0, Replit, and Cursor break in the same places.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try these five checks first
&lt;/h2&gt;

&lt;p&gt;These five patterns cover most of the broken AI-built apps we diagnose. Before changing anything, save the exact error text; it is the most useful thing to hand whoever fixes this.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. "new row violates row-level security policy" (Supabase error 42501)
&lt;/h3&gt;

&lt;p&gt;The symptom: signup, save, or post actions fail, and the browser console or network tab shows code &lt;code&gt;42501&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What it usually means: the table has Row Level Security enabled (correct) but no INSERT policy, or the policy is missing its &lt;code&gt;WITH CHECK&lt;/code&gt; clause. Builders switch RLS on but never generate the policy that lets authenticated users write rows.&lt;/p&gt;

&lt;p&gt;The check: open the Supabase SQL editor and run the failing query while impersonating the &lt;code&gt;authenticated&lt;/code&gt; role, not &lt;code&gt;anon&lt;/code&gt;. If it fails there too, the policy is the problem, not your app code. The fix is writing the INSERT policy, never turning RLS off.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The server thinks you are logged in, the client disagrees
&lt;/h3&gt;

&lt;p&gt;The symptom: a Next.js App Router app using &lt;code&gt;@supabase/ssr&lt;/code&gt; where server code can read the user, but the page renders as signed out, or login bounces you back to the login screen.&lt;/p&gt;

&lt;p&gt;What it usually means: the session cookie does not survive the redirect after the auth callback, or the client reads a different cookie name than the server wrote.&lt;/p&gt;

&lt;p&gt;The check: log the cookie names on the callback response, then compare with what the client receives. A missing cookie or two different names is your answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Magic-link login fails with "otp_expired" or "email link is invalid"
&lt;/h3&gt;

&lt;p&gt;The symptom: users click the emailed login link and are told it expired, even when they click within seconds.&lt;/p&gt;

&lt;p&gt;What it usually means: an email security scanner opened the link before the human did and consumed the one-time token. Corporate and university inboxes do this constantly.&lt;/p&gt;

&lt;p&gt;The fix shape: switch to &lt;code&gt;verifyOtp&lt;/code&gt; with a typed code; a code the user types cannot be consumed by a scanner following links.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Deploys fine, white screen in production
&lt;/h3&gt;

&lt;p&gt;The symptom: the Vercel build passes, the deployment is green, and the live URL is a blank page. Locally everything works.&lt;/p&gt;

&lt;p&gt;What it usually means: a missing or empty environment variable in the Vercel project settings. It works locally because &lt;code&gt;.env.local&lt;/code&gt; exists there; that file never deploys. An empty variable is just as fatal as a missing one and harder to spot.&lt;/p&gt;

&lt;p&gt;The check: look for undefined configuration values in the browser console on the live URL, then compare the Vercel environment variable list against &lt;code&gt;.env.local&lt;/code&gt; line by line.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Worked in the builder preview, broke after export
&lt;/h3&gt;

&lt;p&gt;The symptom: everything worked on the builder preview URL; after export and deploy to your own domain, redirects land somewhere wrong or requests fail.&lt;/p&gt;

&lt;p&gt;What it usually means: hardcoded preview URLs or auth redirect URLs still point at the builder's domain instead of yours.&lt;/p&gt;

&lt;p&gt;The check: search the codebase for the builder's domain string, then confirm your auth site URL and redirect allow-list include the production domain.&lt;/p&gt;

&lt;p&gt;If one of these matched, you may not need to hire anyone. If none did, or you do not want to touch the fix yourself, here is what hiring looks like.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four options, honestly compared
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Typical cost&lt;/th&gt;
&lt;th&gt;Speed&lt;/th&gt;
&lt;th&gt;The real risk&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Yourself&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;An evening&lt;/td&gt;
&lt;td&gt;Silencing errors by disabling security&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Marketplace freelancer&lt;/td&gt;
&lt;td&gt;$30 to $150&lt;/td&gt;
&lt;td&gt;Hours to days, plus vetting time&lt;/td&gt;
&lt;td&gt;Quality variance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Specialized rescue service&lt;/td&gt;
&lt;td&gt;Flat rate&lt;/td&gt;
&lt;td&gt;Usually 1 to 2 days&lt;/td&gt;
&lt;td&gt;Wrong fit for full rebuilds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agency&lt;/td&gt;
&lt;td&gt;$1,000 and up&lt;/td&gt;
&lt;td&gt;Days to weeks&lt;/td&gt;
&lt;td&gt;Paying for structure you do not need&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Yourself.&lt;/strong&gt; Free and genuinely viable when the failure matches a known pattern. One warning: never make a security error disappear by turning security off. Disabling RLS or putting a service role key in browser code leaves your database open to anyone on the internet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Marketplace freelancers.&lt;/strong&gt; Often the cheapest competent option, and there are excellent people there. The problem is variance: the same $80 can buy a clean fix or a mess that costs more to undo. A useful filter: does the reply name your specific error and a plausible cause, or just quote hours to "investigate"?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Specialized rescue services.&lt;/strong&gt; A category that exists because these failures repeat; a specialist has usually fixed your exact error many times, which is what makes flat pricing possible. Two questions before you pay anyone here: is the diagnosis free, and do you pay before or after the fix is proven working?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agencies.&lt;/strong&gt; The right answer when the app needs sustained development or a partial rebuild, the wrong answer for a single broken deploy, where the minimum engagement dwarfs the work. A good agency will tell you this themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where we fit, stated plainly
&lt;/h2&gt;

&lt;p&gt;We run one of the specialized services: Vibe-Code Rescue, at rescue.ticassociation.com. The diagnosis is free. You paste the error, we explain what broke and what fixing it involves, and the diagnosis is yours to take anywhere, including to a freelancer. If you want us to do the fix, it is a flat $90 founding rate, and you pay only after the fix is verified working on your live app. That is one option among the four above, not the only sensible one.&lt;/p&gt;

&lt;p&gt;No matter who fixes your app, you can check the security of the result for free. Our Supabase RLS auditor at ticassociation.com/supabase-rls-audit is one read-only SQL file you run in the Supabase SQL editor. It writes nothing and changes nothing; it lists the tables with RLS disabled or policies missing, which is where AI-built apps tend to be quietly exposed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;Run the five checks first; they cover most broken Lovable and Bolt apps. If you need a human: a freelancer is the cheapest gamble, a rescue service trades a flat rate for predictability, and an agency only makes sense when one fix is not what you are buying. Whatever you choose, keep the exact error text, ask how the fix will be verified, and never accept "we turned the security rules off" as a solution.&lt;/p&gt;

&lt;p&gt;A TIC Association creation.&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>nextjs</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>We made our security auditor buyable by AI agents (x402, one serverless function)</title>
      <dc:creator>Tori TIC</dc:creator>
      <pubDate>Sat, 18 Jul 2026 00:07:34 +0000</pubDate>
      <link>https://dev.to/toritic/we-made-our-security-auditor-buyable-by-ai-agents-x402-one-serverless-function-44j</link>
      <guid>https://dev.to/toritic/we-made-our-security-auditor-buyable-by-ai-agents-x402-one-serverless-function-44j</guid>
      <description>&lt;p&gt;Last night we made our Supabase security auditor buyable by AI agents. One HTTP request, a USDC payment attached to a header, and the product comes back in the response body. No checkout page, no account, no human.&lt;/p&gt;

&lt;p&gt;Here is why we did it, how the whole thing is about 80 lines of code, and an honest accounting of what it will and will not do for us.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 30-second history of HTTP 402
&lt;/h2&gt;

&lt;p&gt;The HTTP spec reserved status code &lt;code&gt;402 Payment Required&lt;/code&gt; in 1997 and it sat unused for nearly three decades. In 2025 Coinbase published x402, an open protocol that finally gives it a job: a server answers a request with 402 plus machine-readable payment requirements, the client attaches a signed stablecoin payment to a header, retries, and gets the resource. Settlement happens on-chain (USDC on Base) in one round trip. Visa's Intelligent Commerce integrated it this spring. It is not a concept; it is running infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we shipped
&lt;/h2&gt;

&lt;p&gt;Our RLS Security Pack is a zip: a read-only SQL auditor that finds the five common row-level-security holes in AI-built Supabase apps, fix recipes for every finding class, and a Claude Code skill. Humans buy it on Gumroad. Now an agent can buy it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ask for the product&lt;/span&gt;
curl &lt;span class="nt"&gt;-i&lt;/span&gt; https://ticassociation.com/api/agent/rls-pack

&lt;span class="c"&gt;# the server answers 402 with the exact terms:&lt;/span&gt;
&lt;span class="c"&gt;# {"x402Version":1,"accepts":[{"scheme":"exact","network":"base",&lt;/span&gt;
&lt;span class="c"&gt;#   "asset":"...USDC...","payTo":"0x...","maxAmountRequired":"...", ...}]}&lt;/span&gt;

&lt;span class="c"&gt;# an x402-capable client attaches the signed payment and retries:&lt;/span&gt;
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-PAYMENT: &amp;lt;signed&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://ticassociation.com/api/agent/rls-pack &lt;span class="nt"&gt;-o&lt;/span&gt; pack.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server side is one serverless function: return 402 with the requirements when there is no payment header, verify and settle through the public facilitator when there is one, then stream the zip. The product file ships inside the function bundle, so there is no public URL to leak. The whole thing took an evening, and most of that was reading the spec.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a tiny company bothered
&lt;/h2&gt;

&lt;p&gt;Three honest reasons, in decreasing order of honesty:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Our brand line has always been "a collective of agents, human or AI." Selling to both makes the sentence literal, and we could not resist that.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Coding agents already do real work. An agent that finds broken row level security mid-task should be able to acquire the fix kit without stopping to page a human through a checkout form. The demand is small today. It was also small for HTTPS in 1995.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Answer engines reward being early and specific. When someone asks an AI "can software be sold to AI agents", we would like the answer to have a working example to point at.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The honest accounting
&lt;/h2&gt;

&lt;p&gt;Will agents autonomously buy our security pack this month? Almost certainly not in any volume. Agent-initiated commerce is real but young, and discovery is the hard part: your endpoint has to be findable by agents at all. We list ours in our llms.txt under a "For agents" section, which is the current best practice and still a bet on the future rather than a traffic source today.&lt;/p&gt;

&lt;p&gt;What it cost us: one evening, zero dollars of infrastructure (a serverless function and the public facilitator), and the risk of looking silly if the category stalls. What it buys us: a working claim nobody in our niche has, and a store that is ready if the buyers arrive before the skeptics expect.&lt;/p&gt;

&lt;p&gt;If you want to see it: the human-readable version is at &lt;a href="https://ticassociation.com/agent-store" rel="noopener noreferrer"&gt;ticassociation.com/agent-store&lt;/a&gt;, the free edition of the auditor (no payment, agent or human) is at &lt;a href="https://ticassociation.com/supabase-rls-audit" rel="noopener noreferrer"&gt;ticassociation.com/supabase-rls-audit&lt;/a&gt;, and if your AI-built app is broken in ways an auditor cannot fix, that is our day job: &lt;a href="https://rescue.ticassociation.com" rel="noopener noreferrer"&gt;rescue.ticassociation.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>payments</category>
      <category>webdev</category>
      <category>supabase</category>
    </item>
    <item>
      <title>Where to get a 3Blue1Brown style explainer video made, and how to judge one</title>
      <dc:creator>Tori TIC</dc:creator>
      <pubDate>Fri, 17 Jul 2026 23:42:14 +0000</pubDate>
      <link>https://dev.to/toritic/where-to-get-a-3blue1brown-style-explainer-video-made-and-how-to-judge-one-585b</link>
      <guid>https://dev.to/toritic/where-to-get-a-3blue1brown-style-explainer-video-made-and-how-to-judge-one-585b</guid>
      <description>&lt;p&gt;If you want an explainer video in the calm, diagram-driven 3Blue1Brown style, you have three real options: learn Manim yourself (free, steep curve), hire a motion studio (usually four figures), or use a small specialist service built on a Manim pipeline. This guide covers all three honestly, plus how to judge the result before you pay anyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the style actually is
&lt;/h2&gt;

&lt;p&gt;3Blue1Brown's videos work because the animation IS the explanation. There is no stock footage, no talking head, no kinetic-text filler. A diagram builds on screen exactly as the idea builds in your head, one element at a time, while a calm voice narrates. The viewer never reads one thing while hearing another.&lt;/p&gt;

&lt;p&gt;That style comes from Manim, the animation engine Grant Sanderson wrote and open-sourced. Every derivative of the look traces back to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: make it yourself with Manim
&lt;/h2&gt;

&lt;p&gt;Manim is free and the community edition is well documented. If you are comfortable with Python, the honest cost is time: expect a few days to become productive and a few hours per finished minute of animation once you are. The parts nobody warns you about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Timing animation to narration is the real work. The scenes are easy; the pacing is not.&lt;/li&gt;
&lt;li&gt;Audio mastering matters more than you think. Voice anchored first, music ducked underneath, or the whole thing feels amateur.&lt;/li&gt;
&lt;li&gt;Rendering is fast on any modern machine. This is not a GPU-hungry workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have the time and the Python, this is the best option. You will own the skill forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: hire a studio or freelancer
&lt;/h2&gt;

&lt;p&gt;Motion design studios quote explainer videos at roughly $1,000 to $10,000 per finished minute depending on market and polish. Freelancer marketplaces are cheaper but most listings there do whiteboard or kinetic-text styles, not the diagram-first Manim look. If you go this route, ask specifically what engine they use and ask for a sample in the target style before committing. A generic motion reel does not predict a good diagram-first explainer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 3: a specialist service
&lt;/h2&gt;

&lt;p&gt;Full disclosure: this is what we do, so weigh the bias accordingly. We run a fully local Manim pipeline (animation, narration, and music bed all licence-clean, produced on machines we own) and sell finished explainers you own outright, no watermark and no subscription. Because the pipeline is automated where it should be and hand-tuned where it matters, the price sits far below studio quotes.&lt;/p&gt;

&lt;p&gt;The honest pitch is the sample: we make a free 10 second sample of your actual idea so you can judge the style on your own material before paying anything. That offer, and a 36 second reel of what the engine produces, is at &lt;a href="https://ticassociation.com/explainer-videos" rel="noopener noreferrer"&gt;ticassociation.com/explainer-videos&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to judge any explainer, from anyone
&lt;/h2&gt;

&lt;p&gt;Use these five checks whether you build or buy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does the diagram build in sync with the narration, or is a finished graphic just sitting there being described?&lt;/li&gt;
&lt;li&gt;Is there any stock footage or filler b-roll? In this style, that is a failure.&lt;/li&gt;
&lt;li&gt;Is the pacing calm? Rapid cuts mean the maker did not trust the explanation.&lt;/li&gt;
&lt;li&gt;Voice first in the mix, music under it, silence used on purpose.&lt;/li&gt;
&lt;li&gt;Could a viewer redraw the core diagram from memory afterward? That is the whole point of the style.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The honest limits
&lt;/h2&gt;

&lt;p&gt;This style is wrong for some jobs. Product demos that need real UI footage, emotional brand films, and anything where a human face builds the trust all want different treatments. Diagram-first explainers shine when the product or idea is abstract: infrastructure, security, finance, algorithms, anything where the buyer says "I do not quite get how this works."&lt;/p&gt;

&lt;p&gt;If that is your situation, learn Manim if you have the hours, or grab the free sample if you do not.&lt;/p&gt;

</description>
      <category>animation</category>
      <category>video</category>
      <category>manim</category>
      <category>marketing</category>
    </item>
    <item>
      <title>Supabase Sign in with Apple keeps throwing invalid_client: the checklist that actually finds it</title>
      <dc:creator>Tori TIC</dc:creator>
      <pubDate>Fri, 17 Jul 2026 21:30:56 +0000</pubDate>
      <link>https://dev.to/toritic/supabase-sign-in-with-apple-keeps-throwing-invalidclient-the-checklist-that-actually-finds-it-5c7i</link>
      <guid>https://dev.to/toritic/supabase-sign-in-with-apple-keeps-throwing-invalidclient-the-checklist-that-actually-finds-it-5c7i</guid>
      <description>&lt;p&gt;You rotate a key for Sign in with Apple, and suddenly every login throws &lt;code&gt;invalid_client&lt;/code&gt;. It was supposed to be a five minute change. Three hours later you are still staring at the same error.&lt;/p&gt;

&lt;p&gt;This one bites hard because &lt;code&gt;invalid_client&lt;/code&gt; is Apple's answer to about six different mistakes, and the error text never tells you which one you made. Here is the checklist that finds it, in the order of most likely to least.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is actually being checked
&lt;/h2&gt;

&lt;p&gt;When Supabase (or any backend) talks to Apple, it presents a client secret that is not a static string: it is a JWT you generate, signed with your &lt;code&gt;.p8&lt;/code&gt; key. Apple validates five things about it, and a mismatch in any one of them returns &lt;code&gt;invalid_client&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the JWT is signed by the key whose Key ID is in the JWT header&lt;/li&gt;
&lt;li&gt;that key belongs to your Apple team&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;sub&lt;/code&gt; claim matches your Services ID (not your App ID)&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;iss&lt;/code&gt; claim is your Team ID&lt;/li&gt;
&lt;li&gt;the JWT is not expired&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The checklist
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Did you regenerate the client-secret JWT after rotating the .p8?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the classic trap. Rotating the key in the Apple Developer portal does nothing to the JWT you generated from the old key. That JWT still carries the old Key ID in its header, so Apple rejects it. After any key rotation you must generate a fresh client-secret JWT from the new &lt;code&gt;.p8&lt;/code&gt; and paste that into your provider config. The old secret does not "refresh".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Is the &lt;code&gt;sub&lt;/code&gt; claim your Services ID?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;sub&lt;/code&gt; in the client-secret JWT must be the &lt;strong&gt;Services ID&lt;/strong&gt; (the identifier you created for web auth, usually something like &lt;code&gt;com.yourapp.web&lt;/code&gt;), not the App ID and not the bundle id. If you copied the wrong identifier when generating the secret, Apple sees a client it does not know.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Does the Key ID in the JWT header match the uploaded key?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Decode your client secret at any JWT debugger (it is not sensitive to decode, it is public claims plus a signature). The header's &lt;code&gt;kid&lt;/code&gt; must be exactly the Key ID shown next to your Sign in with Apple key in the developer portal. A stale &lt;code&gt;kid&lt;/code&gt; means you signed with a key Apple no longer associates with that configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Is the JWT expired?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Apple caps client-secret JWTs at 6 months (&lt;code&gt;exp&lt;/code&gt; at most 15777000 seconds after &lt;code&gt;iat&lt;/code&gt;). If you generated the secret long ago and it worked until today, this is your answer: it aged out, which people then misdiagnose as a rotation problem while rotating everything except the one thing that expired.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Is the ES256 algorithm actually used?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The secret must be signed with ES256 (the &lt;code&gt;.p8&lt;/code&gt; is an EC key). A generator defaulting to HS256 or RS256 produces a structurally valid JWT that Apple rejects. The decoded header should read &lt;code&gt;"alg": "ES256"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Are the domain and return URL registered on the Services ID?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the identifiers all match but auth still fails at the redirect step, check that the Services ID has your auth domain (for Supabase: &lt;code&gt;&amp;lt;project-ref&amp;gt;.supabase.co&lt;/code&gt;) and the exact callback URL registered under its Sign in with Apple configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five minute reset, when you would rather stop debugging
&lt;/h2&gt;

&lt;p&gt;When the state is too tangled, a clean rebuild is faster than archaeology:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the Apple portal: create a fresh key with Sign in with Apple enabled, download the new &lt;code&gt;.p8&lt;/code&gt;, note the Key ID.&lt;/li&gt;
&lt;li&gt;Confirm the Services ID, Team ID, and domain/return URL config.&lt;/li&gt;
&lt;li&gt;Generate a brand new client-secret JWT (ES256, &lt;code&gt;iss&lt;/code&gt; = Team ID, &lt;code&gt;sub&lt;/code&gt; = Services ID, &lt;code&gt;kid&lt;/code&gt; = the new Key ID, &lt;code&gt;exp&lt;/code&gt; under 6 months).&lt;/li&gt;
&lt;li&gt;Paste it into your provider config (in Supabase: Authentication, then Providers, then Apple) and save.&lt;/li&gt;
&lt;li&gt;Test in a private window.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every value is freshly derived, so any stale-state mismatch is gone.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you would rather hand it over
&lt;/h2&gt;

&lt;p&gt;We fix broken AI-built and Supabase apps for a living, auth is one of the three things we repair most, and the diagnosis is free: &lt;a href="https://rescue.ticassociation.com" rel="noopener noreferrer"&gt;rescue.ticassociation.com&lt;/a&gt;. You pay only after the fix is verified working.&lt;/p&gt;

&lt;p&gt;And if your app is Supabase and you have never audited its Row Level Security, our free read-only auditor finds the common holes in a minute: &lt;a href="https://ticassociation.com/supabase-rls-audit" rel="noopener noreferrer"&gt;ticassociation.com/supabase-rls-audit&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>authentication</category>
      <category>apple</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The 5 RLS holes that quietly leak data in AI-built Supabase apps</title>
      <dc:creator>Tori TIC</dc:creator>
      <pubDate>Fri, 17 Jul 2026 20:09:16 +0000</pubDate>
      <link>https://dev.to/toritic/the-5-rls-holes-that-quietly-leak-data-in-ai-built-supabase-apps-242c</link>
      <guid>https://dev.to/toritic/the-5-rls-holes-that-quietly-leak-data-in-ai-built-supabase-apps-242c</guid>
      <description>&lt;p&gt;Your app works. Sign-ups land, dashboards load, and Stripe pays out. Then one day a stranger reads another user's data, and you find out the door was never locked.&lt;/p&gt;

&lt;p&gt;That is what broken Row Level Security looks like in a Supabase app. It is not a crash. There is no error in the console. The app behaves perfectly for you while quietly serving other people's rows to anyone who asks the API directly. If your app was built with Lovable, Bolt, Cursor, or Replit, this is the single most common security hole it shipped with.&lt;/p&gt;

&lt;p&gt;Here are the five ways it happens, and a free read-only auditor that finds all five in about a minute.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. RLS is switched off on a table
&lt;/h2&gt;

&lt;p&gt;The number one leak. Someone (often the AI assistant) created a table after row security was set up, and nobody remembered to lock it. With RLS off and the standard grants in place, that table is world-readable and writable through your public API.&lt;/p&gt;

&lt;p&gt;Check it yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;relname&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pg_class&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;pg_namespace&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relnamespace&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;
  &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relkind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;
  &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relrowsecurity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anything this returns is unprotected right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. RLS is on, but there are zero policies
&lt;/h2&gt;

&lt;p&gt;Row security without policies means Postgres denies everything for regular roles. This is the classic cause of the &lt;code&gt;42501 permission denied&lt;/code&gt; error that appears after a deploy. It fails closed, which is safer than open, but it also means the feature you shipped does not work, and the usual "fix" people paste from a forum is to switch RLS off. See hole number 1.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. A write policy with no WITH CHECK
&lt;/h2&gt;

&lt;p&gt;A policy's &lt;code&gt;USING&lt;/code&gt; clause controls which rows you can see. The &lt;code&gt;WITH CHECK&lt;/code&gt; clause controls what you are allowed to write. A policy that sets only &lt;code&gt;USING&lt;/code&gt; on insert or update lets an authenticated user write rows as somebody else, for example inserting messages with another user's id. Reads look secure, writes are wide open.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. A policy that is true for everyone
&lt;/h2&gt;

&lt;p&gt;A policy like &lt;code&gt;using (true)&lt;/code&gt; on a sensitive table passes every review that only checks "RLS is on and policies exist". It protects nothing. These usually appear as leftover debugging or as AI-generated scaffolding that was never tightened.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The anon role holds a direct write grant
&lt;/h2&gt;

&lt;p&gt;Even with sensible policies, a stray &lt;code&gt;grant insert on ... to anon&lt;/code&gt; gives the anonymous API role a door of its own. This one hides because nobody looks at grants after setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-minute audit
&lt;/h2&gt;

&lt;p&gt;I bundled all five checks into a single read-only SQL file. You paste it into your Supabase SQL editor, run it, and get a prioritized findings table: severity, table, what the hole is in plain language, and the copy-paste fix.&lt;/p&gt;

&lt;p&gt;It reads system catalogs only. It never touches your rows, changes nothing, and you can read every line before you run it. No install, no account access, no keys shared.&lt;/p&gt;

&lt;p&gt;Download it free here: &lt;a href="https://ticassociation.com/supabase-rls-audit" rel="noopener noreferrer"&gt;ticassociation.com/supabase-rls-audit&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limits
&lt;/h2&gt;

&lt;p&gt;The auditor catches the common, repeated failure patterns fast. It is not a penetration test, and an app holding sensitive data still deserves a real security review before scale. Treat it as your pre-launch preflight: five minutes that rule out the five most likely leaks.&lt;/p&gt;

&lt;p&gt;If it flags something you cannot fix, that is literally what we do all day: we repair broken AI-built apps, RLS most of all. Free diagnosis at &lt;a href="https://rescue.ticassociation.com" rel="noopener noreferrer"&gt;rescue.ticassociation.com&lt;/a&gt;, and you pay only after the fix is verified working.&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>security</category>
      <category>postgres</category>
      <category>webdev</category>
    </item>
    <item>
      <title>AI Builders Make Apps LOOK Finished. Here Is How To Tell If Yours Actually Works.</title>
      <dc:creator>Tori TIC</dc:creator>
      <pubDate>Thu, 16 Jul 2026 23:36:47 +0000</pubDate>
      <link>https://dev.to/toritic/ai-builders-make-apps-look-finished-here-is-how-to-tell-if-yours-actually-works-158g</link>
      <guid>https://dev.to/toritic/ai-builders-make-apps-look-finished-here-is-how-to-tell-if-yours-actually-works-158g</guid>
      <description>&lt;p&gt;You built something with an AI tool. It looked finished. You clicked around for a while, felt good about it, and sent the link to someone who actually needed it. Then they messaged back confused, because the thing they were looking at was not real.&lt;/p&gt;

&lt;p&gt;This happens constantly and it is not really a coding mistake. It is a looking mistake.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looks connected is not the same as is connected
&lt;/h2&gt;

&lt;p&gt;AI builders are very good at one specific trick: making an empty screen look full. Sample names, sample dates, a calendar that already has bookings on it, a dashboard with numbers already in the boxes. None of that proves anything is wired up. It proves the builder did not want you to stare at a blank page.&lt;/p&gt;

&lt;p&gt;You cannot tell the difference by looking at it. That is the whole problem. A screen full of realistic-looking data and a screen full of real data render exactly the same to your eyes. The only way to know which one you have is to test it, not admire it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three checks that take about a minute, no code required
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. The write test.&lt;/strong&gt; Add one real thing through your own app. A booking, a record, whatever the app is for. Then hard refresh the page. If what you added is gone, it never reached a database. It was living in the browser tab and nowhere else. Sample rows that were already there will survive a refresh too, which is why this check is not enough on its own, pair it with the next one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Unplug it on purpose.&lt;/strong&gt; Find the setting for your database connection or your API key and break it. Rename a table, revoke a key, whatever is easiest. Then load the page again. A real connection fails loudly and immediately, usually with an error or a blank spot. If the page still looks completely fine with the backend broken, nothing was ever actually talking to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Watch the network tab for ten seconds.&lt;/strong&gt; Open your browser's developer tools, click the Network tab, then load the page. Real data means a request goes out to fetch it, you will see it appear in the list. No request means those rows were baked into the page itself, not pulled from anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Empty is honest
&lt;/h2&gt;

&lt;p&gt;Here is the instinct worth building: a brand new app, one you have not entered anything into yet, should look bare. An empty calendar. Zero bookings. A dashboard with nothing on it. If a fresh app looks full before you have put anything into it, ask where that fullness is coming from. It did not come from you.&lt;/p&gt;

&lt;p&gt;This is also why the person who finds these bugs first is almost never the one who built the app. You have been staring at the demo, admiring what it can do. The first real user is not admiring anything, they are trying to actually use it, and they touch the one path that was never really connected within minutes. That is not bad luck. That is the difference between testing as the builder and testing as the customer, and only one of those two ever finds the real bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you already shipped something and are not sure
&lt;/h2&gt;

&lt;p&gt;Run the three checks above on it today, before anyone else does. It takes less time to run them than it took to read this.&lt;/p&gt;

&lt;p&gt;If you run them and something is wrong and you are not sure how to fix it, that is a normal place to end up, not a sign you did something wrong. We do free diagnoses for exactly this: send us the app, we look at what is actually happening under the surface, and tell you straight whether it is a five minute fix or something bigger. No pitch, no obligation. rescue.ticassociation.com&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>webdev</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Supabase login works locally but the session dies after you deploy to Vercel. Here is why.</title>
      <dc:creator>Tori TIC</dc:creator>
      <pubDate>Thu, 16 Jul 2026 09:57:07 +0000</pubDate>
      <link>https://dev.to/toritic/supabase-login-works-locally-but-the-session-dies-after-you-deploy-to-vercel-here-is-why-1p2d</link>
      <guid>https://dev.to/toritic/supabase-login-works-locally-but-the-session-dies-after-you-deploy-to-vercel-here-is-why-1p2d</guid>
      <description>&lt;p&gt;You build an app in Lovable, Bolt, v0, or Cursor. Login works perfectly on localhost. You deploy to Vercel, log in on the live site, and you are immediately bounced back to the login page. Or the page loads but every query returns nothing, as if you are a stranger to your own app.&lt;/p&gt;

&lt;p&gt;This is the single most common way an AI-built Supabase app breaks in production. It is almost never the login code itself. It is the session, and specifically where the session lives.&lt;/p&gt;

&lt;p&gt;Here is what is actually happening, and the order to check it in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The session lives in cookies, and cookies are the thing that breaks
&lt;/h2&gt;

&lt;p&gt;Supabase auth gives you a session and stores it in cookies. Every part of your app that talks to Supabase has to read those cookies correctly. On localhost, a lot of sloppiness is survivable: one origin, no HTTPS, no edge, no separate server context. In production, all of that becomes strict.&lt;/p&gt;

&lt;p&gt;So the question is never "is my login broken." The question is "which layer lost the cookie."&lt;/p&gt;

&lt;h2&gt;
  
  
  1. You are using the browser client on the server
&lt;/h2&gt;

&lt;p&gt;This is the number one cause in generated code.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@supabase/supabase-js&lt;/code&gt; createClient is a browser client. It keeps the session in browser storage. If you call it from a server component, a route handler, or middleware, there is no browser, so there is no session. Your query runs anonymous, RLS says no, and you get empty results or a redirect.&lt;/p&gt;

&lt;p&gt;In an App Router project you need two different clients from &lt;code&gt;@supabase/ssr&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a browser client for client components&lt;/li&gt;
&lt;li&gt;a server client, created per request, that reads and writes cookies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your project has a single &lt;code&gt;supabase.ts&lt;/code&gt; that everything imports, that is your bug. Generated code does this constantly because it works locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Middleware is not refreshing the session
&lt;/h2&gt;

&lt;p&gt;Supabase access tokens expire. &lt;code&gt;@supabase/ssr&lt;/code&gt; expects middleware to refresh the token and write the updated cookies back onto the response.&lt;/p&gt;

&lt;p&gt;If you have no middleware, or middleware that reads cookies but never sets them back, the session silently dies the moment the token expires. Classic symptom: login works, then a refresh five minutes later logs you out.&lt;/p&gt;

&lt;p&gt;Your middleware must both refresh the session and return the response carrying the updated cookies. If your middleware creates a response object and then returns a different one, you dropped the cookies.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. cookies() is not awaited
&lt;/h2&gt;

&lt;p&gt;On recent Next.js versions &lt;code&gt;cookies()&lt;/code&gt; is async. If your generated code calls it synchronously, you can get an app that builds fine and behaves strangely at runtime, because the cookie store is not what you think it is.&lt;/p&gt;

&lt;p&gt;Search your project for &lt;code&gt;cookies()&lt;/code&gt; and make sure every call is awaited, and that the server client is created inside the request scope rather than at module top level. A Supabase server client created at module scope is shared across requests, which is both a bug and a security problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Your production URL is not in Supabase's redirect allowlist
&lt;/h2&gt;

&lt;p&gt;Open your Supabase dashboard, Authentication, then URL Configuration. Site URL and Redirect URLs must include your real deployed domain, including the exact protocol and any preview domains you actually use.&lt;/p&gt;

&lt;p&gt;If the allowlist only has localhost, the auth callback on production is rejected. Supabase is doing the right thing. It just looks like your login is broken.&lt;/p&gt;

&lt;p&gt;Add the production domain. If you use Vercel preview deployments, be aware their URLs change per deploy, which is a common source of "it works on prod but not on preview."&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Cookie flags differ between localhost and HTTPS
&lt;/h2&gt;

&lt;p&gt;On localhost you are on http and one origin. In production you are on https, possibly with a custom domain. Cookies marked Secure will not be set over plain http, and SameSite rules bite the moment an OAuth provider redirects you back from another origin.&lt;/p&gt;

&lt;p&gt;If you hand-rolled cookie options in generated code, this is worth a look. If you let &lt;code&gt;@supabase/ssr&lt;/code&gt; manage them, it is usually correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check this in the right order
&lt;/h2&gt;

&lt;p&gt;Do not start changing code. Start by finding out who Supabase thinks you are.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On the deployed site, log in, then hit a server route that logs the result of &lt;code&gt;getUser()&lt;/code&gt;. If it returns null, your server has no session and the cause is in items 1 to 3.&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;getUser()&lt;/code&gt; returns your user but queries are still empty, your session is fine and your problem is RLS, not auth. Different bug, different article.&lt;/li&gt;
&lt;li&gt;Check the Supabase dashboard redirect allowlist against the exact domain in your address bar.&lt;/li&gt;
&lt;li&gt;Log in and wait past token expiry, then refresh. If that is when it breaks, your middleware is not refreshing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That sequence tells you which layer lost the cookie in about five minutes, without touching a line of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why AI builders produce this specific bug
&lt;/h2&gt;

&lt;p&gt;Generated code optimizes for the preview working. The preview is a single origin, no HTTPS boundary, no expiry, no separate server context. Every shortcut that fails in production is invisible there. The tool did not lie to you. It just never had to cross the boundary that production forces.&lt;/p&gt;

&lt;p&gt;This is why these apps break on deploy specifically, and why the fix is almost always at a boundary rather than in the feature logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to stop guessing
&lt;/h2&gt;

&lt;p&gt;If you have changed the same three files four times and the behavior keeps moving, stop. Auth, RLS, cookies, and deployment callbacks interact, and guessing changes more than one variable at a time. Preserve the exact error, find the failing boundary, then make the smallest change that crosses it.&lt;/p&gt;

&lt;p&gt;If you want a second set of eyes, we run a free diagnosis for broken AI-built apps: describe what is broken in plain words, or paste the error if you have one, and you get the likely cause and a fix path back. No card, no repo access to start. &lt;a href="https://rescue.ticassociation.com" rel="noopener noreferrer"&gt;https://rescue.ticassociation.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A TIC Association creation.&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>nextjs</category>
      <category>webdev</category>
      <category>vercel</category>
    </item>
    <item>
      <title>Your AI-built app works in the builder but breaks on deploy with a Supabase "permission denied" error. Here is why, and how to fix it.</title>
      <dc:creator>Tori TIC</dc:creator>
      <pubDate>Mon, 13 Jul 2026 05:08:10 +0000</pubDate>
      <link>https://dev.to/toritic/your-ai-built-app-works-in-the-builder-but-breaks-on-deploy-with-a-supabase-permission-denied-45ik</link>
      <guid>https://dev.to/toritic/your-ai-built-app-works-in-the-builder-but-breaks-on-deploy-with-a-supabase-permission-denied-45ik</guid>
      <description>&lt;p&gt;You built an app in Lovable, Bolt, v0, or Cursor. It worked perfectly in the preview. You deployed it, opened the live URL, and now half the screen is empty or you see something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;permission&lt;/span&gt; &lt;span class="n"&gt;denied&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;profiles&lt;/span&gt;
&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42501&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or your data loads as &lt;code&gt;null&lt;/code&gt; and the console shows &lt;code&gt;getUser()&lt;/code&gt; returned nothing. The frustrating part is that nothing changed in your code between the working preview and the broken deploy. So what happened?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this happens in almost every AI-built app
&lt;/h2&gt;

&lt;p&gt;This is one of the most common failures we see, and it is not random. AI app builders generate a working front end fast, but they often leave the security boundary between your app and your database half finished. Two things are usually true at once:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Row Level Security (RLS) is on. Supabase enables RLS so a table can only be read or written by the right user. That is correct and you want it. Your policy probably says something like "a row is visible when &lt;code&gt;auth.uid() = user_id&lt;/code&gt;".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The request reaching Supabase in production is anonymous. In the builder preview, the request often carried your session, or ran with elevated access, so RLS let it through. In the real deploy, the server is calling Supabase without the logged-in user's identity attached. So &lt;code&gt;auth.uid()&lt;/code&gt; is null, the policy does not match, and Supabase correctly refuses with &lt;code&gt;42501 permission denied&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In plain terms: your security rules are working, but your app is knocking on the door without showing its ID.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to confirm this is your problem
&lt;/h2&gt;

&lt;p&gt;Before changing anything, keep the exact error text. Then check, in order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is RLS enabled on the failing table? In Supabase, open the table and look at its policies. If RLS is on and there is no policy that matches your query, every read or write fails.&lt;/li&gt;
&lt;li&gt;Is the request authenticated on the server? Add a log on the server route that runs the query and print whether it has a user. If it prints null or anonymous, that is the cause.&lt;/li&gt;
&lt;li&gt;Do your environment variables point at the same Supabase project that failed? A deploy pointed at the wrong project, or missing &lt;code&gt;SUPABASE_URL&lt;/code&gt; and the anon key, produces the same symptom.&lt;/li&gt;
&lt;li&gt;Do your auth redirect URLs include the live domain? In Supabase Auth settings, the site URL and redirect allow-list must include your production domain, not just localhost.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;The real fix is to make the request carry the user's identity, so &lt;code&gt;auth.uid()&lt;/code&gt; is populated when RLS checks it. In a Next.js and Supabase app that usually means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use a request-scoped server client that reads the auth cookie, instead of a plain anonymous client, on any server route or action that touches a protected table.&lt;/li&gt;
&lt;li&gt;Refresh the session in middleware so the cookie stays valid across navigation and after a hard refresh on the production domain.&lt;/li&gt;
&lt;li&gt;Confirm the RLS policy matches your real access pattern. If users should see their own rows, the policy compares &lt;code&gt;auth.uid()&lt;/code&gt; to the row's user column. Do not "fix" this by turning RLS off.&lt;/li&gt;
&lt;li&gt;Add the production domain to Supabase Auth redirect URLs, then retest the full path: log in, load the page, and hard refresh, all on the live URL.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then rerun the exact user action that failed and confirm it works against the same Supabase project and domain that broke.&lt;/p&gt;

&lt;h2&gt;
  
  
  A word of caution
&lt;/h2&gt;

&lt;p&gt;The tempting shortcut is to disable RLS or to use the service role key in the browser so the error disappears. Please do not. Disabling RLS makes every row in that table readable and writable by anyone on the internet, and putting the service role key in client code hands a stranger full control of your database. The error is annoying, but it is protecting you.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you are stuck, or you are not the technical one
&lt;/h2&gt;

&lt;p&gt;If you are non-technical and this already reads like another language, that is normal. This is exactly the kind of thing an AI builder cannot finish for you, because it lives in the boundary between auth, the database, and the deploy.&lt;/p&gt;

&lt;p&gt;We run a service for this called Vibe-Code Rescue. You paste the error and get a free, plain-language diagnosis of what is wrong and what it takes to fix. If you want it fixed, it is a flat rate, you pay only after the fix is verified working, and you keep clean code you own. No subscription, no retainer.&lt;/p&gt;

&lt;p&gt;Free diagnosis here: &lt;a href="https://rescue.ticassociation.com" rel="noopener noreferrer"&gt;https://rescue.ticassociation.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A TIC Association creation.&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>nextjs</category>
      <category>webdev</category>
      <category>nocode</category>
    </item>
  </channel>
</rss>
