<?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: Sora2 Hub Team</title>
    <description>The latest articles on DEV Community by Sora2 Hub Team (@sora2hubteam).</description>
    <link>https://dev.to/sora2hubteam</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%2F4173417%2Fdf6026a4-7673-4bdb-9d0c-44a0a961bbdb.png</url>
      <title>DEV Community: Sora2 Hub Team</title>
      <link>https://dev.to/sora2hubteam</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sora2hubteam"/>
    <language>en</language>
    <item>
      <title>Supabase RLS Checklist for AI-Built Next.js Apps</title>
      <dc:creator>Sora2 Hub Team</dc:creator>
      <pubDate>Fri, 30 Oct 2026 13:01:02 +0000</pubDate>
      <link>https://dev.to/sora2hubteam/supabase-rls-checklist-for-ai-built-nextjs-apps-5gai</link>
      <guid>https://dev.to/sora2hubteam/supabase-rls-checklist-for-ai-built-nextjs-apps-5gai</guid>
      <description>&lt;p&gt;AI coding agents are good at scaffolding a Next.js app with Supabase Auth and a few tables. They are less reliable at locking those tables down. If Row Level Security (RLS) is missing or too loose, every signed-in user can read everyone else's rows.&lt;/p&gt;

&lt;p&gt;Use this checklist before you put a preview or production URL in front of customers.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Turn RLS on for every user-facing table
&lt;/h2&gt;

&lt;p&gt;In the Supabase dashboard (or a migration), confirm RLS is enabled on profiles, projects, documents, billing_customer, and any table that stores tenant data. A table with RLS off is public to the anon/authenticated roles that have GRANTs.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Prefer &lt;code&gt;auth.uid()&lt;/code&gt; policies over client-side filters
&lt;/h2&gt;

