<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Quillpost]]></title><description><![CDATA[Quillpost]]></description><link>https://quillpost.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Fri, 23 Oct 2026 12:51:53 GMT</lastBuildDate><atom:link href="https://quillpost.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[3 LinkedIn writing guides for founders (hooks, structure, when to skip)]]></title><description><![CDATA[Three short guides just went live on the Quillpost site. They are about writing on LinkedIn, not about an AI that posts for you. Quillpost is a free…]]></description><link>https://quillpost.hashnode.dev/3-linkedin-writing-guides-for-founders-hooks-structure-when-to-skip</link><guid isPermaLink="true">https://quillpost.hashnode.dev/3-linkedin-writing-guides-for-founders-hooks-structure-when-to-skip</guid><dc:creator><![CDATA[Quillpost]]></dc:creator><pubDate>Tue, 20 Oct 2026 05:57:07 GMT</pubDate><content:encoded><![CDATA[<p>Three short guides just went live on the Quillpost site. They are about writing on LinkedIn, not about an AI that posts for you.</p>
<ul>
<li><a href="https://quillpost-liart.vercel.app/blog/how-to-write-linkedin-hooks-that-get-reads">How to write LinkedIn hooks that get reads</a></li>
<li><a href="https://quillpost-liart.vercel.app/blog/linkedin-post-structure-for-busy-founders">LinkedIn post structure for busy founders</a></li>
<li><a href="https://quillpost-liart.vercel.app/blog/when-not-to-post-on-linkedin">When not to post on LinkedIn</a></li>
</ul>
<p>Quillpost is a free LinkedIn draft generator. It is drafts-only: you get up to 3 drafts a day, and it never posts to LinkedIn for you. Pro is not live. There are zero users so far.</p>
<p>The product site: <a href="https://quillpost-liart.vercel.app">https://quillpost-liart.vercel.app</a></p>
]]></content:encoded></item><item><title><![CDATA[Supabase + Next.js 16: authorize with getClaims(), not getSession()]]></title><description><![CDATA[getSession() is unsafe for server authz in Supabase + Next.js. Use getClaims() instead. Free Loadout rules pack.]]></description><link>https://quillpost.hashnode.dev/supabase-next-js-16-authorize-with-getclaims-not-getsession</link><guid isPermaLink="true">https://quillpost.hashnode.dev/supabase-next-js-16-authorize-with-getclaims-not-getsession</guid><category><![CDATA[Next.js]]></category><category><![CDATA[AI]]></category><category><![CDATA[webdev]]></category><category><![CDATA[supabase]]></category><dc:creator><![CDATA[Quillpost]]></dc:creator><pubDate>Tue, 20 Oct 2026 02:12:47 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p><strong>AI disclosure:</strong> I wrote this article with AI assistance. I checked every claim and code sample against the Supabase and Next.js docs. If you spot a mistake, tell me in the comments and I'll fix it.</p>
</blockquote>
<p>If you let an AI agent write the auth code in a Supabase + Next.js app, check one thing first: <strong>what your server code trusts</strong>.</p>
<p>Agents (and plenty of older tutorials) still produce this in Server Components, Server Actions, and route handlers:</p>
<pre><code class="language-ts">const { data: { session } } = await supabase.auth.getSession()
if (!session) redirect('/login')
const userId = session.user.id // used to decide what this request can read or write
</code></pre>
<p>It looks fine and it works in testing. The problem is where the data comes from.</p>
<h2>Why getSession() is the wrong check on the server</h2>
<p>On the server, <code>getSession()</code> loads the session from the auth cookie <strong>without revalidating it</strong>. A cookie is user input. The Supabase SSR guide says so directly: anyone can forge the session cookie, so never trust <code>getSession()</code> inside server code.</p>
<p>If <code>session.user.id</code> decides which rows a request can read or write, a forged cookie decides it too.</p>
<h2>Use getClaims() to protect pages and data</h2>
<p><code>getClaims()</code> verifies the access token's signature before it returns the claims:</p>
<ul>
<li>On projects with <strong>asymmetric JWT signing keys</strong> (ECC or RSA, the default for new projects), it verifies against your project's JWKS endpoint (<code>/.well-known/jwks.json</code>). The keys are usually cached, so this is fast.</li>
<li>On projects still using a <strong>symmetric</strong> key, it sends a request to the Auth server to verify the token, similar to <code>getUser()</code>.</li>
</ul>
<p>A protected Server Component:</p>
<pre><code class="language-tsx">// app/dashboard/page.tsx
import { redirect } from 'next/navigation'
import { createClient } from '@/lib/supabase/server'