&lt;p&gt;Client &lt;code&gt;.eq('user_id', user.id)&lt;/code&gt; is not security. The API still returns other rows if a policy allows it. Policies should look like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SELECT / UPDATE / DELETE: &lt;code&gt;auth.uid() = user_id&lt;/code&gt; (or &lt;code&gt;auth.uid() = owner_id&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;INSERT: &lt;code&gt;WITH CHECK (auth.uid() = user_id)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Never expose the service role key in the browser
&lt;/h2&gt;

&lt;p&gt;The service role bypasses RLS. Keep it on the server only (Route Handlers, Server Actions, Edge Functions). If an AI agent put &lt;code&gt;SUPABASE_SERVICE_ROLE_KEY&lt;/code&gt; in a &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; env var, rotate the key and remove it from the client bundle.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Test with two real accounts
&lt;/h2&gt;

&lt;p&gt;Create user A and user B. As A, create a row. As B, try to fetch it by ID and by list query. Both should fail (empty list / 0 rows / 403). Repeat for update and delete.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Watch storage buckets
&lt;/h2&gt;

&lt;p&gt;If you store uploads, bucket policies need the same owner checks. A public bucket with guessable paths leaks files even when table RLS is correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Avoid SECURITY DEFINER surprises
&lt;/h2&gt;

&lt;p&gt;AI agents sometimes wrap queries in a Postgres function marked &lt;code&gt;SECURITY DEFINER&lt;/code&gt; so it can "just work". That function runs as the owner and can bypass RLS unless you re-check &lt;code&gt;auth.uid()&lt;/code&gt; inside it. Prefer &lt;code&gt;SECURITY INVOKER&lt;/code&gt; (Postgres 15+) or keep RLS-aware SQL in policies, not in privileged helpers.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Stripe webhooks and admin jobs
&lt;/h2&gt;

&lt;p&gt;Webhook handlers often need the service role. Scope those writes tightly (update only the customer/subscription row for that &lt;code&gt;stripe_customer_id&lt;/code&gt;) and keep the handler server-side.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Check GRANTs, not only policies
&lt;/h2&gt;

&lt;p&gt;RLS policies only apply to roles that can touch the table. If &lt;code&gt;anon&lt;/code&gt; or &lt;code&gt;authenticated&lt;/code&gt; still have broad &lt;code&gt;GRANT ALL&lt;/code&gt;, a missing policy fails open for that role. Prefer least privilege: grant SELECT/INSERT/UPDATE/DELETE only where needed, then layer RLS. After AI scaffolding, run &lt;code&gt;\dp public.*&lt;/code&gt; (or the dashboard Privileges view) and revoke anything the client roles should not have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-deploy mini plan
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;RLS enabled on all tenant tables&lt;/li&gt;
&lt;li&gt;Policies use &lt;code&gt;auth.uid()&lt;/code&gt; with WITH CHECK on inserts&lt;/li&gt;
&lt;li&gt;No service role in client env&lt;/li&gt;
&lt;li&gt;Two-user isolation test passed&lt;/li&gt;
&lt;li&gt;Storage policies reviewed&lt;/li&gt;
&lt;li&gt;No unsafe SECURITY DEFINER helpers bypassing RLS&lt;/li&gt;
&lt;li&gt;Webhook path uses service role only on the server&lt;/li&gt;
&lt;li&gt;Client-role GRANTs reviewed (least privilege)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ship when all eight are green.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Massvai helps
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://massvai.com" rel="noopener noreferrer"&gt;Massvai&lt;/a&gt; is an AI full-stack Next.js app builder. Use it to scaffold faster, then run this checklist yourself before you ship. Agents draft policies; humans verify them.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>supabase</category>
      <category>security</category>
    </item>
    <item>
      <title>Supabase Auth dans le middleware Next.js : 6 erreurs fréquentes (et comment les corriger)</title>
      <dc:creator>Sora2 Hub Team</dc:creator>
      <pubDate>Sat, 17 Oct 2026 16:11:05 +0000</pubDate>
      <link>https://dev.to/sora2hubteam/supabase-auth-dans-le-middleware-nextjs-6-erreurs-frequentes-et-comment-les-corriger-nj1</link>
      <guid>https://dev.to/sora2hubteam/supabase-auth-dans-le-middleware-nextjs-6-erreurs-frequentes-et-comment-les-corriger-nj1</guid>
      <description>&lt;p&gt;Le middleware de Next.js est l'endroit naturel pour rafraîchir la session Supabase et rediriger les visiteurs non connectés. C'est aussi là que naissent les bugs les plus agaçants : déconnexions aléatoires, boucles de redirection, pages protégées qui s'affichent quand même. Voici les six erreurs que l'on rencontre le plus souvent avec l'App Router.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Utiliser encore les anciens « auth helpers »
&lt;/h2&gt;

&lt;p&gt;Le paquet &lt;code&gt;@supabase/auth-helpers-nextjs&lt;/code&gt; est déprécié. La méthode actuelle passe par &lt;code&gt;@supabase/ssr&lt;/code&gt;, qui fournit &lt;code&gt;createServerClient&lt;/code&gt; (serveur, middleware) et &lt;code&gt;createBrowserClient&lt;/code&gt; (composants client). Mélanger les deux paquets dans un même projet donne des cookies incompatibles et des sessions qui disparaissent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @supabase/supabase-js @supabase/ssr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Ne pas renvoyer les cookies rafraîchis
&lt;/h2&gt;

&lt;p&gt;Quand le jeton d'accès expire, Supabase le renouvelle et écrit de nouveaux cookies. Si le middleware ne les recopie pas sur la réponse, le navigateur garde l'ancien jeton et l'utilisateur est déconnecté au bout d'une heure. Le client doit écrire à la fois sur la requête et sur la réponse :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// middleware.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createServerClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@supabase/ssr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createServerClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_SUPABASE_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_SUPABASE_ANON_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nf"&gt;setAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cookiesToSet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;cookiesToSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
          &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
          &lt;span class="nx"&gt;cookiesToSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Glisser du code entre &lt;code&gt;createServerClient&lt;/code&gt; et &lt;code&gt;getUser()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;La documentation de Supabase insiste : n'exécutez pas de logique entre la création du client et l'appel à &lt;code&gt;auth.getUser()&lt;/code&gt;. Un &lt;code&gt;await&lt;/code&gt; sur autre chose à cet endroit peut faire perdre le rafraîchissement du jeton, et l'on obtient des déconnexions difficiles à reproduire. Créez le client, appelez &lt;code&gt;getUser()&lt;/code&gt;, puis seulement ensuite décidez de la redirection.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Faire confiance à &lt;code&gt;getSession()&lt;/code&gt; côté serveur
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;getSession()&lt;/code&gt; lit la session depuis les cookies sans la revalider : un cookie modifié à la main passerait le contrôle. Dans le middleware, les Server Components et les Route Handlers, utilisez &lt;code&gt;getUser()&lt;/code&gt;, qui vérifie le jeton auprès de Supabase Auth. Gardez &lt;code&gt;getSession()&lt;/code&gt; pour le client, où il sert surtout à l'affichage.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Un &lt;code&gt;matcher&lt;/code&gt; trop large
&lt;/h2&gt;

&lt;p&gt;Sans configuration, le middleware tourne sur chaque requête, y compris les fichiers statiques et les images optimisées. Chaque appel ajoute un aller-retour vers Supabase et ralentit tout le site. Excluez ce qui n'a pas besoin de session :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/((?!_next/static|_next/image|favicon.ico|.*&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;.(?:svg|png|jpg|jpeg|gif|webp)$).*)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Et si vous redirigez vers &lt;code&gt;/login&lt;/code&gt;, vérifiez que &lt;code&gt;/login&lt;/code&gt; ne redirige pas à son tour : c'est la cause classique de la boucle infinie.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Croire que le middleware protège les données
&lt;/h2&gt;

&lt;p&gt;Le middleware protège la navigation, pas la base. Une requête faite directement avec la clé &lt;code&gt;anon&lt;/code&gt; contourne entièrement vos redirections. Activez Row Level Security sur chaque table exposée et écrivez des politiques basées sur &lt;code&gt;auth.uid()&lt;/code&gt;. Considérez le middleware comme une amélioration de l'expérience utilisateur, et RLS comme la vraie barrière de sécurité.&lt;/p&gt;

&lt;h2&gt;
  
  
  En résumé
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@supabase/ssr&lt;/code&gt; à la place des anciens helpers, cookies recopiés sur la réponse, &lt;code&gt;getUser()&lt;/code&gt; appelé immédiatement et utilisé côté serveur, un &lt;code&gt;matcher&lt;/code&gt; qui ignore les fichiers statiques, et RLS partout. Avec ces six points, l'authentification se comporte de la même façon en local et en production sur Vercel.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Transparence : cet article est écrit par l'équipe de &lt;a href="https://massvai.com" rel="noopener noreferrer"&gt;Massvai&lt;/a&gt;, un générateur d'applications par IA qui crée des projets full-stack Next.js, Supabase et Vercel. Les conseils s'appliquent à n'importe quel projet, que vous utilisiez notre outil ou non.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>supabase</category>
      <category>webdev</category>
      <category>authentication</category>
    </item>
    <item>
      <title>Supabase Realtime en Next.js (App Router): 5 errores comunes y cómo evitarlos</title>
      <dc:creator>Sora2 Hub Team</dc:creator>
      <pubDate>Sat, 17 Oct 2026 11:28:08 +0000</pubDate>
      <link>https://dev.to/sora2hubteam/supabase-realtime-en-nextjs-app-router-5-errores-comunes-y-como-evitarlos-4o1j</link>
      <guid>https://dev.to/sora2hubteam/supabase-realtime-en-nextjs-app-router-5-errores-comunes-y-como-evitarlos-4o1j</guid>
      <description>&lt;p&gt;Supabase Realtime permite que una app de Next.js reciba cambios de la base de datos al instante: un chat, un tablero de pedidos o un contador de notificaciones. Funciona muy bien en local, pero al pasar a producción aparecen siempre los mismos problemas. Estos son los cinco que más vemos y su solución.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Suscribirse desde un Server Component
&lt;/h2&gt;

&lt;p&gt;Realtime abre un WebSocket en el navegador. Un Server Component se ejecuta una vez en el servidor y no mantiene conexiones, así que la suscripción nunca llega al cliente. La suscripción debe vivir en un componente con &lt;code&gt;'use client'&lt;/code&gt;, y los datos iniciales pueden venir del servidor como props.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createBrowserClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@supabase/ssr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Pedidos&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;iniciales&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;iniciales&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Pedido&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;pedidos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setPedidos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iniciales&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBrowserClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_SUPABASE_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_SUPABASE_ANON_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pedidos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postgres_changes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INSERT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pedidos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setPedidos&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Pedido&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pedidos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;titulo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Olvidar limpiar el canal
&lt;/h2&gt;

&lt;p&gt;Sin &lt;code&gt;removeChannel&lt;/code&gt; en el cleanup del &lt;code&gt;useEffect&lt;/code&gt;, cada navegación o recarga en caliente crea un canal nuevo. El resultado son eventos duplicados y, con el tiempo, el límite de conexiones del proyecto alcanzado. Limpia siempre el canal al desmontar.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. No activar la publicación de la tabla
&lt;/h2&gt;

&lt;p&gt;Por defecto las tablas no emiten cambios. Hay que añadirlas a la publicación &lt;code&gt;supabase_realtime&lt;/code&gt; (en el panel: Database, Publications, o con SQL):&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;alter&lt;/span&gt; &lt;span class="n"&gt;publication&lt;/span&gt; &lt;span class="n"&gt;supabase_realtime&lt;/span&gt; &lt;span class="k"&gt;add&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pedidos&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si no llega ningún evento y no hay errores en consola, casi siempre es esto.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Esperar eventos que RLS bloquea
&lt;/h2&gt;

&lt;p&gt;Realtime respeta las políticas de Row Level Security. Si el usuario no puede hacer &lt;code&gt;select&lt;/code&gt; sobre una fila, tampoco recibirá su evento. Es lo correcto por seguridad, pero confunde cuando las políticas solo cubren &lt;code&gt;insert&lt;/code&gt;. Revisa que exista una política de lectura para las filas que el cliente debe ver.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Filtrar en el cliente en lugar de en el canal
&lt;/h2&gt;

&lt;p&gt;Recibir todos los cambios de la tabla y filtrarlos en JavaScript gasta ancho de banda y expone datos innecesarios. Usa el parámetro &lt;code&gt;filter&lt;/code&gt; del canal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mensajes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`sala_id=eq.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;salaId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Resumen
&lt;/h2&gt;

&lt;p&gt;Suscripción solo en cliente, limpieza del canal, tabla añadida a la publicación, políticas de lectura correctas y filtros en el servidor. Con eso, Realtime se comporta igual en Vercel que en local.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Aviso: escribimos esto desde el equipo de &lt;a href="https://massvai.com" rel="noopener noreferrer"&gt;Massvai&lt;/a&gt;, un generador de apps con IA que crea proyectos full-stack con Next.js, Supabase y Vercel. Los consejos sirven para cualquier proyecto, uses o no nuestra herramienta.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>supabase</category>
      <category>spanish</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Keep a Character Consistent Across AI-Generated Images and Clips</title>
      <dc:creator>Sora2 Hub Team</dc:creator>
      <pubDate>Sat, 17 Oct 2026 02:56:03 +0000</pubDate>
      <link>https://dev.to/sora2hubteam/how-to-keep-a-character-consistent-across-ai-generated-images-and-clips-50jf</link>
      <guid>https://dev.to/sora2hubteam/how-to-keep-a-character-consistent-across-ai-generated-images-and-clips-50jf</guid>
      <description>&lt;p&gt;You generate a great character in one image. Then you ask for the same person in a kitchen, at night, from the side, and you get a cousin. Different jawline, different jacket, different hair. Character drift is the most common complaint from people making storyboards, short ads or comic panels with AI.&lt;/p&gt;

&lt;p&gt;The good news is that most drift comes from a handful of fixable habits. This guide walks through a workflow that keeps a character recognizable from the first still to the final animated clip.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Write a character sheet before you write a scene
&lt;/h2&gt;

&lt;p&gt;Most people describe the character inside every scene prompt, slightly differently each time. The model treats each wording change as a new instruction.&lt;/p&gt;

&lt;p&gt;Instead, write one fixed block of text and paste it unchanged into every prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Mira: woman in her late 20s, short black bob with blunt bangs, round tortoiseshell glasses, mustard-yellow raincoat, small silver hoop earrings, calm expression.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Rules for a good sheet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Five to eight visual anchors.&lt;/strong&gt; Hair, eyewear, one signature garment, one accessory, age range, build.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concrete nouns, not vibes.&lt;/strong&gt; "Mustard-yellow raincoat" survives; "quirky artsy style" does not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never edit it mid-project.&lt;/strong&gt; If you must change something, start a new version and regenerate the reference.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Lock a reference image first
&lt;/h2&gt;

&lt;p&gt;Generate the character alone on a plain background, front-facing, neutral light. Make a few variations and pick one. This is your master reference.&lt;/p&gt;

&lt;p&gt;From then on, use image-to-image or reference-image mode for every new shot instead of pure text-to-image. Text alone gives the model too much freedom; a reference image pins the face and outfit.&lt;/p&gt;

&lt;p&gt;A useful trick is a simple turnaround: front, three-quarter and side views generated from the master. Having all three makes later angles far more stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Separate "who" from "where"
&lt;/h2&gt;

&lt;p&gt;Structure each scene prompt in two parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The unchanged character sheet.&lt;/li&gt;
&lt;li&gt;The scene: location, action, camera angle, lighting.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;[Character sheet]. Scene: she is sitting at a café window on a rainy evening, holding a paper cup, medium shot, warm interior light, rain streaks on the glass.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When drift happens, you only ever edit part two. That makes problems easy to isolate.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Change one variable at a time
&lt;/h2&gt;

&lt;p&gt;Moving the character to a new place, a new pose, new lighting and a new lens in one step almost guarantees drift. Change the location first and keep the pose simple. Once that works, change the lighting. Then the angle.&lt;/p&gt;

&lt;p&gt;It feels slower, but it's faster than regenerating twenty images that are all slightly wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Watch the usual drift triggers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Extreme close-ups and wide shots.&lt;/strong&gt; Faces drift most at the edges of the framing range. Medium shots are the safest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heavy style words.&lt;/strong&gt; "Oil painting", "anime" or "cinematic" can override facial features. Pick a style once and keep it in the sheet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Busy backgrounds.&lt;/strong&gt; Crowds and patterned walls pull detail away from the character.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hands holding new objects.&lt;/strong&gt; Props are fine, but introduce them one at a time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Animate from your best stills, not from text
&lt;/h2&gt;

&lt;p&gt;When you move to video, start from a still you already approved. Image-to-video keeps the face and outfit far better than generating the clip from a text prompt.&lt;/p&gt;

&lt;p&gt;Keep the motion prompt about motion only:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Slow push-in. She lifts the cup and looks out the window. Face, glasses and raincoat stay unchanged; no new people appear.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Short clips (3 to 5 seconds) drift less than long ones. If you need a longer sequence, chain several short clips, each starting from an approved frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Keep a project log
&lt;/h2&gt;

&lt;p&gt;A plain text file with the character sheet, the master reference, and the exact prompts that worked saves hours. When a client asks for "one more shot of Mira", you can reproduce her in minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple workflow to copy
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Write the character sheet.&lt;/li&gt;
&lt;li&gt;Generate and pick a master reference plus a turnaround.&lt;/li&gt;
&lt;li&gt;Build each scene with reference mode, editing only the scene half of the prompt.&lt;/li&gt;
&lt;li&gt;Approve stills.&lt;/li&gt;
&lt;li&gt;Animate approved stills into short clips.&lt;/li&gt;
&lt;li&gt;Log what worked.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;p&gt;Any generator with reference-image and image-to-video support can run this workflow. If you want image generation and video generation in one place, &lt;a href="https://www.sora2hub.org" rel="noopener noreferrer"&gt;Sora2 Hub&lt;/a&gt; is an AI image and video generator that lets you create the stills and animate them without switching apps.&lt;/p&gt;

&lt;p&gt;Consistency isn't magic. It's mostly discipline: one fixed description, one locked reference, and one change at a time.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>imagegeneration</category>
      <category>video</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Déployer une app Next.js + Supabase sur Vercel : la checklist en 8 points</title>
      <dc:creator>Sora2 Hub Team</dc:creator>
      <pubDate>Sun, 11 Oct 2026 18:43:52 +0000</pubDate>
      <link>https://dev.to/sora2hubteam/deployer-une-app-nextjs-supabase-sur-vercel-la-checklist-en-8-points-3ci1</link>
      <guid>https://dev.to/sora2hubteam/deployer-une-app-nextjs-supabase-sur-vercel-la-checklist-en-8-points-3ci1</guid>
      <description>&lt;p&gt;Votre app marche en local, &lt;code&gt;npm run dev&lt;/code&gt; ne renvoie aucune erreur… et la version en production affiche une page blanche. C'est presque toujours l'un des huit points ci-dessous. Gardez cette liste sous la main avant chaque mise en ligne.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Les variables d'environnement sont dans le bon environnement
&lt;/h2&gt;

&lt;p&gt;Sur Vercel, une variable peut être définie pour &lt;em&gt;Production&lt;/em&gt;, &lt;em&gt;Preview&lt;/em&gt; ou &lt;em&gt;Development&lt;/em&gt;. Vérifiez que &lt;code&gt;NEXT_PUBLIC_SUPABASE_URL&lt;/code&gt; et &lt;code&gt;NEXT_PUBLIC_SUPABASE_ANON_KEY&lt;/code&gt; existent bien pour &lt;strong&gt;Production&lt;/strong&gt;, puis relancez un déploiement : une variable ajoutée après le build n'est pas prise en compte.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. La clé &lt;code&gt;service_role&lt;/code&gt; ne sort jamais du serveur
&lt;/h2&gt;

&lt;p&gt;Elle contourne toutes les règles RLS. Ne lui donnez jamais le préfixe &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt;, utilisez-la uniquement dans des Route Handlers ou des Server Actions, et jamais dans un composant client.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. RLS est activé sur chaque table exposée
&lt;/h2&gt;

&lt;p&gt;Une table sans Row Level Security est lisible par n'importe qui possédant la clé anon, c'est-à-dire tout visiteur. Dans le tableau de bord Supabase, activez RLS et écrivez au moins une politique &lt;code&gt;select&lt;/code&gt; basée sur &lt;code&gt;auth.uid()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Les URL de redirection d'authentification incluent le domaine de production
&lt;/h2&gt;

&lt;p&gt;Dans &lt;em&gt;Authentication &amp;gt; URL Configuration&lt;/em&gt;, ajoutez &lt;code&gt;https://votre-domaine.com/**&lt;/code&gt; et, si vous utilisez les previews, &lt;code&gt;https://*-votre-equipe.vercel.app/**&lt;/code&gt;. Sinon, les liens magiques et l'OAuth renvoient vers &lt;code&gt;localhost&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Les cookies de session sont gérés côté serveur
&lt;/h2&gt;

&lt;p&gt;Avec l'App Router, utilisez &lt;code&gt;@supabase/ssr&lt;/code&gt; et un middleware qui rafraîchit la session. Sans cela, l'utilisateur semble connecté sur le client mais anonyme dans les Server Components.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Les migrations sont appliquées sur la base de production
&lt;/h2&gt;

&lt;p&gt;Les tables créées à la main en local n'existent pas forcément en ligne. Versionnez le schéma avec &lt;code&gt;supabase migration new&lt;/code&gt; et appliquez-le avec &lt;code&gt;supabase db push&lt;/code&gt; avant de déployer le code qui en dépend.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Les images distantes sont autorisées
&lt;/h2&gt;

&lt;p&gt;Si vous affichez des fichiers du Storage Supabase avec &lt;code&gt;next/image&lt;/code&gt;, déclarez l'hôte &lt;code&gt;*.supabase.co&lt;/code&gt; dans &lt;code&gt;images.remotePatterns&lt;/code&gt; de &lt;code&gt;next.config.js&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Les fonctions ne dépassent pas le délai
&lt;/h2&gt;

&lt;p&gt;Les traitements longs (génération, export, e-mails en masse) dépassent vite la durée maximale d'une fonction serverless. Déplacez-les dans une file ou une Edge Function Supabase et renvoyez immédiatement une réponse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pour aller plus vite
&lt;/h2&gt;

&lt;p&gt;Si vous partez de zéro, un générateur d'applications peut créer cette base pour vous. &lt;a href="https://massvai.com" rel="noopener noreferrer"&gt;Massvai&lt;/a&gt; est un générateur d'applications IA qui produit des apps full-stack Next.js, Supabase et Vercel dont vous gardez le code ; la checklist ci-dessus reste valable pour relire ce qu'il génère.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Transparence : je fais partie de l'équipe Massvai.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>supabase</category>
      <category>vercel</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Write a Spec an AI App Builder Can Actually Ship</title>
      <dc:creator>Sora2 Hub Team</dc:creator>
      <pubDate>Sun, 11 Oct 2026 16:14:22 +0000</pubDate>
      <link>https://dev.to/sora2hubteam/how-to-write-a-spec-an-ai-app-builder-can-actually-ship-5eie</link>
      <guid>https://dev.to/sora2hubteam/how-to-write-a-spec-an-ai-app-builder-can-actually-ship-5eie</guid>
      <description>&lt;h1&gt;
  
  
  How to Write a Spec an AI App Builder Can Actually Ship
&lt;/h1&gt;

&lt;p&gt;AI app builders can now produce a working full-stack app from a description. The catch is the description. "Build me a CRM" gets you a pretty demo with fake data. A short, structured spec gets you something close to a real product.&lt;/p&gt;

&lt;p&gt;Here's the spec format that consistently produces usable Next.js apps on the first or second try.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why vague prompts fail
&lt;/h2&gt;

&lt;p&gt;An AI builder has to make hundreds of decisions: data model, auth, routes, validation, empty states. Every decision you leave out, it guesses. Guesses compound. By the time you notice the data model is wrong, three screens are built on top of it.&lt;/p&gt;

&lt;p&gt;A spec doesn't need to be long. It needs to answer the questions the builder would otherwise guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-page spec
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. One sentence on who it's for and what they do
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;A booking tool for independent yoga teachers to publish class times and let students reserve a spot.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This single line decides tone, layout and which features matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Users and roles
&lt;/h3&gt;

&lt;p&gt;List each role and what it can do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Teacher:&lt;/strong&gt; creates classes, sees bookings, cancels classes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Student:&lt;/strong&gt; browses classes, books, cancels own booking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No admin panel&lt;/strong&gt; in v1.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Saying what's &lt;em&gt;out&lt;/em&gt; of scope is just as useful as what's in.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Data model in plain words
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Teacher has many Classes. Class has title, start time, duration, capacity, price. Student has many Bookings. Booking belongs to one Class and one Student; status is booked or cancelled.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You don't need SQL. Nouns, relationships and key fields are enough for the builder to produce a sane schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The three to five core flows
&lt;/h3&gt;

&lt;p&gt;Write each as steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Student opens a teacher's public page, sees upcoming classes.&lt;/li&gt;
&lt;li&gt;Student picks a class, signs in with email, confirms booking.&lt;/li&gt;
&lt;li&gt;Teacher sees the booking count update on their dashboard.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If a flow isn't listed, assume it won't be built well.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Rules and edge cases
&lt;/h3&gt;

&lt;p&gt;This is where most generated apps break:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A class can't be booked past capacity.&lt;/li&gt;
&lt;li&gt;A student can't book the same class twice.&lt;/li&gt;
&lt;li&gt;Cancelling within 12 hours of start is not allowed.&lt;/li&gt;
&lt;li&gt;All times are shown in the viewer's timezone.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. Stack and integrations
&lt;/h3&gt;

&lt;p&gt;If you have preferences, state them: Next.js App Router, Postgres, email magic-link auth, Stripe Checkout for paid classes. If you don't, say "use sensible defaults" so the builder doesn't invent something unusual.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. What "done" looks like
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Done when a teacher can create a class and a student can book and cancel it end to end, with capacity enforced.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This gives the builder, and you, a clear test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Iterate in small steps
&lt;/h2&gt;

&lt;p&gt;After the first build, resist rewriting the whole spec. Ask for one change at a time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Add a waitlist when a class is full."&lt;/li&gt;
&lt;li&gt;"Send the teacher an email when a booking is cancelled."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Small requests keep the existing structure intact. Big rewrites tend to regenerate working parts and break them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the boring parts
&lt;/h2&gt;

&lt;p&gt;Before you show anyone the app, test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Empty states (no classes yet).&lt;/li&gt;
&lt;li&gt;Error states (payment declined, invalid input).&lt;/li&gt;
&lt;li&gt;A second account, to confirm users can't see each other's data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the places AI-generated code is most likely to cut corners.&lt;/p&gt;

&lt;h2&gt;
  
  
  A template to copy
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Product: [one sentence]
Roles: [role: can do X, Y; out of scope: Z]
Data: [nouns, relationships, key fields]
Core flows: [3 to 5 numbered flows]
Rules: [limits, permissions, edge cases]
Stack: [preferences or "sensible defaults"]
Done when: [one end-to-end test]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where to try it
&lt;/h2&gt;

&lt;p&gt;Paste the template into any AI app builder. If you want a full-stack Next.js app with database and auth generated from a spec like this, &lt;a href="https://massvai.com" rel="noopener noreferrer"&gt;Massvai&lt;/a&gt; is an AI full-stack app builder built for exactly that workflow.&lt;/p&gt;

&lt;p&gt;The better your spec, the less you fight the generator. One page of clear decisions beats fifty follow-up prompts.&lt;/p&gt;

</description>
      <category>startup</category>
      <category>ai</category>
      <category>webdev</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Supabase RLS for a Next.js SaaS: The 7 Policies You Actually Need</title>
      <dc:creator>Sora2 Hub Team</dc:creator>
      <pubDate>Sat, 10 Oct 2026 16:59:51 +0000</pubDate>
      <link>https://dev.to/sora2hubteam/supabase-rls-for-a-nextjs-saas-the-7-policies-you-actually-need-5a15</link>
      <guid>https://dev.to/sora2hubteam/supabase-rls-for-a-nextjs-saas-the-7-policies-you-actually-need-5a15</guid>
      <description>&lt;p&gt;"new row violates row-level security policy" is probably the most common error people hit on their first Supabase + Next.js app. It usually shows up right after you enable RLS, which is the correct thing to do. The fix is almost never "turn RLS off". You need a small, predictable set of policies and a client that actually sends the user's JWT.&lt;/p&gt;

&lt;p&gt;This is the checklist I use for a typical multi-tenant SaaS: users, profiles, workspaces, members and app data.&lt;/p&gt;

&lt;h2&gt;
  
  
  First: how RLS decides
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RLS on with no policies means nobody gets in&lt;/strong&gt; (except the service role). That's why inserts suddenly fail after you flip the switch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each operation needs its own policy.&lt;/strong&gt; A &lt;code&gt;select&lt;/code&gt; policy does not allow &lt;code&gt;insert&lt;/code&gt;, and an &lt;code&gt;update&lt;/code&gt; policy does not cover &lt;code&gt;upsert&lt;/code&gt; inserts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;using&lt;/code&gt; filters existing rows; &lt;code&gt;with check&lt;/code&gt; validates new or changed rows.&lt;/strong&gt; Inserts only use &lt;code&gt;with check&lt;/code&gt;. Updates use both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgREST returns the inserted row by default.&lt;/strong&gt; If you have an insert policy but no matching select policy, the insert can fail on the return step. Add a select policy or call &lt;code&gt;.insert(row, { returning: 'minimal' })&lt;/code&gt; (in supabase-js v2, just don't chain &lt;code&gt;.select()&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grants still apply.&lt;/strong&gt; A missing grant raises &lt;code&gt;42501&lt;/code&gt; before any policy runs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The 7 policies
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Profiles: read your own
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="nv"&gt;"profiles: read own"&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;profiles&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt;
&lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;authenticated&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wrapping &lt;code&gt;auth.uid()&lt;/code&gt; in &lt;code&gt;(select ...)&lt;/code&gt; lets Postgres cache it per statement, which matters on large tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Profiles: created by a trigger, not the client
&lt;/h3&gt;

&lt;p&gt;Don't insert profile rows from &lt;code&gt;onAuthStateChange&lt;/code&gt; in the browser. Use a trigger on &lt;code&gt;auth.users&lt;/code&gt;:&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;create&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_new_user&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="k"&gt;trigger&lt;/span&gt; &lt;span class="k"&gt;language&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt; &lt;span class="k"&gt;security&lt;/span&gt; &lt;span class="k"&gt;definer&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="n"&gt;search_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;profiles&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;trigger&lt;/span&gt; &lt;span class="n"&gt;on_auth_user_created&lt;/span&gt;
&lt;span class="k"&gt;after&lt;/span&gt; &lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt; &lt;span class="k"&gt;execute&lt;/span&gt; &lt;span class="k"&gt;procedure&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_new_user&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No client insert policy is needed, which is one less thing to get wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Profiles: update your own, can't change owner
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="nv"&gt;"profiles: update own"&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;profiles&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;update&lt;/span&gt;
&lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;authenticated&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="k"&gt;check&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Membership helper
&lt;/h3&gt;

&lt;p&gt;Most SaaS data belongs to a workspace, not a user. Put the membership check in one &lt;code&gt;security definer&lt;/code&gt; function so policies stay short and you avoid recursive policies on the members table:&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;create&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="nb"&gt;boolean&lt;/span&gt; &lt;span class="k"&gt;language&lt;/span&gt; &lt;span class="k"&gt;sql&lt;/span&gt; &lt;span class="k"&gt;stable&lt;/span&gt; &lt;span class="k"&gt;security&lt;/span&gt; &lt;span class="k"&gt;definer&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="n"&gt;search_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
  &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="k"&gt;exists&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;workspace_members&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;workspace_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Workspace data: read and write if member
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="nv"&gt;"projects: member read"&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;authenticated&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workspace_id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="nv"&gt;"projects: member insert"&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;authenticated&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="k"&gt;check&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workspace_id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="nv"&gt;"projects: member update"&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;update&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;authenticated&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workspace_id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="k"&gt;check&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workspace_id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Owner-only delete
&lt;/h3&gt;

&lt;p&gt;Deletes are where accidents happen. Restrict them to owners:&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;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="nv"&gt;"projects: owner delete"&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;authenticated&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;exists&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;workspace_members&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
  &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;workspace_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;workspace_id&lt;/span&gt;
    &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'owner'&lt;/span&gt;
&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. Storage objects
&lt;/h3&gt;

&lt;p&gt;Storage uploads are inserts into &lt;code&gt;storage.objects&lt;/code&gt; followed by a &lt;code&gt;returning&lt;/code&gt;. You need insert &lt;strong&gt;and&lt;/strong&gt; select:&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;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="nv"&gt;"avatars: upload own folder"&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;authenticated&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="k"&gt;check&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'avatars'&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;foldername&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;())::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="nv"&gt;"avatars: read own folder"&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;authenticated&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'avatars'&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;foldername&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;())::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Next.js side: make sure the JWT arrives
&lt;/h2&gt;

&lt;p&gt;Half of "RLS is broken" reports are really "the request was anonymous".&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the App Router, use &lt;code&gt;@supabase/ssr&lt;/code&gt; and &lt;code&gt;createServerClient&lt;/code&gt; with cookie &lt;code&gt;getAll&lt;/code&gt;/&lt;code&gt;setAll&lt;/code&gt; in Server Components, Route Handlers and middleware. Don't mix it with the old auth-helpers package.&lt;/li&gt;
&lt;li&gt;In middleware, write refreshed cookies to both the request and the response, or the next request will look logged out.&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;supabase.auth.getUser()&lt;/code&gt; on the server, not &lt;code&gt;getSession()&lt;/code&gt;, when you make auth decisions.&lt;/li&gt;
&lt;li&gt;Never ship the service role key to the browser. If something only works with the service role, that's a missing policy, not a reason to use the service key.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Test policies, don't eyeball them
&lt;/h2&gt;

&lt;p&gt;Put pgTAP tests in &lt;code&gt;supabase/tests/&lt;/code&gt; and run &lt;code&gt;supabase test db&lt;/code&gt;. For each table, assert:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;anon can't select, insert, update or delete&lt;/li&gt;
&lt;li&gt;a member can do what they should&lt;/li&gt;
&lt;li&gt;a non-member gets zero rows on select and &lt;code&gt;42501&lt;/code&gt; on insert&lt;/li&gt;
&lt;li&gt;an update can't move a row to another workspace&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One catch: a denied &lt;code&gt;update&lt;/code&gt; filtered by &lt;code&gt;using&lt;/code&gt; raises nothing. It just matches zero rows. Assert with &lt;code&gt;returning&lt;/code&gt; and check the result, or your test will pass on a write that never happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generated code still needs this
&lt;/h2&gt;

&lt;p&gt;If you scaffold apps with an AI builder, check the generated migrations against this list before launch. Tools that output a real Next.js + Supabase codebase, like &lt;a href="https://massvai.com" rel="noopener noreferrer"&gt;Massvai&lt;/a&gt;, make that easy because you can read and edit the SQL. Whatever generated it, the rule is the same: one policy per operation, a membership helper, and tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick debugging order
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Is RLS enabled and are the grants there?&lt;/li&gt;
&lt;li&gt;Is there a policy for this exact operation?&lt;/li&gt;
&lt;li&gt;Does the insert also need a select policy for &lt;code&gt;returning&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Is the request authenticated? Log &lt;code&gt;auth.uid()&lt;/code&gt; with &lt;code&gt;select auth.uid()&lt;/code&gt; from the same client.&lt;/li&gt;
&lt;li&gt;Does &lt;code&gt;with check&lt;/code&gt; match the values you're actually sending, for example &lt;code&gt;user_id&lt;/code&gt;?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Get those five right and the error mostly goes away.&lt;/p&gt;

</description>
      <category>security</category>
      <category>supabase</category>
      <category>nextjs</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Camera Motion Prompts for AI Image-to-Video: A Practical Cheat Sheet</title>
      <dc:creator>Sora2 Hub Team</dc:creator>
      <pubDate>Sat, 10 Oct 2026 06:35:00 +0000</pubDate>
      <link>https://dev.to/sora2hubteam/camera-motion-prompts-for-ai-image-to-video-a-practical-cheat-sheet-41od</link>
      <guid>https://dev.to/sora2hubteam/camera-motion-prompts-for-ai-image-to-video-a-practical-cheat-sheet-41od</guid>
      <description>&lt;p&gt;Most weak image-to-video clips fail for the same reason: the prompt describes the picture again instead of describing the &lt;em&gt;movement&lt;/em&gt;. The model can already see your image. What it needs from you is how the camera moves, what moves inside the frame, and what must stay still.&lt;/p&gt;

&lt;p&gt;This cheat sheet collects the camera moves that work reliably, the wording that tends to produce them, and the mistakes that cause warping and drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three-part motion prompt
&lt;/h2&gt;

&lt;p&gt;A good image-to-video prompt usually answers three questions, in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Camera:&lt;/strong&gt; How does the camera move? (push in, orbit, pan, static)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subject:&lt;/strong&gt; What moves in the scene? (hair in the wind, steam rising, a person turning their head)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Constraints:&lt;/strong&gt; What must not change? (the logo, the face, the product shape, the background)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Slow push-in toward the cup. Steam rises gently from the coffee. The cup, label and table stay unchanged; no new objects appear.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Short and specific beats long and poetic. If you write a paragraph of adjectives, the model spends its effort on style instead of motion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Camera moves that work well
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Move&lt;/th&gt;
&lt;th&gt;Prompt wording&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Push-in&lt;/td&gt;
&lt;td&gt;"slow push-in toward the subject"&lt;/td&gt;
&lt;td&gt;Product hero shots, portraits, reveals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pull-out&lt;/td&gt;
&lt;td&gt;"slow pull-back revealing the room"&lt;/td&gt;
&lt;td&gt;Showing context or scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orbit&lt;/td&gt;
&lt;td&gt;"camera orbits 90 degrees around the subject, smooth"&lt;/td&gt;
&lt;td&gt;Products, sculptures, characters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pan&lt;/td&gt;
&lt;td&gt;"slow pan left across the scene"&lt;/td&gt;
&lt;td&gt;Landscapes, wide interiors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tilt&lt;/td&gt;
&lt;td&gt;"camera tilts up from the shoes to the face"&lt;/td&gt;
&lt;td&gt;Fashion, full-body shots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static&lt;/td&gt;
&lt;td&gt;"locked-off static camera"&lt;/td&gt;
&lt;td&gt;When only the subject should move&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Handheld&lt;/td&gt;
&lt;td&gt;"subtle handheld movement"&lt;/td&gt;
&lt;td&gt;Casual, UGC-style social clips&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dolly zoom&lt;/td&gt;
&lt;td&gt;"dolly zoom, background stretches"&lt;/td&gt;
&lt;td&gt;Dramatic moments (use sparingly)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two tips that apply to all of them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Say the speed.&lt;/strong&gt; "Slow" and "smooth" reduce jitter. Fast moves amplify every artifact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use one camera move per clip.&lt;/strong&gt; "Push in, then orbit, then pan up" in a five-second clip almost always breaks. If you need several moves, generate several clips and cut them together.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Subject motion: keep it small
&lt;/h2&gt;

&lt;p&gt;Small, physical motions look natural: blinking, breathing, fabric moving, water rippling, leaves swaying, light flickering. Large actions (running, dancing, turning around fully) are where faces and hands fall apart.&lt;/p&gt;

&lt;p&gt;If a character needs to do something big, consider giving the model an end frame as well as a start frame, if your tool supports it. Anchoring both ends of the clip keeps identity much more stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Constraints: tell the model what to protect
&lt;/h2&gt;

&lt;p&gt;This is the part people skip, and it matters most for commercial work. Useful constraint phrases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"the product shape and label stay unchanged"&lt;/li&gt;
&lt;li&gt;"the face stays consistent, no morphing"&lt;/li&gt;
&lt;li&gt;"background remains still"&lt;/li&gt;
&lt;li&gt;"no text, no new objects"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not every model follows negative instructions equally well, so check the output frame by frame anyway, especially logos and hands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Re-describing the image.&lt;/strong&gt; "A red sneaker on a white background" adds nothing. Describe motion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conflicting moves.&lt;/strong&gt; "Static camera, slow orbit" confuses the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low-resolution input.&lt;/strong&gt; Compression artifacts become flicker once the image moves. Start from the sharpest original you have.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Busy source images.&lt;/strong&gt; Lots of small details (text, patterns, crowds) tend to warp. Simpler compositions animate more cleanly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Judging one take.&lt;/strong&gt; The same prompt can produce very different results. Generate three or four variations before you rewrite anything.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A quick workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Pick or generate a clean first frame (an image model helps if you need a new setting).&lt;/li&gt;
&lt;li&gt;Write the three-part prompt: camera, subject, constraints.&lt;/li&gt;
&lt;li&gt;Generate a few short variations with the same prompt.&lt;/li&gt;
&lt;li&gt;Change one variable at a time: speed, move, or constraint wording.&lt;/li&gt;
&lt;li&gt;Try the same prompt on a second video model; models differ a lot in how they handle motion.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Comparing models without juggling accounts
&lt;/h2&gt;

&lt;p&gt;Step 5 is where most people give up, because every model lives behind a different account and billing page. &lt;a href="https://www.sora2hub.org" rel="noopener noreferrer"&gt;Sora2 Hub&lt;/a&gt; is an AI image and video generator that puts several image and video models behind one credit balance, so you can run the same motion prompt on two or three models and compare the clips side by side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Copy-paste starter prompts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Product:&lt;/strong&gt; "Slow 90-degree orbit around the bottle on a marble surface. Soft light glints on the glass. Bottle shape and label stay unchanged."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portrait:&lt;/strong&gt; "Locked-off static camera. She blinks and smiles slightly; hair moves in a light breeze. Face stays consistent."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Landscape:&lt;/strong&gt; "Slow pan right across the valley. Clouds drift, grass sways. No people appear."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Food:&lt;/strong&gt; "Slow push-in toward the bowl. Steam rises, sauce glistens. Bowl and table stay still."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interior:&lt;/strong&gt; "Slow pull-back from the sofa revealing the whole living room. Curtains move gently."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start with these, change one thing at a time, and keep the camera move simple. That alone fixes most bad clips.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>video</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>v0 Alternatives for Full-Stack Next.js Apps</title>
      <dc:creator>Sora2 Hub Team</dc:creator>
      <pubDate>Fri, 09 Oct 2026 13:06:26 +0000</pubDate>
      <link>https://dev.to/sora2hubteam/v0-alternatives-for-full-stack-nextjs-apps-4m2b</link>
      <guid>https://dev.to/sora2hubteam/v0-alternatives-for-full-stack-nextjs-apps-4m2b</guid>
      <description>&lt;p&gt;&lt;em&gt;v0 is a strong Next.js tool. If you want a different setup for backend, hosting, billing or workflow, here are the alternatives worth testing and what each one is built around.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quick answer:&lt;/strong&gt; If you need a &lt;strong&gt;full-stack Next.js&lt;/strong&gt; app and want something other than v0, look at four options. &lt;strong&gt;Massvai&lt;/strong&gt; generates a Next.js 15 + TypeScript app with Supabase and Stripe in the stack, syncs to GitHub, deploys to your Vercel project, and exports every file. &lt;strong&gt;Bolt&lt;/strong&gt; supports many JavaScript frameworks including Next.js, with hosting and databases on Bolt Cloud. &lt;strong&gt;Replit Agent&lt;/strong&gt; works with any framework and hosts the app on Replit with a built-in PostgreSQL database. &lt;strong&gt;Lovable&lt;/strong&gt; is a capable full-stack builder, but it doesn't generate Next.js. New apps use TanStack Start, so it only fits if Next.js isn't a hard requirement. Pick based on where you want the database and hosting to live.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  First: what v0 already does well
&lt;/h2&gt;

&lt;p&gt;It's worth being precise, because many "alternatives" lists understate v0. According to Vercel's docs, v0 builds with &lt;strong&gt;Next.js, React, TypeScript, Tailwind CSS and shadcn/ui&lt;/strong&gt;. It can import existing GitHub repos, Vercel projects or ZIP files, syncs with GitHub in both directions, and publishes through pull requests that respect required checks. Since early 2026, previews run in Vercel Sandbox, so server code, API routes and database connections work in the preview. Databases and services come from &lt;strong&gt;Vercel Marketplace integrations&lt;/strong&gt; such as Neon, Supabase and Upstash, and Stripe is available as an integration.&lt;/p&gt;

&lt;p&gt;So people usually look elsewhere for workflow reasons, not missing features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They want a &lt;strong&gt;fixed, opinionated SaaS stack&lt;/strong&gt; (auth + database + billing) generated from the first prompt, rather than assembled from integrations.&lt;/li&gt;
&lt;li&gt;They want the &lt;strong&gt;builder itself to host everything&lt;/strong&gt;, including the database, outside Vercel.&lt;/li&gt;
&lt;li&gt;They prefer a &lt;strong&gt;different pricing model&lt;/strong&gt;. v0 combines a plan with usage credits, priced per user on team plans.&lt;/li&gt;
&lt;li&gt;They want &lt;strong&gt;frameworks beyond Next.js&lt;/strong&gt; in the same tool.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Comparison table
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;From each vendor's official site and documentation, checked October 2026. Plans change, so confirm details before you buy.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;
&lt;strong&gt;v0&lt;/strong&gt; (reference)&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Massvai&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Bolt&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Replit Agent&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Lovable&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Next.js&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;td&gt;Default (Next.js 15)&lt;/td&gt;
&lt;td&gt;One of many JS frameworks&lt;/td&gt;
&lt;td&gt;Any framework&lt;/td&gt;
&lt;td&gt;Not offered (TanStack Start for new apps)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Marketplace integrations (Neon, Supabase, Upstash…)&lt;/td&gt;
&lt;td&gt;Supabase&lt;/td&gt;
&lt;td&gt;Bolt Cloud database or Supabase&lt;/td&gt;
&lt;td&gt;Built-in managed PostgreSQL (separate dev and production DBs)&lt;/td&gt;
&lt;td&gt;Lovable Cloud or Supabase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Payments&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stripe integration&lt;/td&gt;
&lt;td&gt;Stripe in the stack&lt;/td&gt;
&lt;td&gt;Stripe via Bolt Cloud&lt;/td&gt;
&lt;td&gt;Ask the agent to integrate&lt;/td&gt;
&lt;td&gt;Built-in payments or Stripe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hosting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Vercel&lt;/td&gt;
&lt;td&gt;Your Vercel project&lt;/td&gt;
&lt;td&gt;Bolt Cloud, Netlify, others via GitHub&lt;/td&gt;
&lt;td&gt;Replit (Autoscale or Reserved VM)&lt;/td&gt;
&lt;td&gt;Lovable hosting; self-host possible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Two-way sync, PR-based publish&lt;/td&gt;
&lt;td&gt;GitHub sync&lt;/td&gt;
&lt;td&gt;Sync, branches, import repos&lt;/td&gt;
&lt;td&gt;Check Replit's docs&lt;/td&gt;
&lt;td&gt;Git sync (new repos only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Code export&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Every file&lt;/td&gt;
&lt;td&gt;Via GitHub or download&lt;/td&gt;
&lt;td&gt;Check Replit's docs&lt;/td&gt;
&lt;td&gt;ZIP on paid plans; Git sync on all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pricing unit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Plan + usage credits&lt;/td&gt;
&lt;td&gt;Credits per agent generation&lt;/td&gt;
&lt;td&gt;Tokens&lt;/td&gt;
&lt;td&gt;Check Replit's pricing page&lt;/td&gt;
&lt;td&gt;Credits per workspace&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Massvai: an opinionated Next.js + Supabase + Stripe starting point
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://massvai.com" rel="noopener noreferrer"&gt;Massvai&lt;/a&gt; is an AI coding agent built around a single stack: &lt;strong&gt;Next.js 15, TypeScript and Tailwind&lt;/strong&gt;, with &lt;strong&gt;Supabase&lt;/strong&gt; for auth and data and &lt;strong&gt;Stripe&lt;/strong&gt; for payments. You describe the product, optionally with a screenshot or reference file. The agent plans before it builds, then writes the code while you watch a live preview. When you're done, you sync the repo to GitHub and deploy to Vercel through a guided flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it differs from v0:&lt;/strong&gt; both produce Next.js and deploy to Vercel. v0 is open-ended and assembles services through Marketplace integrations. Massvai starts from a full SaaS shape (auth, database, billing-ready UI) built on Supabase. Every file is exportable, so the result is a standard Next.js + Supabase repo you can keep developing elsewhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; credits per agent generation. The Free plan includes 100 welcome credits with live preview and code export. Builder is $25/month (1,500 credits). Pro is $49/month (4,000 credits) and adds Supabase setup help, version history and priority support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good fit if:&lt;/strong&gt; you're a founder or small team building a SaaS MVP or internal tool and want Supabase specifically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consider something else if:&lt;/strong&gt; you need a non-Supabase database, a framework other than Next.js, or deep work inside a large existing repo. v0 and Bolt both import existing repositories.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bolt: framework flexibility with built-in hosting
&lt;/h2&gt;

&lt;p&gt;Bolt's docs describe support for JavaScript web technologies, with Node.js on the backend and a choice of JavaScript frontend frameworks. You can ask for Next.js, but it isn't the only path. New projects host on &lt;strong&gt;Bolt Cloud&lt;/strong&gt;, which Bolt says is powered by Netlify and Supabase and covers hosting, domains, databases, auth, storage and server functions. Bolt can also generate &lt;strong&gt;Expo mobile apps&lt;/strong&gt; when you ask for a mobile app in your first prompt. It syncs with GitHub, supports branches and imports existing repos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; token-based. Bolt notes that larger projects use more tokens per message because project files are sent to the AI. Its pricing page lists a free tier, Pro from $25/month, and Teams per member.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good fit if:&lt;/strong&gt; you want one tool for several frameworks or a mobile companion app, and you're happy with hosting on Bolt Cloud. Bolt's docs say it doesn't support Python or PHP backends.&lt;/p&gt;




&lt;h2&gt;
  
  
  Replit Agent: any framework, hosted on Replit
&lt;/h2&gt;

&lt;p&gt;Replit's docs describe its General Agent as working with &lt;strong&gt;any framework or language&lt;/strong&gt;. It can do research and file generation, connect to services like BigQuery, Linear and Slack, and build full-stack apps. Every Replit App comes with a &lt;strong&gt;managed PostgreSQL database&lt;/strong&gt;, and published apps get a separate production database. Full-stack apps built with Agent publish to &lt;strong&gt;Autoscale&lt;/strong&gt; (the default) or &lt;strong&gt;Reserved VM&lt;/strong&gt; deployments, not static hosting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good fit if:&lt;/strong&gt; you want the build environment, database and hosting all in one place, or your project mixes Next.js with other languages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consider something else if:&lt;/strong&gt; you specifically want deployments in your own Vercel account or a Supabase backend.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lovable: a full-stack builder if Next.js is negotiable
&lt;/h2&gt;

&lt;p&gt;Lovable is often listed as a v0 alternative, and it's a strong product, but its own FAQ says you can't choose Next.js. New apps (since May 13, 2026) use &lt;strong&gt;TanStack Start&lt;/strong&gt;, and older apps use React + Vite. It includes Lovable Cloud (managed PostgreSQL, auth, storage), built-in payments on paid plans, hosting on &lt;code&gt;lovable.app&lt;/code&gt;, and Git sync on any plan. Pricing uses workspace credits rather than per-seat fees.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good fit if:&lt;/strong&gt; you care more about getting a hosted app quickly than about the framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not a fit if:&lt;/strong&gt; your team, hiring plan or existing code requires Next.js.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to choose
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Is Next.js a hard requirement?&lt;/strong&gt; If yes, your shortlist is v0, Massvai, and Bolt or Replit (both possible, but not their default).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where should the database live?&lt;/strong&gt; Vercel Marketplace (v0), Supabase (Massvai, also an option in Bolt and Lovable), Bolt Cloud, or Replit's built-in Postgres.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where should the app run?&lt;/strong&gt; Your own Vercel account (v0, Massvai), the builder's cloud (Bolt, Replit, Lovable), or anywhere via GitHub.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New project or existing repo?&lt;/strong&gt; For existing repos, v0 and Bolt both document repo import.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How will you pay?&lt;/strong&gt; Credits, tokens and per-seat plans aren't directly comparable. Estimate based on how many iterations your project needs.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  A simple test
&lt;/h2&gt;

&lt;p&gt;Write one product brief, for example "a team task tracker with auth, a Postgres-backed board, and a paid Pro plan", and give it to your top two picks. Compare four things: does it run, is the auth and database setup secure (check RLS or equivalent access rules), can you push it to your GitHub, and would you be comfortable maintaining the code six months from now? That last question usually decides it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sources: v0 docs, FAQ and pricing page; Massvai homepage; Bolt pricing page and support docs (supported technologies, Bolt Cloud, GitHub); Replit docs (General Agent, deployment types, SQL database, development and production databases); Lovable docs FAQ and pricing page. Checked October 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>v0</category>
    </item>
    <item>
      <title>Best AI Video Generators in 2026: Which Model for Which Job</title>
      <dc:creator>Sora2 Hub Team</dc:creator>
      <pubDate>Fri, 09 Oct 2026 13:00:32 +0000</pubDate>
      <link>https://dev.to/sora2hubteam/sora-2-is-shut-down-what-creators-can-use-instead-4ffj</link>
      <guid>https://dev.to/sora2hubteam/sora-2-is-shut-down-what-creators-can-use-instead-4ffj</guid>
      <description>&lt;h1&gt;
  
  
  Best AI Video Generators in 2026: Which Model for Which Job
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;An honest look at the leading AI video models, what each one does well, where it falls short, and who it suits.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quick answer:&lt;/strong&gt; No single AI video model wins at everything in 2026, so pick by job. &lt;strong&gt;Google Veo 3.1&lt;/strong&gt; suits cinematic clips with native audio. &lt;strong&gt;Kling 3.0&lt;/strong&gt; suits multi-shot scenes up to 15 seconds. &lt;strong&gt;ByteDance Seedance 2.0&lt;/strong&gt; suits heavy reference-driven work. &lt;strong&gt;MiniMax Hailuo 2.3&lt;/strong&gt; suits affordable, motion-heavy clips. &lt;strong&gt;Alibaba Wan 2.7&lt;/strong&gt; suits prompt-based video editing. For stills and thumbnails, &lt;strong&gt;Nano Banana Pro&lt;/strong&gt; and &lt;strong&gt;GPT Image 2&lt;/strong&gt; are the main options.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  How to choose a model
&lt;/h2&gt;

&lt;p&gt;Decide what you actually need first: native audio or not, one shot or several, consistent references (product, logo, character), output resolution and aspect ratio, and cost per &lt;em&gt;usable&lt;/em&gt; clip. A cheap generation isn't cheap if you rerun it five times.&lt;/p&gt;

&lt;p&gt;All the specs below come from each vendor's own documentation or announcement. Vendors update their models often, so check them again before you commit a budget.&lt;/p&gt;




&lt;h2&gt;
  
  
  The video models
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Google Veo 3.1: cinematic shots with native audio
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Strengths.&lt;/strong&gt; Google describes Veo 3.1 as generating video with natively generated audio, including dialogue, sound effects and ambient sound. The Gemini API docs list 720p, 1080p and 4K output at 24 fps, in 16:9 or 9:16. Veo 3.1 supports text-to-video, image-to-video, first-and-last-frame transitions and reference images. &lt;em&gt;Scene extension&lt;/em&gt; lets you continue a Veo clip to build longer sequences.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limits.&lt;/strong&gt; Base clips are 4, 6 or 8 seconds, so longer pieces depend on extension. In the Gemini API, extension works only on 720p Veo-generated video. Google's docs also note that higher resolutions take longer and that 4K costs more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; brand films, cinematic B-roll, and short ads where synced sound matters and you want to direct the camera.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kling 3.0 (Kuaishou): multi-shot storytelling up to 15 seconds
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Strengths.&lt;/strong&gt; Kuaishou launched the Kling AI 3.0 series in February 2026: Video 3.0, Video 3.0 Omni, Image 3.0 and Image 3.0 Omni. According to Kling's guides, Video 3.0 adds native audio in several languages, dialects and accents, multi-shot storyboarding, element references for keeping characters consistent, and flexible clip lengths from 3 to 15 seconds. Kuaishou also highlights better preservation of text in the frame, such as signage and logos, and gives e-commerce ads as an example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limits.&lt;/strong&gt; The Video 3.0 Omni guide lists 1080p and 720p modes, and credit cost depends on your inputs and clip length. Plan for 1080p delivery rather than native 4K video.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; short narrative content, dialogue scenes, and product ads where a logo or character must stay readable and consistent across shots.&lt;/p&gt;

&lt;h3&gt;
  
  
  Seedance 2.0 (ByteDance Seed): the reference-heavy option
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Strengths.&lt;/strong&gt; Seedance 2.0 is built on what ByteDance calls a unified multimodal audio-video joint generation architecture. It accepts four kinds of input: text, image, audio and video. ByteDance says one generation can combine up to 9 images, 3 video clips and 3 audio clips with natural-language instructions. The model can borrow composition, camera movement, motion and sound from those references. ByteDance's launch post also mentions multi-shot audio-video output up to 15 seconds, stereo audio, and video extension and editing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limits.&lt;/strong&gt; Setting up a prompt with many inputs takes more work, and it's at its best when you have reference assets. ByteDance's performance claims come from its own internal benchmark, so test it on your own content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; music-synced edits, outfit-change and product-showcase videos, and anyone who wants to copy a specific camera move or motion style from an existing clip.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hailuo 2.3 (MiniMax): motion and value
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Strengths.&lt;/strong&gt; MiniMax built Hailuo 2.3 on Hailuo 02, with better body movement, physical realism, facial micro-expressions and response to motion instructions. It also improves support for anime, illustration, ink-wash and game-CG styles. A &lt;strong&gt;Hailuo 2.3 Fast&lt;/strong&gt; variant generates faster at a lower price, and MiniMax publishes per-clip pay-as-you-go prices on its API pricing page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limits.&lt;/strong&gt; MiniMax's API docs list 768P clips at 6 or 10 seconds and 1080P clips at 6 seconds only. That's shorter and lower-resolution than some competitors. Check whether the version you use generates audio, and plan to add sound in editing if it doesn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; e-commerce sellers and social teams making many short clips, stylized and anime content, and dance or action scenes where motion quality matters most.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wan 2.7 (Alibaba): editing video with prompts
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Strengths.&lt;/strong&gt; Alibaba launched Wan2.7-Video in April 2026 as four models: text-to-video, image-to-video, reference-to-video and video editing. It generates 2 to 15 seconds at 720p or 1080p. Its standout feature is editing with plain-language instructions: you can change actions, dialogue, appearance, scenes, style or camera work in an existing clip. Alibaba says it keeps up to five characters consistent across videos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limits.&lt;/strong&gt; Wan 2.7 is offered as a hosted service through Alibaba Cloud Model Studio and the Wan website. If you chose Wan because earlier versions had openly downloadable weights, check which versions are actually open before you plan a self-hosted pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; creators who iterate on existing footage, and teams that want to change one element of a clip without regenerating everything.&lt;/p&gt;




&lt;h2&gt;
  
  
  The image models (for thumbnails, key art and product shots)
&lt;/h2&gt;

&lt;p&gt;Most video projects also need stills, for thumbnails, key art and first frames for image-to-video. Two image models cover most of that work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nano Banana Pro (Gemini 3 Pro Image)
&lt;/h3&gt;

&lt;p&gt;Google positions Nano Banana Pro as its studio-quality image generation and editing model. Google highlights accurate, legible text inside images in multiple languages, controls for localized edits, camera angle, focus, lighting and color, and output at 1K, 2K or 4K. Images include SynthID watermarking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; posters, thumbnails, infographics and localized ad creatives where the text has to be right, plus high-resolution first frames for image-to-video.&lt;/p&gt;

&lt;h3&gt;
  
  
  GPT Image 2 (OpenAI)
&lt;/h3&gt;

&lt;p&gt;OpenAI describes GPT Image 2 (&lt;code&gt;gpt-image-2&lt;/code&gt;) as its state-of-the-art model for fast, high-quality image generation and editing, with flexible image sizes, high-fidelity image inputs and inpainting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; creators who liked OpenAI's style of prompt understanding and want quick concept art, product mockups and edits to existing images.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it's good at&lt;/th&gt;
&lt;th&gt;Main limit to plan around&lt;/th&gt;
&lt;th&gt;Good fit for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Veo 3.1&lt;/td&gt;
&lt;td&gt;Video&lt;/td&gt;
&lt;td&gt;Native audio, up to 4K, frame control&lt;/td&gt;
&lt;td&gt;4–8 s base clips; extension at 720p&lt;/td&gt;
&lt;td&gt;Cinematic ads, B-roll&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kling 3.0&lt;/td&gt;
&lt;td&gt;Video&lt;/td&gt;
&lt;td&gt;Multi-shot, 3–15 s, multilingual audio&lt;/td&gt;
&lt;td&gt;1080p/720p modes&lt;/td&gt;
&lt;td&gt;Narrative shorts, product ads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seedance 2.0&lt;/td&gt;
&lt;td&gt;Video&lt;/td&gt;
&lt;td&gt;Mixed image/video/audio references&lt;/td&gt;
&lt;td&gt;Prompt setup takes more work&lt;/td&gt;
&lt;td&gt;Music-synced, reference-driven edits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hailuo 2.3&lt;/td&gt;
&lt;td&gt;Video&lt;/td&gt;
&lt;td&gt;Motion, expressions, styles, Fast tier&lt;/td&gt;
&lt;td&gt;6–10 s; 1080P only at 6 s&lt;/td&gt;
&lt;td&gt;High-volume social and e-commerce&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wan 2.7&lt;/td&gt;
&lt;td&gt;Video&lt;/td&gt;
&lt;td&gt;Prompt-based video editing, 2–15 s&lt;/td&gt;
&lt;td&gt;Hosted; check which versions have open weights&lt;/td&gt;
&lt;td&gt;Iterating on existing clips&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nano Banana Pro&lt;/td&gt;
&lt;td&gt;Image&lt;/td&gt;
&lt;td&gt;Legible text, up to 4K&lt;/td&gt;
&lt;td&gt;Image only&lt;/td&gt;
&lt;td&gt;Thumbnails, posters, first frames&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT Image 2&lt;/td&gt;
&lt;td&gt;Image&lt;/td&gt;
&lt;td&gt;Fast generation and editing, inpainting&lt;/td&gt;
&lt;td&gt;Image only&lt;/td&gt;
&lt;td&gt;Concepts, mockups, edits&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Who should use what
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Solo creators and YouTubers:&lt;/strong&gt; Start with Veo 3.1 or Kling 3.0 for hero shots, and use Nano Banana Pro for thumbnails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Marketers and agencies:&lt;/strong&gt; Use Kling 3.0 or Seedance 2.0 when you need brand consistency across several shots. Veo 3.1 is a good choice when the client wants 4K.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;E-commerce sellers:&lt;/strong&gt; Hailuo 2.3 Fast and Kling 3.0 are worth testing for product videos at volume. GPT Image 2 or Nano Banana Pro can handle the product stills.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Editors iterating on footage:&lt;/strong&gt; Wan 2.7's prompt-based editing can save full regenerations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most teams end up with &lt;strong&gt;two or three models&lt;/strong&gt;, and the hidden cost is switching between them: separate accounts, separate billing, and different prompt habits.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing several models without juggling subscriptions
&lt;/h2&gt;

&lt;p&gt;The most reliable way to choose is to run &lt;strong&gt;the same prompt and the same reference image&lt;/strong&gt; through a few models, then compare usable clips per dollar rather than looks alone.&lt;/p&gt;

&lt;p&gt;If you'd rather not open several separate accounts, &lt;a href="https://www.sora2hub.org" rel="noopener noreferrer"&gt;Sora2 Hub&lt;/a&gt; is an AI image and video generator that offers many models, including Veo 3.1, Kling, Seedance 2.0, Hailuo, Wan, Nano Banana Pro and GPT Image 2, on &lt;strong&gt;one credit balance&lt;/strong&gt;. That makes side-by-side tests and mixed workflows easier, such as a Nano Banana Pro first frame animated with Kling or Veo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;There's no single best AI video generator. Veo 3.1, Kling 3.0, Seedance 2.0, Hailuo 2.3 and Wan 2.7 each lead on a different job. Test two or three of them on your own prompts and reference images, and build your workflow around the results.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sources: Google Gemini API Veo documentation; Kuaishou/Kling AI 3.0 announcement and model guides; ByteDance Seed Seedance 2.0 launch post; MiniMax Hailuo 2.3 announcement and API docs; Alibaba Cloud Wan2.7-Video announcement; Google DeepMind Nano Banana Pro page; OpenAI GPT-Image-2 model page. Specs were checked in October 2026 and may change.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aivideo</category>
      <category>videogeneration</category>
      <category>veo</category>
      <category>kling</category>
    </item>
    <item>
      <title>Seedance 2.0 vs Kling 3.0 vs Veo 3.1: Which AI Video Model for Which Job</title>
      <dc:creator>Sora2 Hub Team</dc:creator>
      <pubDate>Fri, 09 Oct 2026 12:54:37 +0000</pubDate>
      <link>https://dev.to/sora2hubteam/seedance-20-vs-kling-30-vs-veo-31-which-ai-video-model-for-which-job-5fop</link>
      <guid>https://dev.to/sora2hubteam/seedance-20-vs-kling-30-vs-veo-31-which-ai-video-model-for-which-job-5fop</guid>
      <description>&lt;p&gt;&lt;em&gt;Three leading video models compared on what their makers document: inputs, length, resolution, audio and control. Then matched to real jobs rather than ranked.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quick answer:&lt;/strong&gt; None of the three wins at everything. &lt;strong&gt;Veo 3.1&lt;/strong&gt; (Google) is the strongest choice for short, high-resolution single shots: clips are 4 to 8 seconds with native audio, up to 4K, with first/last-frame control and up to three reference images. &lt;strong&gt;Kling 3.0&lt;/strong&gt; (Kuaishou) is built for short stories: multi-shot clips from 3 to 15 seconds, native audio in several languages, and reusable "elements" that keep a character or product consistent. &lt;strong&gt;Seedance 2.0&lt;/strong&gt; (ByteDance) is the reference-heavy option. It takes text, images, video and audio together (up to 9 images, 3 videos and 3 audio clips, ByteDance says) and outputs multi-shot clips up to 15 seconds. Pick by job, then test on your own material.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why "which is best?" is the wrong question
&lt;/h2&gt;

&lt;p&gt;All three vendors publish impressive demos, and ByteDance and Google both describe strong results on their own evaluations. Those are vendor claims, measured by the vendors. No independent benchmark covers all three on the same prompts. What you &lt;em&gt;can&lt;/em&gt; compare reliably is what each model accepts and produces, because that's documented. Those specs decide which jobs a model can do at all. Quality on your content is something you have to test.&lt;/p&gt;




&lt;h2&gt;
  
  
  Side-by-side specs
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;From each vendor's official documentation, checked October 2026. Plans and API tiers may expose only some of these options.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Veo 3.1&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Kling 3.0&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Seedance 2.0&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Google DeepMind&lt;/td&gt;
&lt;td&gt;Kuaishou (Kling AI)&lt;/td&gt;
&lt;td&gt;ByteDance Seed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Inputs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Text, image, first + last frame, up to 3 reference images, Veo video for extension&lt;/td&gt;
&lt;td&gt;Text, image, start + end frames, element references (images; video in 3.0 / 3.0 Omni)&lt;/td&gt;
&lt;td&gt;Text, image, video and audio together: up to 9 images, 3 videos, 3 audio clips&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Clip length&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4, 6 or 8 s (8 s required at 1080p/4K or with references); extension adds 7 s per step&lt;/td&gt;
&lt;td&gt;3–15 s, flexible&lt;/td&gt;
&lt;td&gt;Up to 15 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-shot in one generation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No (build sequences via extension or editing)&lt;/td&gt;
&lt;td&gt;Yes: automatic or custom multi-shot&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Resolution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;720p, 1080p, 4K (extension at 720p only)&lt;/td&gt;
&lt;td&gt;720P, 1080P, 4K listed for 3.0 and 3.0 Omni in Kling's API capability map&lt;/td&gt;
&lt;td&gt;Not stated in the launch post; check your provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Native audio&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Always on (dialogue, effects, ambience)&lt;/td&gt;
&lt;td&gt;Yes, multilingual, with dialects and accents&lt;/td&gt;
&lt;td&gt;Yes, dual-channel stereo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Editing / extension&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Scene extension of Veo clips&lt;/td&gt;
&lt;td&gt;Separate tools for multi-element editing and lip sync&lt;/td&gt;
&lt;td&gt;Video extension and targeted editing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Aspect ratios&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;16:9, 9:16&lt;/td&gt;
&lt;td&gt;Check your plan or provider&lt;/td&gt;
&lt;td&gt;Check your provider&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Veo 3.1: the single hero shot
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does well.&lt;/strong&gt; Google's Gemini API docs describe Veo 3.1 as generating 8-second videos at 720p, 1080p or 4K with natively generated audio, at 24 fps. You get three kinds of control. Image-to-video animates a starting image. First-and-last-frame generation interpolates between two images you supply. Up to three reference images preserve the look of a person, character or product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to plan around.&lt;/strong&gt; Base clips top out at 8 seconds. Longer pieces come from extension, which in the Gemini API works only on Veo-generated 720p video, adds 7 seconds per step, and requires the source video to have been created or referenced in the last two days. Google notes that higher resolutions mean longer waits and that 4K costs more.&lt;/p&gt;

&lt;p&gt;Google's Gemini API docs now also recommend &lt;strong&gt;Gemini Omni Flash&lt;/strong&gt; as the default video model for many workflows, and point to Veo 3.1 for scene extension, last-frame control and existing pipelines. If you build on Google's API, check both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best jobs:&lt;/strong&gt; cinematic B-roll, hero product shots, short ads where one beautiful, well-lit 8-second shot with synced sound is the deliverable, and anything that needs 4K.&lt;/p&gt;




&lt;h2&gt;
  
  
  Kling 3.0: short stories with consistent characters
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does well.&lt;/strong&gt; Kling's model guide describes Video 3.0 as combining native audio, element consistency and multi-shot storytelling. You can let the model plan shots automatically, or use &lt;strong&gt;Custom Multi-Shot&lt;/strong&gt; to set each shot's duration, framing, angle and camera movement. Clips run from 3 to 15 seconds. Native audio supports Chinese, English, Japanese, Korean and Spanish, plus dialects and accents.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Element Library&lt;/strong&gt; is the key feature for brand work. An element is a reusable asset built from 2–4 reference images. In 3.0 Omni, it can also be built from a short character video and carry a bound voice, so the same character or product looks and sounds consistent across generations. Kuaishou also points to better preservation of on-screen text such as signage and logos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to plan around.&lt;/strong&gt; Multi-shot prompts take more planning than one-line prompts. Kling lists several variants (3.0 Turbo, 3.0, 3.0 Omni) with different capabilities. Turbo, for example, doesn't support element control in the capability map, so check which variant your plan or provider uses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best jobs:&lt;/strong&gt; narrative social content, dialogue scenes, recurring characters or mascots, and multi-angle product ads in a single generation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Seedance 2.0: when you have references to follow
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does well.&lt;/strong&gt; ByteDance describes Seedance 2.0 as a unified multimodal audio-video model that accepts four input types at once. Its launch post says you can combine up to 9 images, 3 video clips and 3 audio clips with a text instruction. The model can take composition, camera movement, motion rhythm, visual effects and sound from those references, and even follow a storyboard image. It outputs multi-shot clips up to 15 seconds with stereo audio, and supports video extension and targeted editing of clips, characters and actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to plan around.&lt;/strong&gt; It works best when you bring assets. A text-only prompt doesn't use its main strength. ByteDance's own launch post lists remaining weaknesses: detail stability, multi-subject consistency, text rendering accuracy and occasional audio distortion. ByteDance also notes that using real people's portraits as references requires identity verification or authorization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best jobs:&lt;/strong&gt; music-synced edits, recreating a camera move or choreography from a reference clip, storyboard-to-video, and outfit or product showcases built from several angles.&lt;/p&gt;




&lt;h2&gt;
  
  
  Which model for which job
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;First choice&lt;/th&gt;
&lt;th&gt;Also test&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;8-second hero shot for a brand film, 4K delivery&lt;/td&gt;
&lt;td&gt;Veo 3.1&lt;/td&gt;
&lt;td&gt;Kling 3.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15-second social ad with three different shots&lt;/td&gt;
&lt;td&gt;Kling 3.0&lt;/td&gt;
&lt;td&gt;Seedance 2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recurring mascot or presenter across a series&lt;/td&gt;
&lt;td&gt;Kling 3.0 (elements)&lt;/td&gt;
&lt;td&gt;Veo 3.1 (reference images)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy the camera move from a reference video&lt;/td&gt;
&lt;td&gt;Seedance 2.0&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edit cut to an existing music track&lt;/td&gt;
&lt;td&gt;Seedance 2.0&lt;/td&gt;
&lt;td&gt;Kling 3.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product photo → simple animated packshot&lt;/td&gt;
&lt;td&gt;Veo 3.1 or Kling 3.0&lt;/td&gt;
&lt;td&gt;Seedance 2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dialogue in Japanese, Korean or Spanish&lt;/td&gt;
&lt;td&gt;Kling 3.0&lt;/td&gt;
&lt;td&gt;Veo 3.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turn a storyboard image into a sequence&lt;/td&gt;
&lt;td&gt;Seedance 2.0&lt;/td&gt;
&lt;td&gt;Kling 3.0 (custom multi-shot)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  How to run a fair test
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fix the inputs.&lt;/strong&gt; Use the same reference image, the same prompt (adjusted only for syntax), and the same aspect ratio and duration where possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate several takes per model.&lt;/strong&gt; One sample tells you very little.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Score usable clips.&lt;/strong&gt; Count a clip as usable only if it needs no regeneration: correct product, stable faces, believable physics, clean audio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track cost per usable clip.&lt;/strong&gt; Prices differ by model, resolution and duration, so the cheapest generation isn't always the cheapest result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repeat in a month.&lt;/strong&gt; All three vendors ship updates frequently.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Running a test like this is easier from one account. &lt;a href="https://www.sora2hub.org" rel="noopener noreferrer"&gt;Sora2 Hub&lt;/a&gt; is a credit-based multi-model studio with Seedance 2.0, Kling 3.0 and Veo 3.1 (plus Hailuo, Wan and image models such as Nano Banana Pro and GPT Image 2) on a single credit balance. You can send the same prompt to all three and compare results without three subscriptions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;Choose &lt;strong&gt;Veo 3.1&lt;/strong&gt; for polished single shots and 4K, &lt;strong&gt;Kling 3.0&lt;/strong&gt; for multi-shot stories and consistent characters, and &lt;strong&gt;Seedance 2.0&lt;/strong&gt; when your references (images, video, music) should drive the result. Many teams keep two of the three and switch per brief.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sources: Google Gemini API documentation (Veo 3.1 and video generation overview); Kling VIDEO 3.0 model guide, Kling Element Library guide and Kling API video capability map; ByteDance Seed "Seedance 2.0 Official Launch" (Feb 12, 2026). Checked October 2026. Capabilities vary by plan and provider, so confirm before production.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aivideo</category>
      <category>aitools</category>
      <category>tfhdailystandup</category>
    </item>
    <item>
      <title>How to Build a SaaS MVP with Next.js, Supabase and Stripe Using an AI Agent</title>
      <dc:creator>Sora2 Hub Team</dc:creator>
      <pubDate>Fri, 09 Oct 2026 12:48:50 +0000</pubDate>
      <link>https://dev.to/sora2hubteam/how-to-build-a-saas-mvp-with-nextjs-supabase-and-stripe-using-an-ai-agent-4hj9</link>
      <guid>https://dev.to/sora2hubteam/how-to-build-a-saas-mvp-with-nextjs-supabase-and-stripe-using-an-ai-agent-4hj9</guid>
      <description>&lt;p&gt;&lt;em&gt;A practical plan for using an AI coding agent to scaffold a subscription SaaS, plus the auth, database and billing details you still need to check yourself.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quick answer:&lt;/strong&gt; Write a one-page spec first: users, the one core workflow, the data tables and the paid plan. Have an AI agent scaffold the Next.js app with Supabase auth and a Stripe subscription flow, then review four areas yourself. (1) Server code checks the user with &lt;code&gt;supabase.auth.getClaims()&lt;/code&gt; (or &lt;code&gt;getUser()&lt;/code&gt;), never &lt;code&gt;getSession()&lt;/code&gt;. (2) Row Level Security is enabled on every table in an exposed schema. (3) The Stripe webhook verifies signatures against the raw request body and handles duplicate and out-of-order events. (4) Secret keys never reach the browser. Then deploy to Vercel with separate test and live environment variables.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 1: Scope the MVP before you prompt
&lt;/h2&gt;

&lt;p&gt;AI agents are fast, so scope creep is cheap to start and expensive to maintain. Write down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Who signs up:&lt;/strong&gt; individuals, or teams with multiple members?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The one workflow worth paying for:&lt;/strong&gt; for example, "upload a CSV, get a cleaned report, download it".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data:&lt;/strong&gt; three to six tables at most, such as &lt;code&gt;profiles&lt;/code&gt;, &lt;code&gt;projects&lt;/code&gt;, &lt;code&gt;reports&lt;/code&gt;, &lt;code&gt;subscriptions&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plans:&lt;/strong&gt; one free tier and one paid tier is enough to test willingness to pay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Out of scope:&lt;/strong&gt; admin panels, teams, usage-based billing, i18n. Add them after real users ask.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This spec becomes your first prompt.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Write a prompt the agent can execute
&lt;/h2&gt;

&lt;p&gt;Name the stack, the pages and the rules. Leave visual polish for later. For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build a Next.js App Router app in TypeScript with Tailwind. Use Supabase for email/password and magic-link auth and for the database. Tables: &lt;code&gt;profiles&lt;/code&gt; (id = auth user id), &lt;code&gt;projects&lt;/code&gt; (owner_id, name, created_at), &lt;code&gt;subscriptions&lt;/code&gt; (user_id, stripe_customer_id, stripe_subscription_id, status, price_id, current_period_end). Enable RLS on all tables so users can only read and write their own rows. Pages: landing, pricing, login, dashboard (list and create projects), settings (manage billing). Use Stripe Checkout in subscription mode for one "Pro" monthly price, and the Stripe customer portal for plan changes and cancellation. Add a webhook route that verifies the Stripe signature and updates &lt;code&gt;subscriptions&lt;/code&gt;. Gate project creation beyond 3 projects behind an active subscription, checked on the server.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ask the agent to &lt;strong&gt;plan first&lt;/strong&gt; and show you the file structure and SQL migration before writing everything. Fixing a plan is much cheaper than fixing code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Review authentication
&lt;/h2&gt;

&lt;p&gt;Supabase's Next.js server-side auth guide uses the &lt;code&gt;@supabase/ssr&lt;/code&gt; package with cookie-based sessions. Two things to check in what the agent generates:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Verify the user on the server properly.&lt;/strong&gt; Supabase's docs say to use &lt;code&gt;supabase.auth.getClaims()&lt;/code&gt; to protect pages and user data. It verifies the access token's signature. You can also call &lt;code&gt;getUser()&lt;/code&gt; for a fresh, server-confirmed user record. The docs also say &lt;em&gt;never&lt;/em&gt; to trust &lt;code&gt;supabase.auth.getSession()&lt;/code&gt; in server code, because it reads the session from a cookie without revalidating it, and cookies can be forged.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session refresh runs in middleware.&lt;/strong&gt; The Supabase guide refreshes the auth token in middleware (Next.js 16 renamed middleware to "Proxy"). In Next.js 15, &lt;code&gt;cookies()&lt;/code&gt; is async, so the server client is created with &lt;code&gt;await&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Also check that protected routes redirect anonymous users on the server, not only in client components.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Review the database and Row Level Security
&lt;/h2&gt;

&lt;p&gt;This is the most important review step. Supabase's RLS guide is blunt: a table in an exposed schema without RLS is readable and writable by any role with a grant on it. Enable RLS on every table in an exposed schema.&lt;/p&gt;

&lt;p&gt;Check the generated migration for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;alter table ... enable row level security;&lt;/code&gt; on &lt;strong&gt;every&lt;/strong&gt; table, including tables added later.&lt;/li&gt;
&lt;li&gt;Policies scoped to &lt;code&gt;auth.uid()&lt;/code&gt;: for example, &lt;code&gt;projects&lt;/code&gt; rows where &lt;code&gt;owner_id = auth.uid()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Views:&lt;/strong&gt; Supabase notes that views bypass RLS by default because they are usually created by the &lt;code&gt;postgres&lt;/code&gt; user. Don't expose a view over a protected table unless it's made safe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secret key usage:&lt;/strong&gt; the secret (service role) key bypasses RLS. Supabase says never to use it in the browser. Use it only in server code such as the webhook handler.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then test as a real user: create two accounts and confirm neither can see the other's data through the API.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Review Stripe billing
&lt;/h2&gt;

&lt;p&gt;The standard flow is: &lt;strong&gt;Checkout&lt;/strong&gt; (subscription mode) → &lt;strong&gt;webhook&lt;/strong&gt; updates your &lt;code&gt;subscriptions&lt;/code&gt; table → your app reads that table to grant access → the &lt;strong&gt;customer portal&lt;/strong&gt; handles upgrades and cancellation.&lt;/p&gt;

&lt;p&gt;Stripe's webhook documentation covers what agents most often get wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Verify every event.&lt;/strong&gt; Use the &lt;code&gt;Stripe-Signature&lt;/code&gt; header and your &lt;code&gt;whsec_&lt;/code&gt; signing secret. Without verification, Stripe warns, attackers could send fake events to grant access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use the raw body.&lt;/strong&gt; Stripe requires the unmodified raw request body for signature verification. In a Next.js route handler, read it with &lt;code&gt;await request.text()&lt;/code&gt; and don't parse JSON first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Return 2xx quickly&lt;/strong&gt;, before slow work, to avoid timeouts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handle duplicates.&lt;/strong&gt; Endpoints can receive the same event more than once, so log processed event IDs and skip repeats.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't assume order.&lt;/strong&gt; Stripe doesn't guarantee event order. Fetch the current subscription from the API when in doubt, rather than trusting the sequence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For subscriptions, Stripe's docs describe &lt;code&gt;customer.subscription.created&lt;/code&gt;, &lt;code&gt;.updated&lt;/code&gt; (renewals, plan changes, discounts) and &lt;code&gt;.deleted&lt;/code&gt; (subscription ends). On &lt;code&gt;invoice.paid&lt;/code&gt;, Stripe recommends confirming the subscription status is &lt;code&gt;active&lt;/code&gt; before extending access. Handle &lt;code&gt;invoice.payment_failed&lt;/code&gt; too, so access lapses cleanly.&lt;/p&gt;

&lt;p&gt;To test locally, run &lt;code&gt;stripe listen --forward-to localhost:3000/api/webhooks/stripe&lt;/code&gt; (adjust to your route). The CLI prints a signing secret for local use.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Deploy
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Push the code to GitHub.&lt;/li&gt;
&lt;li&gt;Import the repo into Vercel and set environment variables: Supabase URL and publishable key (public), Supabase secret key and Stripe secret and webhook secret (server-only, no &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; prefix).&lt;/li&gt;
&lt;li&gt;Create a &lt;strong&gt;live-mode&lt;/strong&gt; webhook endpoint in Stripe pointing at your production URL, and use its own signing secret.&lt;/li&gt;
&lt;li&gt;Add your production URL to Supabase Auth's redirect settings so magic links and OAuth return to the right domain.&lt;/li&gt;
&lt;li&gt;Run one real subscription with a live card, then cancel it through the portal and confirm access is removed.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Where an AI agent helps, and where it doesn't
&lt;/h2&gt;

&lt;p&gt;An agent removes most of the boilerplate: routing, forms, dashboard UI, the Supabase client setup, the migration skeleton and the Checkout and portal routes. It doesn't remove your responsibility for &lt;strong&gt;security and money&lt;/strong&gt;. RLS policies, webhook verification and server-side access checks are where a plausible-looking bug costs real data or revenue. Read those files line by line.&lt;/p&gt;

&lt;p&gt;This is the workflow &lt;a href="https://massvai.com" rel="noopener noreferrer"&gt;Massvai&lt;/a&gt; is built around. Its AI coding agent turns a prompt into a full-stack Next.js 15 + TypeScript app with Supabase and Stripe in the stack, shows the build in a live preview, syncs the repo to GitHub, and deploys to your own Vercel project through a guided flow. You can export every file, so you can run the review checklist below in your own editor.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pre-launch checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Server code uses &lt;code&gt;getClaims()&lt;/code&gt; / &lt;code&gt;getUser()&lt;/code&gt;, never &lt;code&gt;getSession()&lt;/code&gt;, for access decisions&lt;/li&gt;
&lt;li&gt;[ ] RLS enabled on every exposed table; policies tested with two accounts&lt;/li&gt;
&lt;li&gt;[ ] No views exposing protected tables&lt;/li&gt;
&lt;li&gt;[ ] Supabase secret key and Stripe secrets are server-only&lt;/li&gt;
&lt;li&gt;[ ] Webhook verifies signatures against the raw body&lt;/li&gt;
&lt;li&gt;[ ] Duplicate events skipped by event ID; no dependence on event order&lt;/li&gt;
&lt;li&gt;[ ] Access granted only for &lt;code&gt;active&lt;/code&gt; subscriptions; failed payments handled&lt;/li&gt;
&lt;li&gt;[ ] Customer portal linked from settings&lt;/li&gt;
&lt;li&gt;[ ] Separate test and live keys, webhooks and redirect URLs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Sources: Supabase docs ("Setting up Server-Side Auth for Next.js", "Row Level Security"); Stripe docs ("Receive Stripe events in your webhook endpoint", "Using webhooks with subscriptions"); Next.js docs (Proxy file convention, version history); Massvai homepage. Checked October 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>supabase</category>
      <category>stripe</category>
      <category>saas</category>
    </item>
  </channel>
</rss>