export default async function DashboardPage() {
  const supabase = await createClient()
  const { data, error } = await supabase.auth.getClaims()
  if (error || !data?.claims) redirect('/login')
  const userId = data.claims.sub // verified subject (the user's id)
  const { data: projects } = await supabase
    .from('projects')
    .select('id, name')
    .eq('owner_id', userId)
  // keep RLS on as well; this filter is not your only lock
  return &lt;ProjectList projects={projects ?? []} /&gt;
}
</code></pre>
<h2>Which call to use when</h2>
<table>
<thead>
<tr>
<th>You need</th>
<th>Use</th>
</tr>
</thead>
<tbody><tr>
<td>To protect a page, Server Action, route handler, or query</td>
<td><code>getClaims()</code></td>
</tr>
<tr>
<td>An up-to-date user record from the Auth server</td>
<td><code>getUser()</code> (makes a network call)</td>
</tr>
<tr>
<td>The raw access or refresh token, e.g. to forward to another service</td>
<td><code>getSession()</code>, <strong>never for authorization</strong></td>
</tr>
</tbody></table>
<h2>Server Actions are public endpoints</h2>
<p>A Server Action can be called over HTTP even if no button in your UI calls it. Treat each one like an API route: validate input, verify claims, then write.</p>
<h2>Free rules pack</h2>
<p>If you want the agent to stop inventing the old pattern, the free Next.js + Supabase Loadout pack includes this as a pinned rule:</p>
<p><a href="https://loadout-blush.vercel.app/packs/nextjs-supabase">https://loadout-blush.vercel.app/packs/nextjs-supabase</a></p>
<p>Canonical fix page (originally published here):</p>
<p><a href="https://loadout-blush.vercel.app/fixes/supabase-ssr-getsession-vs-getclaims">https://loadout-blush.vercel.app/fixes/supabase-ssr-getsession-vs-getclaims</a></p>
<p>Also on DEV: <a href="https://dev.to/anthonypan99/supabase-nextjs-16-authorize-with-getclaims-not-getsession-4423">https://dev.to/anthonypan99/supabase-nextjs-16-authorize-with-getclaims-not-getsession-4423</a></p>
]]></content:encoded></item><item><title><![CDATA[Quillpost: LinkedIn drafts that sound like you (free, drafts-only)]]></title><description><![CDATA[Quillpost turns posts you have already written into new LinkedIn drafts that sound like you. How it works What is live today Try it:]]></description><link>https://quillpost.hashnode.dev/quillpost-linkedin-drafts-that-sound-like-you-free-drafts-only</link><guid isPermaLink="true">https://quillpost.hashnode.dev/quillpost-linkedin-drafts-that-sound-like-you-free-drafts-only</guid><dc:creator><![CDATA[Quillpost]]></dc:creator><pubDate>Mon, 19 Oct 2026 23:24:38 GMT</pubDate><content:encoded><![CDATA[<p>Quillpost turns posts you have already written into new LinkedIn drafts that sound like you.</p>
<p>How it works</p>
<ul>
<li>Paste past posts.</li>
<li>Quillpost drafts new posts in your voice.</li>
<li>It never posts for you. You review every draft and publish it yourself.</li>
</ul>
<p>What is live today</p>
<ul>
<li>Free: 3 drafts a day.</li>
<li>Pro is coming soon. It is not live, and there is nothing to buy.</li>
<li>There are zero users yet. This is the launch.</li>
</ul>
<p>Try it: <a href="https://quillpost-liart.vercel.app">https://quillpost-liart.vercel.app</a></p>
]]></content:encoded></item></channel></rss>