<?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: Mahdi BEN RHOUMA</title>
    <description>The latest articles on DEV Community by Mahdi BEN RHOUMA (@mahdi_benrhouma_fe1c6005).</description>
    <link>https://dev.to/mahdi_benrhouma_fe1c6005</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%2F3623634%2F405ea568-6e7f-4cab-923a-240f9a44bf72.png</url>
      <title>DEV Community: Mahdi BEN RHOUMA</title>
      <link>https://dev.to/mahdi_benrhouma_fe1c6005</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mahdi_benrhouma_fe1c6005"/>
    <language>en</language>
    <item>
      <title>Next.js 16 Removed next lint: ESLint 9 Migration</title>
      <dc:creator>Mahdi BEN RHOUMA</dc:creator>
      <pubDate>Sat, 25 Jul 2026 21:47:02 +0000</pubDate>
      <link>https://dev.to/mahdi_benrhouma_fe1c6005/nextjs-16-removed-next-lint-eslint-9-migration-390n</link>
      <guid>https://dev.to/mahdi_benrhouma_fe1c6005/nextjs-16-removed-next-lint-eslint-9-migration-390n</guid>
      <description>&lt;h2&gt;
  
  
  What actually broke in Next.js 16
&lt;/h2&gt;

&lt;p&gt;Three things disappeared at once, and each one silently changes how your&lt;br&gt;
project behaves in CI:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;next lint&lt;/code&gt; is removed.&lt;/strong&gt; Running &lt;code&gt;npx next lint&lt;/code&gt; in Next.js 16 throws
&lt;code&gt;Unknown command: lint&lt;/code&gt;. If your &lt;code&gt;package.json&lt;/code&gt; has &lt;code&gt;"lint": "next lint"&lt;/code&gt;,
that script is now dead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;eslint&lt;/code&gt; next.config key is removed.&lt;/strong&gt; &lt;code&gt;eslint: { ignoreDuringBuilds: true }&lt;/code&gt;
in &lt;code&gt;next.config.mjs&lt;/code&gt; is silently ignored — and so is &lt;code&gt;eslint: { dirs: ['lib'] }&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;next build&lt;/code&gt; no longer lints.&lt;/strong&gt; Pre-16, a production build ran an ESLint
pass and could fail the build on lint errors. That pass is gone. A build
that used to turn red on a lint error now turns green.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a migration, not a regression. The Next.js team moved linting to the&lt;br&gt;
ESLint CLI (or Biome) because &lt;code&gt;next lint&lt;/code&gt; was a thin wrapper around ESLint&lt;br&gt;
that lagged behind ESLint releases — the original incompatibility with&lt;br&gt;
ESLint 9 was tracked for months in &lt;a href="https://github.com/vercel/next.js/issues/64409" rel="noopener noreferrer"&gt;vercel/next.js#64409&lt;/a&gt; before&lt;br&gt;
being fixed, and the team decided to retire the wrapper entirely in v16.&lt;/p&gt;

&lt;p&gt;The upgrade guide is the canonical source for the change; the flat-config&lt;br&gt;
default landed in &lt;a href="https://github.com/vercel/next.js/pull/84874" rel="noopener noreferrer"&gt;PR #84874&lt;/a&gt;, and the migration codemod shipped in&lt;br&gt;
&lt;a href="https://github.com/vercel/next.js/commit/c57fe2561cda36f8a5abb41a94ea1a3f0a094226" rel="noopener noreferrer"&gt;commit c57fe25&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  The 30-second path: run the official codemod
&lt;/h2&gt;

&lt;p&gt;Next.js ships a codemod that does the mechanical part for you. From your&lt;br&gt;
project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @next/codemod@canary next-lint-to-eslint-cli &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It does four things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rewrites &lt;code&gt;package.json&lt;/code&gt; scripts.&lt;/strong&gt; &lt;code&gt;"lint": "next lint"&lt;/code&gt; becomes
&lt;code&gt;"lint": "eslint ."&lt;/code&gt;; &lt;code&gt;--strict&lt;/code&gt; maps to &lt;code&gt;--max-warnings 0&lt;/code&gt;; &lt;code&gt;--dir&lt;/code&gt; /
&lt;code&gt;--file&lt;/code&gt; flags map to explicit paths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creates &lt;code&gt;eslint.config.mjs&lt;/code&gt;&lt;/strong&gt; with the Next.js recommended flat config
if you do not have one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Updates an existing flat config&lt;/strong&gt; to include the Next.js rules if it
does not already.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Installs the dependencies&lt;/strong&gt; for you: &lt;code&gt;eslint&lt;/code&gt;, &lt;code&gt;eslint-config-next&lt;/code&gt;,
&lt;code&gt;@eslint/eslintrc&lt;/code&gt; (the compatibility shim you will need if any plugin in
your tree still ships legacy config).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Run it, then run &lt;code&gt;npm run lint&lt;/code&gt; once to confirm the output is the same shape&lt;br&gt;
as before. That is the happy path. The rest of this article is the unhappy&lt;br&gt;
path — the parts the codemod cannot fix automatically.&lt;/p&gt;


  &lt;img src="/article-assets/proofs/nextjs-16-next-lint-removed-eslint-flat-config/terminal-codemod.png" alt="Illustration: terminal output of the @next/codemod next-lint-to-eslint-cli run, showing the four file mutations (eslint.config.mjs created, .eslintrc.json removed, package.json scripts updated, reminder that next build no longer runs linting). Verbatim text sourced from the official Next.js 16 upgrade guide and the @next/codemod README."&gt;&lt;strong&gt;Illustration — not a live capture.&lt;/strong&gt; Verbatim text sourced from the official Next.js 16 upgrade guide and the &lt;code&gt;@next/codemod&lt;/code&gt; README. The orange banner inside the image is the renderer-level "not a live capture" badge.
  

&lt;h2&gt;
  
  
  Writing &lt;code&gt;eslint.config.mjs&lt;/code&gt; by hand
&lt;/h2&gt;

&lt;p&gt;If the codemod does not cover your setup (monorepo, custom rules, Prettier&lt;br&gt;
chain), here is the minimal flat config that reproduces what&lt;br&gt;
&lt;code&gt;eslint-config-next&lt;/code&gt; used to give you in legacy &lt;code&gt;.eslintrc.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// eslint.config.mjs&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;coreWebVitals&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;eslint-config-next/core-web-vitals&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;typescript&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;eslint-config-next/typescript&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eslintConfig&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="nx"&gt;coreWebVitals&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;typescript&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ignores&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/**&lt;/span&gt;&lt;span class="dl"&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;out/**&lt;/span&gt;&lt;span class="dl"&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;build/**&lt;/span&gt;&lt;span class="dl"&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-env.d.ts&lt;/span&gt;&lt;span class="dl"&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;node_modules/**&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;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;eslintConfig&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="/article-assets/proofs/nextjs-16-next-lint-removed-eslint-flat-config/eslint-config-diff.png" class="article-body-image-wrapper"&gt;&lt;img src="/article-assets/proofs/nextjs-16-next-lint-removed-eslint-flat-config/eslint-config-diff.png" alt="Illustration: diff from the legacy .eslintrc.json (extends next/core-web-vitals and next/typescript) to the flat eslint.config.mjs that imports eslint-config-next/core-web-vitals and eslint-config-next/typescript. Example only — text quoted from the official Next.js ESLint plugin API reference and the upgrade guide."&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;&lt;strong&gt;Illustration — not a live capture.&lt;/strong&gt; Example diff; the wording is quoted from the official Next.js ESLint plugin API reference and the upgrade guide. The orange banner inside the image is the renderer-level "not a live capture" badge.
  &lt;p&gt;&lt;/p&gt;

&lt;p&gt;Two subpath exports matter, both confirmed in &lt;a href="https://github.com/vercel/next.js/pull/84874" rel="noopener noreferrer"&gt;PR #84874&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;eslint-config-next/core-web-vitals&lt;/code&gt; — the core-web-vitals ruleset (the
one &lt;code&gt;next lint&lt;/code&gt; applied by default).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;eslint-config-next/typescript&lt;/code&gt; — TypeScript-specific rules; add it only
if your project uses TypeScript.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The spread is intentional: each export is an array of config objects, so you&lt;br&gt;
concatenate them into one flat config array. Do not wrap them in&lt;br&gt;
&lt;code&gt;defineConfig([...])&lt;/code&gt; and then nest again — ESLint will warn about&lt;br&gt;
&lt;code&gt;configFactory&lt;/code&gt; returning an array.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why not &lt;code&gt;defineConfig&lt;/code&gt; and &lt;code&gt;globalIgnores&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;You will see tutorials that import &lt;code&gt;defineConfig&lt;/code&gt; and &lt;code&gt;globalIgnores&lt;/code&gt; from&lt;br&gt;
&lt;code&gt;eslint/config&lt;/code&gt;. That works, but it is sugar over the array form. The&lt;br&gt;
&lt;code&gt;@next/eslint-plugin-next&lt;/code&gt; README and the PR both use the plain-array form,&lt;br&gt;
which is what &lt;code&gt;eslint-config-next&lt;/code&gt; itself exports. Stick to the plain form&lt;br&gt;
and you will not be surprised when a plugin you add does not understand&lt;br&gt;
&lt;code&gt;defineConfig&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;code&gt;package.json&lt;/code&gt; scripts that replace &lt;code&gt;next lint&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The codemod writes these for you, but if you are setting them up manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"next dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm run lint &amp;amp;&amp;amp; next build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"next start"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eslint ."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint:fix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eslint --fix ."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details that bite people:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--max-warnings 0&lt;/code&gt; replaces &lt;code&gt;--strict&lt;/code&gt;.&lt;/strong&gt; The old &lt;code&gt;next lint --strict&lt;/code&gt;
flag turned warnings into errors. The ESLint CLI equivalent is
&lt;code&gt;eslint . --max-warnings 0&lt;/code&gt;. Put it in your &lt;code&gt;lint&lt;/code&gt; script if you want CI to
fail on warnings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;next build&lt;/code&gt; no longer lints, so add lint to the build script&lt;/strong&gt; if your
CI relied on the build to catch lint errors. &lt;code&gt;npm run lint &amp;amp;&amp;amp; next build&lt;/code&gt;
is the cheapest way to preserve the old behavior without a separate CI
step.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Migrating from a legacy &lt;code&gt;.eslintrc.json&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;If you had a hand-written &lt;code&gt;.eslintrc.json&lt;/code&gt; that extended &lt;code&gt;next/core-web-vitals&lt;/code&gt;&lt;br&gt;
and added custom rules, the codemod will not perfectly translate your custom&lt;br&gt;
rules. The ESLint project publishes its own migration tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @eslint/migrate-config .eslintrc.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It emits a starter &lt;code&gt;eslint.config.mjs&lt;/code&gt; that wraps your old config with&lt;br&gt;
&lt;code&gt;FlatCompat&lt;/code&gt; from &lt;code&gt;@eslint/eslintrc&lt;/code&gt;. The output looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// eslint.config.mjs — generated by @eslint/migrate-config&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;FlatCompat&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;@eslint/eslintrc&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;fileURLToPath&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;node:url&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;dirname&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;node:path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;__filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fileURLToPath&lt;/span&gt;&lt;span class="p"&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;meta&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;__dirname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__filename&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;compat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FlatCompat&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;baseDirectory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;__dirname&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;eslintConfig&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="nx"&gt;compat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extends&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/core-web-vitals&lt;/span&gt;&lt;span class="dl"&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/typescript&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;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// your old custom rules land here&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-console&lt;/span&gt;&lt;span class="dl"&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;warn&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;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;eslintConfig&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;FlatCompat&lt;/code&gt; is the bridge: it lets a flat config consume plugins and&lt;br&gt;
shareable configs that still ship the legacy &lt;code&gt;.eslintrc&lt;/code&gt;-style export. Use&lt;br&gt;
it for any third-party plugin that has not migrated yet — for example, many&lt;br&gt;
&lt;code&gt;eslint-plugin-*&lt;/code&gt; packages on npm still export a legacy config object. Once&lt;br&gt;
the plugin ships a flat config, you can drop the compat layer and import it&lt;br&gt;
directly.&lt;/p&gt;
&lt;h2&gt;
  
  
  The &lt;code&gt;.eslintrc&lt;/code&gt; plugins that have not migrated
&lt;/h2&gt;

&lt;p&gt;This is the part the upgrade guide skips. If your &lt;code&gt;.eslintrc.json&lt;/code&gt; extended&lt;br&gt;
&lt;code&gt;plugin:prettier/recommended&lt;/code&gt; or any plugin that has not published a flat&lt;br&gt;
config export, ESLint 9 will throw &lt;code&gt;Failed to load config "prettier"&lt;/code&gt; at&lt;br&gt;
startup. Two fixes, in order of preference:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use the plugin's flat config export if it has one.&lt;/strong&gt; Prettier, for
instance, ships &lt;code&gt;eslint-config-prettier/flatconfig&lt;/code&gt; since v9.1.0. Import
it directly and spread it last in your array.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrap with &lt;code&gt;FlatCompat&lt;/code&gt; if it does not.&lt;/strong&gt; &lt;code&gt;compat.extends('plugin:prettier/recommended')&lt;/code&gt;
still works because &lt;code&gt;@eslint/eslintrc&lt;/code&gt; knows how to translate the legacy
format.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The codemod installs &lt;code&gt;@eslint/eslintrc&lt;/code&gt; for exactly this reason. Keep it in&lt;br&gt;
your devDependencies until every plugin in your tree has migrated; then you&lt;br&gt;
can remove it.&lt;/p&gt;
&lt;h2&gt;
  
  
  CI/CD: where the silent break lives
&lt;/h2&gt;

&lt;p&gt;This is the highest-impact section. Pre-16, a typical GitHub Actions job&lt;br&gt;
looked like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;next build&lt;/code&gt; ran ESLint, and a lint error failed the job. Post-16, the same&lt;br&gt;
job is green on the same codebase even if it is full of lint errors,&lt;br&gt;
because &lt;code&gt;next build&lt;/code&gt; no longer lints. Your lint coverage silently drops to&lt;br&gt;
zero the day you upgrade.&lt;/p&gt;

&lt;p&gt;Fix it by adding an explicit lint step &lt;strong&gt;before&lt;/strong&gt; the build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ci&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run lint&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have a separate CI workflow that already ran &lt;code&gt;next lint&lt;/code&gt;, change the&lt;br&gt;
script call from &lt;code&gt;next lint&lt;/code&gt; to &lt;code&gt;eslint .&lt;/code&gt;. The &lt;a href="https://www.iloveblogs.blog/guides/nextjs-supabase-cicd-github-actions-production" rel="noopener noreferrer"&gt;Next.js + Supabase CI/CD&lt;br&gt;
production guide&lt;/a&gt;&lt;br&gt;
covers the broader workflow, but the one-line change for the lint step is&lt;br&gt;
&lt;code&gt;npx next lint&lt;/code&gt; → &lt;code&gt;npx eslint .&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  The &lt;code&gt;ignoreDuringBuilds&lt;/code&gt; trap
&lt;/h2&gt;

&lt;p&gt;A lot of projects had this in &lt;code&gt;next.config.mjs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;eslint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ignoreDuringBuilds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;In Next.js 16, the &lt;code&gt;eslint&lt;/code&gt; key is removed. The config still loads without&lt;br&gt;
error — Next.js ignores unknown keys — so the file does not break. But the&lt;br&gt;
behavior changes: the key no longer does anything because the build no&lt;br&gt;
longer lints at all. You can leave the block in for a version or two as a&lt;br&gt;
no-op, but the clean move is to delete it and move the ignore list into&lt;br&gt;
&lt;code&gt;eslint.config.mjs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;ignores&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;scripts/**&lt;/span&gt;&lt;span class="dl"&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;docs/**&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That puts all lint configuration in one place — the ESLint flat config —&lt;br&gt;
instead of split between &lt;code&gt;next.config.mjs&lt;/code&gt; and &lt;code&gt;.eslintrc&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common pitfalls, ranked by how often they bite
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. &lt;code&gt;Unknown command: lint&lt;/code&gt; in CI.&lt;/strong&gt; Your CI script still calls&lt;br&gt;
&lt;code&gt;next lint&lt;/code&gt;. Change it to &lt;code&gt;eslint .&lt;/code&gt; (or run the codemod, which rewrites the&lt;br&gt;
&lt;code&gt;lint&lt;/code&gt; script). This is the single most common upgrade break.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;Failed to load config "next/core-web-vitals"&lt;/code&gt;.&lt;/strong&gt; You copied an old&lt;br&gt;
&lt;code&gt;.eslintrc.json&lt;/code&gt; that extends &lt;code&gt;next/core-web-vitals&lt;/code&gt; into a Next.js 16&lt;br&gt;
project. The legacy export path is gone. Replace the file with&lt;br&gt;
&lt;code&gt;eslint.config.mjs&lt;/code&gt; that imports from &lt;code&gt;eslint-config-next/core-web-vitals&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. &lt;code&gt;next build&lt;/code&gt; is green, lint is broken.&lt;/strong&gt; You removed the lint step&lt;br&gt;
during the upgrade and never added it back. Add &lt;code&gt;npm run lint&lt;/code&gt; to CI before&lt;br&gt;
&lt;code&gt;next build&lt;/code&gt; — otherwise lint regressions ship to production undetected.&lt;br&gt;
This is the silent-coverage bug above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. TypeScript rules missing.&lt;/strong&gt; You added &lt;code&gt;eslint-config-next/core-web-vitals&lt;/code&gt;&lt;br&gt;
but forgot &lt;code&gt;eslint-config-next/typescript&lt;/code&gt;. Your &lt;code&gt;@typescript-eslint&lt;/code&gt; rules&lt;br&gt;
disappear and &lt;code&gt;no-unused-vars&lt;/code&gt; no longer catches unused TS variables. Add&lt;br&gt;
the TypeScript export to the array.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. &lt;code&gt;Parsing error: Cannot find module 'next/babel'&lt;/code&gt;.&lt;/strong&gt; This is a&lt;br&gt;
different, older issue — a leftover &lt;code&gt;.babelrc&lt;/code&gt; referencing &lt;code&gt;next/babel&lt;/code&gt;&lt;br&gt;
while the project uses SWC. It is covered separately in the&lt;br&gt;
&lt;a href="https://www.iloveblogs.blog/fix/parsing-error-cannot-find-module-nextbabel" rel="noopener noreferrer"&gt;&lt;code&gt;next/babel&lt;/code&gt; parsing error fix&lt;/a&gt;;&lt;br&gt;
do not conflate it with the flat-config migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on Turbopack
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;next build --turbopack&lt;/code&gt; (the Turbopack bundler) is unrelated to this&lt;br&gt;
change. Turbopack replaces Webpack as the bundler; it does not change how&lt;br&gt;
linting works. If you hit a build error after upgrading and it mentions&lt;br&gt;
Turbopack, it is a bundler issue, not a lint issue — the&lt;br&gt;
&lt;a href="https://www.iloveblogs.blog/post/nextjs-turbopack-stuck-fix" rel="noopener noreferrer"&gt;Turbopack stuck fix&lt;/a&gt; covers that&lt;br&gt;
separately. Do not try to "fix" a Turbopack error by editing your ESLint&lt;br&gt;
config.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying the migration worked
&lt;/h2&gt;

&lt;p&gt;Three checks, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;npm run lint&lt;/code&gt; runs without &lt;code&gt;ERR_MODULE_NOT_FOUND&lt;/code&gt; and without
&lt;code&gt;Failed to load config&lt;/code&gt;. If either appears, a plugin in your tree still
ships legacy config — wrap it with &lt;code&gt;FlatCompat&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm run build&lt;/code&gt; succeeds and the build log does &lt;strong&gt;not&lt;/strong&gt; contain a lint
pass. Pre-16, the build log had a "Linting and checking validity of
types" step; post-16, that step is absent. That absence is correct.&lt;/li&gt;
&lt;li&gt;CI fails on a deliberately introduced lint error. Add &lt;code&gt;const _x = 1&lt;/code&gt; to a
file, push, and confirm the CI job fails on the &lt;code&gt;npm run lint&lt;/code&gt; step. If
it passes, your &lt;code&gt;lint&lt;/code&gt; script is still pointing at &lt;code&gt;next lint&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Production recommendations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pin &lt;code&gt;eslint&lt;/code&gt; and &lt;code&gt;eslint-config-next&lt;/code&gt; to the same minor version as
Next.js.&lt;/strong&gt; A &lt;code&gt;eslint-config-next&lt;/code&gt; major bump can change rule severity
without warning; lock the version in &lt;code&gt;package.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run &lt;code&gt;lint:fix&lt;/code&gt; in pre-commit, not in CI.&lt;/strong&gt; CI should run &lt;code&gt;eslint .&lt;/code&gt;
(read-only) and fail on errors. Auto-fixing in CI hides the signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add &lt;code&gt;eslint . --max-warnings 0&lt;/code&gt; to CI only after you reach zero
warnings.&lt;/strong&gt; Turning warnings into errors before the codebase is clean
makes CI red for weeks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep &lt;code&gt;@eslint/eslintrc&lt;/code&gt; in devDependencies until every plugin in your
tree has a flat-config export.&lt;/strong&gt; Then remove it — the compat layer is
dead weight once you do not need it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Move all ignore patterns into &lt;code&gt;eslint.config.mjs&lt;/code&gt;&lt;/strong&gt;, not
&lt;code&gt;next.config.mjs&lt;/code&gt;. One source of truth for lint config is the whole point
of the migration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Should I migrate to ESLint 9 or wait for ESLint 10?
&lt;/h3&gt;

&lt;p&gt;Migrate now. ESLint 10 will drop legacy config support entirely; v16 already&lt;br&gt;
defaults to flat config and will not maintain backwards compatibility. The&lt;br&gt;
longer you stay on &lt;code&gt;.eslintrc.*&lt;/code&gt;, the more painful the eventual move.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use Biome instead of ESLint in Next.js 16?
&lt;/h3&gt;

&lt;p&gt;Yes. The upgrade guide explicitly mentions Biome as an alternative. Biome&lt;br&gt;
does not consume &lt;code&gt;eslint-config-next&lt;/code&gt; rules, so you lose the Next-specific&lt;br&gt;
checks (the &lt;code&gt;no-html-link-for-pages&lt;/code&gt;, &lt;code&gt;no-img-element&lt;/code&gt;, core-web-vitals&lt;br&gt;
rules). If those matter to you, stay on ESLint.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about the &lt;code&gt;eslint-config-next&lt;/code&gt; package — is it deprecated?
&lt;/h3&gt;

&lt;p&gt;No. &lt;code&gt;eslint-config-next&lt;/code&gt; is the supported way to get Next.js lint rules in&lt;br&gt;
flat config. Only the &lt;code&gt;next lint&lt;/code&gt; wrapper command is removed. The package&lt;br&gt;
is alive and is what you import from in &lt;code&gt;eslint.config.mjs&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/fix/parsing-error-cannot-find-module-nextbabel" rel="noopener noreferrer"&gt;Fix: &lt;code&gt;Parsing error: Cannot find module 'next/babel'&lt;/code&gt;&lt;/a&gt; — the other common Next.js lint error, separate from the flat-config migration.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/post/nextjs-build-module-not-found" rel="noopener noreferrer"&gt;Next.js build &lt;code&gt;MODULE_NOT_FOUND&lt;/code&gt; fix&lt;/a&gt; — when a Next.js 16 upgrade pulls in a missing dependency.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/post/nextjs-15-minimum-nodejs-version-18180" rel="noopener noreferrer"&gt;Next.js 15 minimum Node.js version (18.18.0)&lt;/a&gt; — the Node version floor that also applies to Next.js 16 and to the ESLint 9 CLI.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/guides/nextjs-supabase-cicd-github-actions-production" rel="noopener noreferrer"&gt;Next.js + Supabase CI/CD with GitHub Actions&lt;/a&gt; — where the &lt;code&gt;npm run lint&lt;/code&gt; CI step belongs in a full-stack pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/post/nextjs-supabase-common-mistakes" rel="noopener noreferrer"&gt;Next.js + Supabase common mistakes&lt;/a&gt; — adjacent production pitfalls beyond linting.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.iloveblogs.blog/post/nextjs-16-next-lint-removed-eslint-flat-config" rel="noopener noreferrer"&gt;https://www.iloveblogs.blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>eslint</category>
      <category>flatconfig</category>
      <category>migration</category>
    </item>
    <item>
      <title>How to Show Tables in PostgreSQL (psql + Supabase)</title>
      <dc:creator>Mahdi BEN RHOUMA</dc:creator>
      <pubDate>Sat, 25 Jul 2026 15:03:43 +0000</pubDate>
      <link>https://dev.to/mahdi_benrhouma_fe1c6005/how-to-show-tables-in-postgresql-psql-supabase-1h28</link>
      <guid>https://dev.to/mahdi_benrhouma_fe1c6005/how-to-show-tables-in-postgresql-psql-supabase-1h28</guid>
      <description>&lt;h2&gt;
  
  
  The one-line answer
&lt;/h2&gt;

&lt;p&gt;There is no &lt;code&gt;SHOW TABLES&lt;/code&gt; in PostgreSQL. The psql client gives you &lt;code&gt;\dt&lt;/code&gt;;&lt;br&gt;
every other client gives you &lt;code&gt;information_schema.tables&lt;/code&gt;. That is the whole&lt;br&gt;
article in two commands:&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="c1"&gt;-- psql&lt;/span&gt;
&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;

&lt;span class="c1"&gt;-- any client (Supabase SQL editor, Drizzle Studio, DataGrip, pgAdmin)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;table_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rest of this article is the part that costs you an hour the first time:&lt;br&gt;
schema filtering, sizes, system tables, Supabase specifics, and the three&lt;br&gt;
reasons your query returns zero rows.&lt;/p&gt;
&lt;h2&gt;
  
  
  Method 1: &lt;code&gt;\dt&lt;/code&gt; in psql
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;\dt&lt;/code&gt; is a psql meta-command, not SQL. It only works inside the &lt;code&gt;psql&lt;/code&gt;&lt;br&gt;
client. The &lt;a href="https://www.postgresql.org/docs/current/app-psql.html" rel="noopener noreferrer"&gt;official PostgreSQL psql documentation&lt;/a&gt; documents it&lt;br&gt;
under the relation-listing group:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it lists&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\dt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;User tables in the current schema search path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\dt+&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Same, plus on-disk size, persistence, description&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\dtS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Include system tables (the &lt;code&gt;S&lt;/code&gt; means "system")&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\dt schema.*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tables in a specific schema&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\dt public.users&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A specific table, if it exists&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- List your tables&lt;/span&gt;
&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;

&lt;span class="c1"&gt;-- List with size and description&lt;/span&gt;
&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;

&lt;span class="c1"&gt;-- List only tables in the "auth" schema&lt;/span&gt;
&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="c1"&gt;-- Include system tables (rarely what you want)&lt;/span&gt;
&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;dtS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;The single most common mistake:&lt;/strong&gt; running &lt;code&gt;\dt&lt;/code&gt; in the Supabase SQL&lt;br&gt;
editor, Drizzle Studio, or pgAdmin and getting a syntax error. Backslash&lt;br&gt;
commands are psql-only — they are interpreted by the psql client before&lt;br&gt;
the query ever reaches the server. Any client that is not psql will send&lt;br&gt;
&lt;code&gt;\dt&lt;/code&gt; to PostgreSQL as SQL, where it is a syntax error.&lt;/p&gt;


  &lt;img src="/article-assets/proofs/how-to-show-tables-in-postgresql-psql-supabase/psql-dt-plus.png" alt="Illustration: terminal output of psql \dt+ connected to a Supabase project, showing the four public-schema tables (profiles, posts, comments, subscriptions) with persistence, size and description columns. Example only — column structure and layout sourced verbatim from the official PostgreSQL psql documentation."&gt;&lt;strong&gt;Illustration — not a live capture.&lt;/strong&gt; Example only; the column structure and table sample are quoted from the official PostgreSQL psql documentation and a typical Supabase-style schema. The orange banner inside the image is the renderer-level "not a live capture" badge.
  

&lt;h3&gt;
  
  
  &lt;code&gt;\d&lt;/code&gt; vs &lt;code&gt;\dt&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;\d&lt;/code&gt; without arguments lists every relation — tables, views, materialized&lt;br&gt;
views, sequences, and foreign tables. It is the same as &lt;code&gt;\dtvmsE&lt;/code&gt;. If you&lt;br&gt;
only want tables, use &lt;code&gt;\dt&lt;/code&gt;. If you want a single relation's schema (the&lt;br&gt;
MySQL &lt;code&gt;DESCRIBE table&lt;/code&gt; equivalent), use &lt;code&gt;\d table_name&lt;/code&gt; — covered in the&lt;br&gt;
&lt;a href="https://www.iloveblogs.blog/post/postgres-describe-table-equivalent" rel="noopener noreferrer"&gt;PostgreSQL DESCRIBE TABLE equivalent&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Method 2: &lt;code&gt;information_schema.tables&lt;/code&gt; (portable SQL)
&lt;/h2&gt;

&lt;p&gt;This is the query that works everywhere — psql, Supabase SQL editor, Drizzle&lt;br&gt;
Studio, DataGrip, pgAdmin, any driver. It is the SQL-standard way and the&lt;br&gt;
only way in the Supabase SQL editor.&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="c1"&gt;-- All tables in the public schema&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;table_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;table_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'BASE TABLE'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;table_type = 'BASE TABLE'&lt;/code&gt; filter excludes views. Without it you get&lt;br&gt;
both tables and views in the result.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why you must filter by schema
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;information_schema.tables&lt;/code&gt; returns &lt;strong&gt;every&lt;/strong&gt; table the server knows about,&lt;br&gt;
including the system catalogs. Run this without a filter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;table_schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;table_schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will get hundreds of rows from &lt;code&gt;pg_catalog&lt;/code&gt; (the system catalog) and&lt;br&gt;
&lt;code&gt;information_schema&lt;/code&gt; itself. Those are not your tables. Always filter on&lt;br&gt;
&lt;code&gt;table_schema&lt;/code&gt; unless you genuinely want the system schemas:&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="c1"&gt;-- Only your app tables (typical)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;table_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;

&lt;span class="c1"&gt;-- Multiple app schemas&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;table_schema&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'public'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'auth'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'storage'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;-- Everything except system schemas&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;table_schema&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pg_catalog'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'information_schema'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pg_toast'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  List all tables across all schemas (excluding system schemas)
&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;table_schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;table_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'BASE TABLE'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;table_schema&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pg_catalog'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'information_schema'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pg_toast'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;table_schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the query to reach for when you do not know which schemas your app&lt;br&gt;
uses — for example, a Supabase project with &lt;code&gt;public&lt;/code&gt;, &lt;code&gt;auth&lt;/code&gt;, &lt;code&gt;storage&lt;/code&gt;,&lt;br&gt;
and a custom &lt;code&gt;billing&lt;/code&gt; schema.&lt;/p&gt;
&lt;h2&gt;
  
  
  Method 3: &lt;code&gt;pg_class&lt;/code&gt; (the most granular)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;information_schema&lt;/code&gt; is the SQL standard, but it does not expose physical&lt;br&gt;
metadata like on-disk size or row estimates. For that, query &lt;code&gt;pg_class&lt;/code&gt;&lt;br&gt;
directly. This is the approach the &lt;a href="https://www.postgresql.org/docs/current/app-psql.html" rel="noopener noreferrer"&gt;PostgreSQL docs&lt;/a&gt; describe&lt;br&gt;
through the system catalogs.&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="c1"&gt;-- List all user tables with their schema&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;schema_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_class&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_namespace&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relnamespace&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relkind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pg_catalog'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'information_schema'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;relkind = 'r'&lt;/code&gt; means "ordinary heap table". Other useful values: &lt;code&gt;p&lt;/code&gt; for&lt;br&gt;
partitioned tables, &lt;code&gt;v&lt;/code&gt; for views, &lt;code&gt;m&lt;/code&gt; for materialized views, &lt;code&gt;S&lt;/code&gt; for&lt;br&gt;
sequences. If you want partitioned tables too, use &lt;code&gt;c.relkind IN ('r', 'p')&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  List tables with their on-disk size
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;information_schema.tables&lt;/code&gt; does not expose size. Use &lt;code&gt;pg_class&lt;/code&gt; with&lt;br&gt;
&lt;code&gt;pg_total_relation_size()&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;schema_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;pg_size_pretty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pg_total_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_size&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_class&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_namespace&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relnamespace&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relkind&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&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;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pg_catalog'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'information_schema'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;pg_total_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pg_total_relation_size()&lt;/code&gt; includes indexes and TOAST data, not just the&lt;br&gt;
heap. If you want only the table heap, use &lt;code&gt;pg_table_size()&lt;/code&gt;; if you want&lt;br&gt;
only the indexes, use &lt;code&gt;pg_indexes_size()&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Approximate row counts
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;information_schema.tables&lt;/code&gt; does not give row counts either, and &lt;code&gt;COUNT(*)&lt;/code&gt;&lt;br&gt;
on every table is too expensive on a large database. Use the planner's&lt;br&gt;
reltuples estimate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reltuples&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;approximate_row_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_class&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_namespace&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relnamespace&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relkind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reltuples&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;reltuples&lt;/code&gt; is updated by &lt;code&gt;ANALYZE&lt;/code&gt; and is an estimate — good enough for&lt;br&gt;
"which table is the big one" diagnostics. For exact counts you must run&lt;br&gt;
&lt;code&gt;SELECT COUNT(*)&lt;/code&gt; per table, which is a sequential scan. The&lt;br&gt;
&lt;a href="https://www.iloveblogs.blog/post/how-to-get-count-in-supabase" rel="noopener noreferrer"&gt;how to get count in Supabase&lt;/a&gt; article&lt;br&gt;
covers the Supabase client side of the same problem.&lt;/p&gt;
&lt;h2&gt;
  
  
  Method 4: Supabase specifics
&lt;/h2&gt;

&lt;p&gt;Supabase runs PostgreSQL, so every method above works in the Supabase SQL&lt;br&gt;
editor with one constraint: &lt;strong&gt;the SQL editor is not psql, so backslash&lt;br&gt;
commands do not work.&lt;/strong&gt; Use &lt;code&gt;information_schema.tables&lt;/code&gt; or &lt;code&gt;pg_class&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Table Editor (zero-SQL path)
&lt;/h3&gt;

&lt;p&gt;If you just want to see your tables in a browser, the Supabase Dashboard has&lt;br&gt;
a Table Editor: Dashboard → Table Editor. It lists every table in every&lt;br&gt;
schema your service role can see, with row counts and a quick-edit view. For&lt;br&gt;
a developer pasting &lt;code&gt;\dt&lt;/code&gt; into Google, the SQL editor query above is faster.&lt;/p&gt;


  &lt;img src="/article-assets/proofs/how-to-show-tables-in-postgresql-psql-supabase/psql-vs-supabase-editor.png" alt="Illustration: side-by-side comparison of what the psql \dt+ CLI shows versus what the Supabase Table Editor surfaces in the dashboard. Same tables, different metadata. Example only — synthesized from the official psql docs and the Supabase Table Editor documentation."&gt;&lt;strong&gt;Illustration — not a live capture.&lt;/strong&gt; Example only; the comparison is synthesized from the official psql documentation and the Supabase Table Editor documentation. The orange banner inside the image is the renderer-level "not a live capture" badge.
  

&lt;h3&gt;
  
  
  Listing tables with RLS enabled
&lt;/h3&gt;

&lt;p&gt;A common Supabase question is not "which tables exist" but "which tables&lt;br&gt;
have row-level security enabled". That is a &lt;code&gt;pg_class&lt;/code&gt; join against&lt;br&gt;
&lt;code&gt;pg_namespace&lt;/code&gt; with a check on &lt;code&gt;relrowsecurity&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;schema_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relrowsecurity&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rls_enabled&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_class&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_namespace&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relnamespace&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relkind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relrowsecurity&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;relrowsecurity&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt; when RLS is enabled. To go deeper — auditing the&lt;br&gt;
actual policies, not just the flag — see the&lt;br&gt;
&lt;a href="https://www.iloveblogs.blog/guides/supabase-rls-policy-design-patterns" rel="noopener noreferrer"&gt;Supabase RLS policy design patterns&lt;/a&gt;&lt;br&gt;
guide and the &lt;a href="https://www.iloveblogs.blog/post/debugging-supabase-rls-issues" rel="noopener noreferrer"&gt;debugging Supabase RLS issues&lt;/a&gt;&lt;br&gt;
post.&lt;/p&gt;
&lt;h3&gt;
  
  
  Listing tables in a specific schema (auth, storage, realtime)
&lt;/h3&gt;

&lt;p&gt;Supabase ships non-&lt;code&gt;public&lt;/code&gt; schemas. To list tables in &lt;code&gt;auth&lt;/code&gt; (Supabase&lt;br&gt;
Auth's internal schema) or &lt;code&gt;storage&lt;/code&gt; (Supabase Storage):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;table_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'auth'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: you will only see these schemas if your role has been granted access.&lt;br&gt;
The &lt;code&gt;anon&lt;/code&gt; and &lt;code&gt;authenticated&lt;/code&gt; roles do not have SELECT on most of &lt;code&gt;auth&lt;/code&gt;&lt;br&gt;
and &lt;code&gt;storage&lt;/code&gt;; run these queries as the &lt;code&gt;postgres&lt;/code&gt; role or a service role.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why your query returns zero rows
&lt;/h2&gt;

&lt;p&gt;Three causes cover almost every "I ran the query and got nothing" report.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. You are connected to the wrong database
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;information_schema.tables&lt;/code&gt; is per-database. If you have two Supabase&lt;br&gt;
projects and your connection string points at project A, you will not see&lt;br&gt;
project B's tables. Confirm with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;current_database&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. The schema name is wrong or quoted differently
&lt;/h3&gt;

&lt;p&gt;PostgreSQL folds unquoted identifiers to lowercase. A table created as&lt;br&gt;
&lt;code&gt;CREATE TABLE "Users"&lt;/code&gt; lives in &lt;code&gt;public.Users&lt;/code&gt; (capital U), but&lt;br&gt;
&lt;code&gt;WHERE table_schema = 'public' AND table_name = 'users'&lt;/code&gt; returns nothing&lt;br&gt;
because the actual name is &lt;code&gt;Users&lt;/code&gt;. Query without the name filter first,&lt;br&gt;
then look at the exact &lt;code&gt;table_name&lt;/code&gt; value.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Your role does not have access
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;information_schema.tables&lt;/code&gt; only shows tables the current role has&lt;br&gt;
privileges on. The &lt;code&gt;anon&lt;/code&gt; role in Supabase sees very little; the &lt;code&gt;postgres&lt;/code&gt;&lt;br&gt;
role sees everything. If you run the query as &lt;code&gt;anon&lt;/code&gt; and get an empty&lt;br&gt;
result, connect as &lt;code&gt;postgres&lt;/code&gt; or your service role and re-run. This is the&lt;br&gt;
same privilege model that produces the&lt;br&gt;
&lt;a href="https://www.iloveblogs.blog/post/postgres-peer-authentication-failed-fix" rel="noopener noreferrer"&gt;Postgres peer authentication failed&lt;/a&gt;&lt;br&gt;
error when the OS user does not match the database role.&lt;/p&gt;
&lt;h2&gt;
  
  
  Performance: do not &lt;code&gt;SELECT COUNT(*)&lt;/code&gt; to list tables
&lt;/h2&gt;

&lt;p&gt;The most expensive mistake on a large database is looping over every table&lt;br&gt;
and running &lt;code&gt;SELECT COUNT(*) FROM table&lt;/code&gt;. Each count is a sequential scan;&lt;br&gt;
on a 50M-row table that is seconds to minutes. For "show tables with row&lt;br&gt;
counts", use the &lt;code&gt;reltuples&lt;/code&gt; estimate from &lt;code&gt;pg_class&lt;/code&gt; (Method 3 above). Run&lt;br&gt;
&lt;code&gt;ANALYZE&lt;/code&gt; first if the estimate is stale:&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;ANALYZE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ANALYZE&lt;/code&gt; updates planner statistics, including &lt;code&gt;reltuples&lt;/code&gt;, and is&lt;br&gt;
read-only with respect to user data — it does not lock tables for long. For&lt;br&gt;
the Supabase client equivalent (counting rows from the browser), see&lt;br&gt;
&lt;a href="https://www.iloveblogs.blog/post/how-to-get-count-in-supabase" rel="noopener noreferrer"&gt;how to get count in Supabase&lt;/a&gt;, which&lt;br&gt;
covers the &lt;code&gt;head: true&lt;/code&gt; trick that avoids fetching rows.&lt;/p&gt;
&lt;h2&gt;
  
  
  Production recommendations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;information_schema.tables&lt;/code&gt; for any non-psql client.&lt;/strong&gt; It is the
only portable way. Backslash commands will fail in the Supabase SQL
editor, Drizzle Studio, and most ORMs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always filter on &lt;code&gt;table_schema&lt;/code&gt;.&lt;/strong&gt; Without a filter you get hundreds
of system tables that obscure your actual tables.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;pg_class&lt;/code&gt; for size and row estimates.&lt;/strong&gt; &lt;code&gt;information_schema.tables&lt;/code&gt;
does not expose physical metadata; &lt;code&gt;pg_class&lt;/code&gt; does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run &lt;code&gt;ANALYZE&lt;/code&gt; before trusting &lt;code&gt;reltuples&lt;/code&gt;.&lt;/strong&gt; The estimate is only as
fresh as the last ANALYZE. On Supabase, the autovacuum default keeps it
reasonably fresh, but a bulk import will leave it stale until the next
run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For schema auditing, combine &lt;code&gt;pg_class&lt;/code&gt; with &lt;code&gt;pg_namespace&lt;/code&gt; and
&lt;code&gt;pg_description&lt;/code&gt;.&lt;/strong&gt; That gives you table, schema, size, RLS flag, and
comment in one query — the basis of any
&lt;a href="https://www.iloveblogs.blog/guides/nextjs-supabase-database-design-optimization" rel="noopener noreferrer"&gt;database design optimization&lt;/a&gt;
review.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Listing views, materialized views, and sequences too
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;\dt&lt;/code&gt; lists only heap tables. Real schemas also contain views, materialized&lt;br&gt;
views, and sequences — and "show me everything in this schema" is a&lt;br&gt;
different question from "show me the tables". Each has its own psql&lt;br&gt;
meta-command and its own &lt;code&gt;relkind&lt;/code&gt; filter in &lt;code&gt;pg_class&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Object type&lt;/th&gt;
&lt;th&gt;psql command&lt;/th&gt;
&lt;th&gt;&lt;code&gt;relkind&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;information_schema&lt;/code&gt; view&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tables&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\dt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;r&lt;/code&gt; (heap) or &lt;code&gt;p&lt;/code&gt; (partitioned)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;information_schema.tables&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Views&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\dv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;information_schema.views&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Materialized views&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\dm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;m&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(none — query &lt;code&gt;pg_class&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sequences&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\ds&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;S&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;information_schema.sequences&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Everything&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;r,p,v,m,S,f&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(no single view)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To list every relation in the &lt;code&gt;public&lt;/code&gt; schema regardless of type, in any&lt;br&gt;
client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relkind&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;schema_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;relation_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_class&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_namespace&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relnamespace&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relkind&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'v'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'S'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relkind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the query to run when you inherit a database and need to know what&lt;br&gt;
is in it before you touch anything. Materialized views are particularly&lt;br&gt;
easy to miss because they do not show up in &lt;code&gt;\dt&lt;/code&gt; and have no row in&lt;br&gt;
&lt;code&gt;information_schema.tables&lt;/code&gt; with &lt;code&gt;table_type = 'BASE TABLE'&lt;/code&gt; — they are a&lt;br&gt;
distinct &lt;code&gt;relkind = 'm'&lt;/code&gt; and only appear in &lt;code&gt;pg_class&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Refreshing materialized views
&lt;/h3&gt;

&lt;p&gt;A materialized view is a stored query result. It does not update&lt;br&gt;
automatically; you refresh it with &lt;code&gt;REFRESH MATERIALIZED VIEW&lt;br&gt;
view_name;&lt;/code&gt;. If you are listing tables to find "why is this data stale", the&lt;br&gt;
materialized view is the usual suspect. List them first with &lt;code&gt;\dm&lt;/code&gt; in psql&lt;br&gt;
or the &lt;code&gt;relkind = 'm'&lt;/code&gt; query above, then check each one's last refresh time&lt;br&gt;
through &lt;code&gt;pg_stat_user_tables&lt;/code&gt; — there is no native "last refreshed" column,&lt;br&gt;
so most teams track it themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is &lt;code&gt;\dt&lt;/code&gt; the same as &lt;code&gt;SHOW TABLES&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;No. &lt;code&gt;SHOW TABLES&lt;/code&gt; is a MySQL statement; PostgreSQL does not have it. &lt;code&gt;\dt&lt;/code&gt;&lt;br&gt;
is a psql client command that lists tables. They serve the same purpose for&lt;br&gt;
the user, but &lt;code&gt;\dt&lt;/code&gt; never leaves the psql client — it is not SQL.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I list tables in a specific schema?
&lt;/h3&gt;

&lt;p&gt;In psql: &lt;code&gt;\dt schema_name.*&lt;/code&gt;. In any other client:&lt;br&gt;
&lt;code&gt;SELECT table_name FROM information_schema.tables WHERE table_schema = 'schema_name';&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I see table sizes in PostgreSQL?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;\dt+&lt;/code&gt; in psql, or query &lt;code&gt;pg_class&lt;/code&gt; with &lt;code&gt;pg_total_relation_size(c.oid)&lt;/code&gt;.&lt;br&gt;
&lt;code&gt;information_schema.tables&lt;/code&gt; does not expose size.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I list only partitioned tables?
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;pg_class&lt;/code&gt;, filter on &lt;code&gt;relkind = 'p'&lt;/code&gt;. In &lt;code&gt;information_schema.tables&lt;/code&gt;,&lt;br&gt;
partitioned tables appear with &lt;code&gt;table_type = 'BASE TABLE'&lt;/code&gt; — the view does&lt;br&gt;
not distinguish partitioned from heap tables, which is a reason to use&lt;br&gt;
&lt;code&gt;pg_class&lt;/code&gt; for physical metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/post/postgres-describe-table-equivalent" rel="noopener noreferrer"&gt;PostgreSQL DESCRIBE TABLE: the psql &lt;code&gt;\d&lt;/code&gt; equivalent&lt;/a&gt; — the sibling article for inspecting a single table's columns, types, and constraints.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/post/how-to-get-count-in-supabase" rel="noopener noreferrer"&gt;How to get count in Supabase&lt;/a&gt; — the Supabase client side of "how many rows", including the &lt;code&gt;head: true&lt;/code&gt; trick.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/guides/supabase-rls-policy-design-patterns" rel="noopener noreferrer"&gt;Supabase RLS policy design patterns&lt;/a&gt; — for auditing which tables have RLS and what the policies do.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/guides/supabase-postgres-functions-triggers-guide" rel="noopener noreferrer"&gt;Supabase Postgres functions and triggers guide&lt;/a&gt; — for listing and inspecting functions and triggers alongside your tables.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/guides/nextjs-supabase-database-design-optimization" rel="noopener noreferrer"&gt;Next.js + Supabase database design optimization&lt;/a&gt; — where table listing fits into a schema review.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/post/postgres-peer-authentication-failed-fix" rel="noopener noreferrer"&gt;Postgres peer authentication failed fix&lt;/a&gt; — when "I cannot see my tables" is actually a role/auth problem.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.iloveblogs.blog/post/how-to-show-tables-in-postgresql-psql-supabase" rel="noopener noreferrer"&gt;https://www.iloveblogs.blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>psql</category>
      <category>supabase</category>
      <category>informationschema</category>
    </item>
    <item>
      <title>How to Change a PostgreSQL User Password (Supabase)</title>
      <dc:creator>Mahdi BEN RHOUMA</dc:creator>
      <pubDate>Sat, 25 Jul 2026 15:03:35 +0000</pubDate>
      <link>https://dev.to/mahdi_benrhouma_fe1c6005/how-to-change-a-postgresql-user-password-supabase-5e2g</link>
      <guid>https://dev.to/mahdi_benrhouma_fe1c6005/how-to-change-a-postgresql-user-password-supabase-5e2g</guid>
      <description>&lt;h2&gt;
  
  
  The one-line answer
&lt;/h2&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="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;alice&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="s1"&gt;'new_password'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the SQL. The rest of this article is about doing it safely — without&lt;br&gt;
leaving the cleartext in your shell history, without breaking Supabase&lt;br&gt;
managed connections, and without forgetting the connection string in your&lt;br&gt;
production app.&lt;/p&gt;


  &lt;img src="/article-assets/proofs/how-to-change-postgresql-user-password-alter-role-supabase/rotation-sequence.png" alt="Illustration: the five-step zero-downtime Postgres password rotation sequence — pick a window, ALTER ROLE, roll credentials in consumers, pg_terminate remaining old-password sessions, then verify and revoke. Example only — synthesized from the article body and the official PostgreSQL ALTER ROLE / pg_terminate_backend documentation."&gt;&lt;strong&gt;Illustration — not a live capture.&lt;/strong&gt; Example diagram only; the steps are synthesized from this article and the official PostgreSQL &lt;code&gt;ALTER ROLE&lt;/code&gt; / &lt;code&gt;pg_terminate_backend&lt;/code&gt; documentation. The orange banner inside the image is the renderer-level "not a live capture" badge.
  


&lt;p&gt;The &lt;a href="https://www.postgresql.org/docs/current/sql-alterrole.html" rel="noopener noreferrer"&gt;PostgreSQL &lt;code&gt;ALTER ROLE&lt;/code&gt; documentation&lt;/a&gt; is the&lt;br&gt;
authoritative source for the syntax; this article adds the operational&lt;br&gt;
context the docs do not.&lt;/p&gt;
&lt;h2&gt;
  
  
  Method 1: &lt;code&gt;ALTER ROLE ... PASSWORD&lt;/code&gt; (the SQL way)
&lt;/h2&gt;

&lt;p&gt;The canonical command from the &lt;a href="https://www.postgresql.org/docs/current/sql-alterrole.html" rel="noopener noreferrer"&gt;PostgreSQL docs&lt;/a&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="c1"&gt;-- Set a password&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;alice&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="s1"&gt;'hunter2'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Remove a password (set to NULL — role can still connect if other auth methods allow)&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;alice&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ALTER USER&lt;/code&gt; is a legacy alias for &lt;code&gt;ALTER ROLE&lt;/code&gt; and behaves identically. The&lt;br&gt;
docs are explicit: &lt;code&gt;ALTER USER&lt;/code&gt; "is accepted for backward compatibility, but&lt;br&gt;
the canonical command is &lt;code&gt;ALTER ROLE&lt;/code&gt;." Use &lt;code&gt;ALTER ROLE&lt;/code&gt; in new code.&lt;/p&gt;
&lt;h3&gt;
  
  
  The &lt;code&gt;ENCRYPTED&lt;/code&gt; keyword is optional and the default
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- These two are equivalent&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;alice&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;ENCRYPTED&lt;/span&gt; &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="s1"&gt;'hunter2'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;alice&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="s1"&gt;'hunter2'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;ENCRYPTED&lt;/code&gt; keyword is optional. The server encrypts the password with&lt;br&gt;
the algorithm set by &lt;code&gt;password_encryption&lt;/code&gt; — &lt;code&gt;scram-sha-256&lt;/code&gt; by default&lt;br&gt;
since PostgreSQL 13. You almost never need to write &lt;code&gt;ENCRYPTED&lt;/code&gt; explicitly.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why &lt;code&gt;ALTER ROLE&lt;/code&gt; leaks the password into logs
&lt;/h3&gt;

&lt;p&gt;The docs warn about this directly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Caution must be exercised when specifying an unencrypted password with&lt;br&gt;
this command. The password will be transmitted to the server in cleartext,&lt;br&gt;
and it might also be logged in the client's command history or the server&lt;br&gt;
log.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you type &lt;code&gt;ALTER ROLE alice WITH PASSWORD 'hunter2';&lt;/code&gt; in psql, the string&lt;br&gt;
&lt;code&gt;hunter2&lt;/code&gt; lands in &lt;code&gt;~/.psql_history&lt;/code&gt;. If statement logging is on, it lands&lt;br&gt;
in the server log too. For a personal dev database that is fine. For&lt;br&gt;
production it is not. Use Method 2.&lt;/p&gt;
&lt;h2&gt;
  
  
  Method 2: &lt;code&gt;\password&lt;/code&gt; in psql (the safe way)
&lt;/h2&gt;

&lt;p&gt;psql ships a meta-command that prompts for the new password interactively&lt;br&gt;
and never writes the cleartext to history or logs:&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="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="n"&gt;alice&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It asks for the new password twice (to confirm) and sends an &lt;code&gt;ALTER ROLE&lt;/code&gt;&lt;br&gt;
to the server with the password already encrypted by the client. This is&lt;br&gt;
the method the PostgreSQL docs recommend for any password change you do&lt;br&gt;
interactively. Use it whenever you are in psql.&lt;/p&gt;
&lt;h2&gt;
  
  
  Method 3: Reset the &lt;code&gt;postgres&lt;/code&gt; role in Supabase
&lt;/h2&gt;

&lt;p&gt;Supabase manages the &lt;code&gt;postgres&lt;/code&gt; role for you. You &lt;strong&gt;can&lt;/strong&gt; run&lt;br&gt;
&lt;code&gt;ALTER ROLE postgres WITH PASSWORD '...'&lt;/code&gt; in the SQL editor, but the&lt;br&gt;
recommended path is the Dashboard, because Supabase-managed services&lt;br&gt;
(PgBouncer, PostgREST, GoTrue, the Studio itself) need their connection&lt;br&gt;
strings rotated too. The Dashboard reset does that for you.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open your Supabase project Dashboard.&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Database → Settings&lt;/strong&gt; (also reachable via Project Settings →
Database).&lt;/li&gt;
&lt;li&gt;Locate the &lt;strong&gt;Database password&lt;/strong&gt; section ("Used for direct Postgres
connections").&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Reset password&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What happens automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;postgres&lt;/code&gt; role password is rotated.&lt;/li&gt;
&lt;li&gt;Supabase-managed services (PostgREST, PgBouncer, Realtime, GoTrue, Studio)
pick up the new password with no downtime.&lt;/li&gt;
&lt;li&gt;The connection string shown in the Dashboard updates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What does &lt;strong&gt;not&lt;/strong&gt; happen automatically, and this is where production&lt;br&gt;
breaks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hardcoded connection strings in your Next.js app do not update.&lt;/strong&gt; If
your &lt;code&gt;DATABASE_URL&lt;/code&gt; env var has the old password baked in, the app starts
failing with &lt;code&gt;password authentication failed for user "postgres"&lt;/code&gt; on the
next connection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI secrets do not update.&lt;/strong&gt; GitHub Actions secrets holding the old
password keep sending it until you rotate them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.pgpass&lt;/code&gt; files on your machine do not update.&lt;/strong&gt; &lt;code&gt;psql&lt;/code&gt; will fall back
to prompting or fail non-interactively.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External services (cron jobs, n8n workflows, backup scripts) do not
update.&lt;/strong&gt; Every one of them breaks on the next connection attempt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The password is not retrievable after reset — Supabase does not store it in&lt;br&gt;
cleartext. Save it somewhere safe the first time.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;code&gt;password_encryption&lt;/code&gt;: scram-sha-256 vs md5
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.postgresql.org/docs/current/sql-alterrole.html" rel="noopener noreferrer"&gt;PostgreSQL &lt;code&gt;ALTER ROLE&lt;/code&gt; docs&lt;/a&gt; state that the password&lt;br&gt;
is encrypted with the algorithm set by &lt;code&gt;password_encryption&lt;/code&gt;. Since&lt;br&gt;
PostgreSQL 13 the default is &lt;code&gt;scram-sha-256&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;SHOW&lt;/span&gt; &lt;span class="n"&gt;password_encryption&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;--  password_encryption&lt;/span&gt;
&lt;span class="c1"&gt;-- ---------------------&lt;/span&gt;
&lt;span class="c1"&gt;--  scram-sha-256&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the value is &lt;code&gt;md5&lt;/code&gt;, your cluster is running a pre-13 config or someone&lt;br&gt;
changed it. You can change it for the current session:&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;SET&lt;/span&gt; &lt;span class="n"&gt;password_encryption&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'scram-sha-256'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;alice&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="s1"&gt;'hunter2'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;RESET&lt;/span&gt; &lt;span class="n"&gt;password_encryption&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A password set while &lt;code&gt;password_encryption = 'scram-sha-256'&lt;/code&gt; is stored as a&lt;br&gt;
SCRAM verifier in &lt;code&gt;pg_authid.rolpassword&lt;/code&gt; (starts with &lt;code&gt;SCRAM-SHA-256$&lt;/code&gt;). A&lt;br&gt;
password set while &lt;code&gt;password_encryption = 'md5'&lt;/code&gt; is stored as an md5 hash&lt;br&gt;
(starts with &lt;code&gt;md5&lt;/code&gt;). A role with an md5 password cannot authenticate to a&lt;br&gt;
server that requires &lt;code&gt;scram-sha-256&lt;/code&gt; in &lt;code&gt;pg_hba.conf&lt;/code&gt;, and vice versa —&lt;br&gt;
this is the most common cause of&lt;br&gt;
&lt;a href="https://www.iloveblogs.blog/post/postgres-peer-authentication-failed-fix" rel="noopener noreferrer"&gt;password authentication failed&lt;/a&gt;&lt;br&gt;
after a migration.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why authentication breaks after a password change
&lt;/h2&gt;

&lt;p&gt;After you run &lt;code&gt;ALTER ROLE alice WITH PASSWORD 'newpass'&lt;/code&gt;, every existing&lt;br&gt;
connection &lt;strong&gt;stays open&lt;/strong&gt; until it disconnects — the password is only&lt;br&gt;
checked at connection time. The breakage starts on the next connection&lt;br&gt;
attempt from anywhere that still has the old password:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Your Next.js app.&lt;/strong&gt; The &lt;code&gt;DATABASE_URL&lt;/code&gt; env var in Vercel/Cloudflare has
the old password. The Next time the app opens a new connection (which
happens on cold start or when the pool expands), it fails. Update the
env var and redeploy. The
&lt;a href="https://www.iloveblogs.blog/guides/supabase-connection-pooling-vercel" rel="noopener noreferrer"&gt;Supabase connection pooling guide&lt;/a&gt;
covers where this env var belongs in a Vercel deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your CI pipeline.&lt;/strong&gt; GitHub Actions secrets still hold the old
password. Migration jobs fail with &lt;code&gt;password authentication failed for
user "postgres"&lt;/code&gt;. Rotate the secret.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.pgpass&lt;/code&gt;.&lt;/strong&gt; &lt;code&gt;~/.pgpass&lt;/code&gt; has the old password on a line like
&lt;code&gt;db.x.supabase.co:5432:*:postgres:oldpass&lt;/code&gt;. Update it, or delete the line
and let psql prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External services.&lt;/strong&gt; Any n8n workflow, cron job, or backup script with
a hardcoded connection string must be updated.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The safe rotation sequence is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add a &lt;strong&gt;new&lt;/strong&gt; role with the new password (or reset the existing one in
Supabase).&lt;/li&gt;
&lt;li&gt;Update every consumer (app, CI, &lt;code&gt;.pgpass&lt;/code&gt;, external services) to the new
password.&lt;/li&gt;
&lt;li&gt;Redeploy / reconnect.&lt;/li&gt;
&lt;li&gt;Only then, if you rotated by creating a new role, revoke the old role.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For the &lt;code&gt;postgres&lt;/code&gt; role in Supabase you cannot create a parallel role —&lt;br&gt;
Supabase owns &lt;code&gt;postgres&lt;/code&gt;. The sequence is: reset in the Dashboard, update&lt;br&gt;
every consumer, redeploy.&lt;/p&gt;
&lt;h2&gt;
  
  
  The &lt;code&gt;.pgpass&lt;/code&gt; file
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;~/.pgpass&lt;/code&gt; is the psql way to avoid typing passwords. Each line is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hostname:port:database:username:password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Supabase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db.abcdefghijklmnop.supabase.co:5432:*:postgres:your-new-password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a password change, edit the line. If you used the Supabase Dashboard&lt;br&gt;
reset and your password contains special characters (&lt;code&gt;:&lt;/code&gt;, &lt;code&gt;@&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;, etc.),&lt;br&gt;
you must percent-encode them in a connection string (&lt;code&gt;postgres://...&lt;/code&gt;) but&lt;br&gt;
&lt;strong&gt;not&lt;/strong&gt; in &lt;code&gt;.pgpass&lt;/code&gt; — &lt;code&gt;.pgpass&lt;/code&gt; stores the literal password.&lt;/p&gt;
&lt;h2&gt;
  
  
  Production recommendations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prefer &lt;code&gt;\password&lt;/code&gt; over &lt;code&gt;ALTER ROLE ... PASSWORD&lt;/code&gt; in psql.&lt;/strong&gt; The
cleartext never hits history or logs. This is the single most important
habit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use scram-sha-256.&lt;/strong&gt; It is the default since PostgreSQL 13; do not
downgrade to md5. If &lt;code&gt;SHOW password_encryption;&lt;/code&gt; returns &lt;code&gt;md5&lt;/code&gt;, investigate
why — it usually means the cluster was initialized on an old config.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotate the &lt;code&gt;postgres&lt;/code&gt; role from the Supabase Dashboard, not from SQL.&lt;/strong&gt;
The Dashboard reset rotates managed services for you; an &lt;code&gt;ALTER ROLE
postgres&lt;/code&gt; in the SQL editor does not, and you will spend the next hour
debugging why PostgREST 401s.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update every consumer before you rotate, or accept a brief outage.&lt;/strong&gt;
The only safe zero-downtime rotation for a single-role setup is to create
a new role, switch consumers to it, then revoke the old one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Percent-encode special characters in connection strings.&lt;/strong&gt; A password
like &lt;code&gt;p@ss:w/ord&lt;/code&gt; must become &lt;code&gt;p%40ss%3Aw%2Ford&lt;/code&gt; in a &lt;code&gt;postgres://&lt;/code&gt; URL.
In &lt;code&gt;.pgpass&lt;/code&gt; it stays literal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit &lt;code&gt;pg_authid.rolpassword&lt;/code&gt; after rotation.&lt;/strong&gt; Every active role's
password should start with &lt;code&gt;SCRAM-SHA-256$&lt;/code&gt;. Anything starting with &lt;code&gt;md5&lt;/code&gt;
is a role that has not had its password reset since before the
&lt;code&gt;scram-sha-256&lt;/code&gt; default — a finding worth noting in a
&lt;a href="https://www.iloveblogs.blog/guides/nextjs-supabase-security-best-practices" rel="noopener noreferrer"&gt;security best-practices review&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do not store the password in the repo.&lt;/strong&gt; Use env vars and a secret
manager. If a password ends up in git, rotate it immediately — &lt;code&gt;git
filter-repo&lt;/code&gt; is not a substitute for rotation, because the hash is
already public.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Common pitfalls
&lt;/h2&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;ALTER USER&lt;/code&gt; vs &lt;code&gt;ALTER ROLE&lt;/code&gt; confusion
&lt;/h3&gt;

&lt;p&gt;They are the same. If you find old scripts using &lt;code&gt;ALTER USER&lt;/code&gt;, they work;&lt;br&gt;
&lt;code&gt;ALTER USER&lt;/code&gt; is a legacy alias. Update them to &lt;code&gt;ALTER ROLE&lt;/code&gt; for clarity but&lt;br&gt;
do not treat it as a bug.&lt;/p&gt;
&lt;h3&gt;
  
  
  Password change does not kick existing sessions
&lt;/h3&gt;

&lt;p&gt;Existing connections stay open. If you are rotating because a password&lt;br&gt;
leaked, you must also terminate existing sessions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pg_terminate_backend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_stat_activity&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;usename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'alice'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pg_backend_pid&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Otherwise a session opened before the rotation keeps running with the old&lt;br&gt;
auth context until it disconnects. For a production rotation triggered by a&lt;br&gt;
credential leak, run the &lt;code&gt;pg_terminate_backend&lt;/code&gt; query immediately after the&lt;br&gt;
&lt;code&gt;ALTER ROLE&lt;/code&gt; so no attacker holds an open session with the old password.&lt;/p&gt;

&lt;h3&gt;
  
  
  md5-to-scram migration leaves roles unable to connect
&lt;/h3&gt;

&lt;p&gt;If you change &lt;code&gt;password_encryption&lt;/code&gt; to &lt;code&gt;scram-sha-256&lt;/code&gt; and a role has an&lt;br&gt;
md5 password, that role cannot authenticate through a &lt;code&gt;scram-sha-256&lt;/code&gt;&lt;br&gt;
&lt;code&gt;pg_hba.conf&lt;/code&gt; entry. Re-set the password with &lt;code&gt;ALTER ROLE ... PASSWORD&lt;/code&gt;&lt;br&gt;
after switching &lt;code&gt;password_encryption&lt;/code&gt;, so the new verifier is stored as&lt;br&gt;
SCRAM. The same trap in reverse is why&lt;br&gt;
&lt;a href="https://www.iloveblogs.blog/post/postgres-peer-authentication-failed-fix" rel="noopener noreferrer"&gt;peer authentication failed&lt;/a&gt;&lt;br&gt;
appears after a version upgrade.&lt;/p&gt;

&lt;h3&gt;
  
  
  Supabase connection pool keeps old password
&lt;/h3&gt;

&lt;p&gt;Supabase's PgBouncer pool is managed and rotates automatically on a&lt;br&gt;
Dashboard reset — you do not need to restart the pooler yourself. But&lt;br&gt;
if you use a direct (non-pooled) connection string in your app, you bypass&lt;br&gt;
PgBouncer — the app authenticates to Postgres directly, and a stale&lt;br&gt;
connection string fails. Use the pooled connection string&lt;br&gt;
(&lt;code&gt;*.pooler.supabase.com:6543&lt;/code&gt;) for the app and the direct string only for&lt;br&gt;
migrations. The&lt;br&gt;
&lt;a href="https://www.iloveblogs.blog/guides/supabase-connection-pooling-vercel" rel="noopener noreferrer"&gt;connection pooling guide&lt;/a&gt;&lt;br&gt;
has the exact env vars.&lt;/p&gt;

&lt;h3&gt;
  
  
  Concurrent &lt;code&gt;ALTER ROLE&lt;/code&gt; during a migration
&lt;/h3&gt;

&lt;p&gt;Two migrations changing the same role's password at the same time race.&lt;br&gt;
The second one wins; the first password is lost. If you run migrations in&lt;br&gt;
parallel, serialize password changes. For the broader concurrency story —&lt;br&gt;
serializable transactions and &lt;code&gt;could not serialize access&lt;/code&gt; — see&lt;br&gt;
&lt;a href="https://www.iloveblogs.blog/post/postgres-could-not-serialize-access-concurrent-update-supabase" rel="noopener noreferrer"&gt;could not serialize access: concurrent update in Supabase&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do I change the &lt;code&gt;postgres&lt;/code&gt; password without downtime?
&lt;/h3&gt;

&lt;p&gt;In a single-role Supabase setup you cannot — the reset is atomic and&lt;br&gt;
managed connections rotate, but hardcoded connection strings break on the&lt;br&gt;
next reconnect. The zero-downtime pattern is: create a new role with the&lt;br&gt;
new password, switch every consumer to it, then revoke the old role. For&lt;br&gt;
the Supabase-managed &lt;code&gt;postgres&lt;/code&gt; role, use the Dashboard reset and update&lt;br&gt;
every consumer immediately after.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does &lt;code&gt;\password&lt;/code&gt; do that &lt;code&gt;ALTER ROLE&lt;/code&gt; does not?
&lt;/h3&gt;

&lt;p&gt;It prompts interactively, sends the password to the server already&lt;br&gt;
encrypted by the client, and does not write the cleartext to&lt;br&gt;
&lt;code&gt;.psql_history&lt;/code&gt; or the server log. Use it for any interactive password&lt;br&gt;
change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I see the current password?
&lt;/h3&gt;

&lt;p&gt;No. PostgreSQL stores a one-way hash (SCRAM-SHA-256 verifier or md5) in&lt;br&gt;
&lt;code&gt;pg_authid.rolpassword&lt;/code&gt;. You can read the hash but you cannot reverse it&lt;br&gt;
to the cleartext. If you lost the password, reset it — there is no recovery.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I use &lt;code&gt;ALTER USER&lt;/code&gt; or &lt;code&gt;ALTER ROLE&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ALTER ROLE&lt;/code&gt;. &lt;code&gt;ALTER USER&lt;/code&gt; is a legacy alias that works identically, but&lt;br&gt;
&lt;code&gt;ALTER ROLE&lt;/code&gt; is the canonical command in the PostgreSQL documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/post/postgres-peer-authentication-failed-fix" rel="noopener noreferrer"&gt;Postgres peer authentication failed fix&lt;/a&gt; — the error you get after a password change with a stale &lt;code&gt;pg_hba.conf&lt;/code&gt; or md5/scram mismatch.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/post/postgres-describe-table-equivalent" rel="noopener noreferrer"&gt;PostgreSQL DESCRIBE TABLE: the psql &lt;code&gt;\d&lt;/code&gt; equivalent&lt;/a&gt; — the sibling psql-meta-command reference for inspecting a table's schema.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/guides/supabase-connection-pooling-vercel" rel="noopener noreferrer"&gt;Supabase connection pooling on Vercel&lt;/a&gt; — where the password lives in a Vercel deployment and the pooled vs direct connection strings.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/guides/nextjs-supabase-security-best-practices" rel="noopener noreferrer"&gt;Next.js + Supabase security best practices&lt;/a&gt; — where password rotation fits into a broader production security review.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/post/postgres-could-not-serialize-access-concurrent-update-supabase" rel="noopener noreferrer"&gt;Could not serialize access: concurrent update in Supabase&lt;/a&gt; — the concurrency side of running migrations and role changes in parallel.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/post/how-can-i-create-an-enum-column-in-supabase" rel="noopener noreferrer"&gt;How to create an enum column in Supabase&lt;/a&gt; — another common Supabase SQL-editor operation that differs from raw Postgres.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.iloveblogs.blog/post/how-to-change-postgresql-user-password-alter-role-supabase" rel="noopener noreferrer"&gt;https://www.iloveblogs.blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>supabase</category>
      <category>security</category>
      <category>authentication</category>
    </item>
    <item>
      <title>Supabase redirectTo not working on Vercel previews: SITE_URL fix</title>
      <dc:creator>Mahdi BEN RHOUMA</dc:creator>
      <pubDate>Thu, 02 Jul 2026 16:13:40 +0000</pubDate>
      <link>https://dev.to/mahdi_benrhouma_fe1c6005/supabase-redirectto-not-working-on-vercel-previews-siteurl-fix-2b67</link>
      <guid>https://dev.to/mahdi_benrhouma_fe1c6005/supabase-redirectto-not-working-on-vercel-previews-siteurl-fix-2b67</guid>
      <description>&lt;p&gt;This is the auth bug that only appears when a teammate opens a Vercel preview link and tries to log in. The error from Supabase: &lt;code&gt;redirect_uri_mismatch&lt;/code&gt; or the redirect silently goes to production instead of the preview.&lt;/p&gt;

&lt;p&gt;I hit this while QA-ing an OAuth flow. Everything worked fine on production. Preview deployments got a blank screen after the OAuth callback — because Supabase was sending the user to &lt;code&gt;https://myapp.com/auth/callback&lt;/code&gt; instead of &lt;code&gt;https://myapp-git-feature-team.vercel.app/auth/callback&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Supabase redirect URLs work
&lt;/h2&gt;

&lt;p&gt;Supabase maintains an allowlist of permitted redirect destinations. When you pass a &lt;code&gt;redirectTo&lt;/code&gt; in your auth calls, Supabase validates it against the list. If the URL is not in the list, the request fails.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This redirectTo will fail if the current preview URL is not in the allowlist&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;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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;signInWithOAuth&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;github&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;redirectTo&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;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/auth/callback`&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;&lt;code&gt;window.location.origin&lt;/code&gt; on a preview deployment returns something like &lt;code&gt;https://myapp-git-feature-branch-myteam.vercel.app&lt;/code&gt; — which does not match any pattern in the list.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dashboard config
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Location:&lt;/strong&gt; Supabase Dashboard → Authentication → URL Configuration&lt;/p&gt;

&lt;p&gt;You need three things configured:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Site URL&lt;/strong&gt; (single value):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://yourdomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the fallback URL for emails (confirmation, password reset) when no &lt;code&gt;redirectTo&lt;/code&gt; is specified. In production this must be your real domain, not &lt;code&gt;http://localhost:3000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redirect URLs&lt;/strong&gt; (allowlist):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;http&lt;/span&gt;://&lt;span class="n"&gt;localhost&lt;/span&gt;:&lt;span class="m"&gt;3000&lt;/span&gt;/**
&lt;span class="n"&gt;https&lt;/span&gt;://&lt;span class="n"&gt;yourdomain&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;/**
&lt;span class="n"&gt;https&lt;/span&gt;://*-&lt;span class="n"&gt;your&lt;/span&gt;-&lt;span class="n"&gt;team&lt;/span&gt;-&lt;span class="n"&gt;slug&lt;/span&gt;.&lt;span class="n"&gt;vercel&lt;/span&gt;.&lt;span class="n"&gt;app&lt;/span&gt;/**
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;**&lt;/code&gt; wildcard matches any sequence of characters. The pattern &lt;code&gt;https://*-your-team-slug.vercel.app/**&lt;/code&gt; covers every preview deployment URL that Vercel generates for your team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to find your team slug:&lt;/strong&gt; in Vercel, go to Settings → General → URL Slug. Your preview URLs follow the format &lt;code&gt;&amp;lt;project&amp;gt;-&amp;lt;branch&amp;gt;-&amp;lt;slug&amp;gt;.vercel.app&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The environment variable pattern for Vercel
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;redirectTo&lt;/code&gt; in your code must be dynamic — it should use the actual URL of the current deployment, not a hardcoded production URL.&lt;/p&gt;

&lt;p&gt;The recommended pattern from the Supabase docs:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```js filename="lib/utils.js"&lt;br&gt;
export function getSiteURL() {&lt;br&gt;
  let url =&lt;br&gt;
    process?.env?.NEXT_PUBLIC_SITE_URL ??&lt;br&gt;
    process?.env?.NEXT_PUBLIC_VERCEL_URL ??&lt;br&gt;
    '&lt;a href="http://localhost:3000/" rel="noopener noreferrer"&gt;http://localhost:3000/&lt;/a&gt;'&lt;/p&gt;

&lt;p&gt;// Vercel provides NEXT_PUBLIC_VERCEL_URL without https://&lt;br&gt;
  url = url.startsWith('http') ? url : &lt;code&gt;https://${url}&lt;/code&gt;&lt;br&gt;
  // Ensure trailing slash&lt;br&gt;
  url = url.endsWith('/') ? url : &lt;code&gt;${url}/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;return url&lt;br&gt;
}&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Then use it when calling auth:



```js filename="app/auth/actions.js"
import { createClient } from '@/lib/supabase/server'
import { getSiteURL } from '@/lib/utils'

export async function signInWithGitHub() {
  const supabase = await createClient()
  const { data, error } = await supabase.auth.signInWithOAuth({
    provider: 'github',
    options: {
      redirectTo: `${getSiteURL()}auth/callback`,
    },
  })

  if (data.url) redirect(data.url)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;In Vercel environment variables:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Environment&lt;/th&gt;
&lt;th&gt;&lt;code&gt;NEXT_PUBLIC_SITE_URL&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Production&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://yourdomain.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preview&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(leave unset — falls back to NEXT_PUBLIC_VERCEL_URL)&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Development&lt;/td&gt;
&lt;td&gt;&lt;code&gt;http://localhost:3000&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;NEXT_PUBLIC_VERCEL_URL&lt;/code&gt; is automatically set by Vercel on every deployment with the correct preview URL. You do not need to set it yourself — but you do need &lt;code&gt;NEXT_PUBLIC_SITE_URL&lt;/code&gt; in production to override the Vercel URL with your custom domain.&lt;/p&gt;
&lt;h2&gt;
  
  
  Email templates: the hidden SITE_URL problem
&lt;/h2&gt;

&lt;p&gt;Password reset and magic link emails use your &lt;code&gt;SITE_URL&lt;/code&gt; by default in the template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Default template — uses SITE_URL, ignores redirectTo --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{{ .SiteURL }}/auth/confirm?..."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Confirm your email&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your SITE_URL is &lt;code&gt;http://localhost:3000&lt;/code&gt; (the Supabase default before you change it), production password reset emails will send users to localhost.&lt;/p&gt;

&lt;p&gt;The fix: update your email templates to use &lt;code&gt;{{ .RedirectTo }}&lt;/code&gt; instead of &lt;code&gt;{{ .SiteURL }}&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Updated template — uses dynamic redirectTo --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{{ .RedirectTo }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Confirm your email&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Location in Supabase Dashboard:&lt;/strong&gt; Authentication → Email Templates&lt;/p&gt;

&lt;h2&gt;
  
  
  The trailing slash trap
&lt;/h2&gt;

&lt;p&gt;Supabase URL matching is exact. &lt;code&gt;https://yourdomain.com&lt;/code&gt; and &lt;code&gt;https://yourdomain.com/&lt;/code&gt; are different entries.&lt;/p&gt;

&lt;p&gt;If your &lt;code&gt;redirectTo&lt;/code&gt; is &lt;code&gt;https://yourdomain.com/auth/callback&lt;/code&gt; and your allowlist only has &lt;code&gt;https://yourdomain.com&lt;/code&gt; (no trailing slash, no wildcard), it will fail.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/**&lt;/code&gt; wildcard suffix is the safest way to avoid this: &lt;code&gt;https://yourdomain.com/**&lt;/code&gt; matches any path on that domain including &lt;code&gt;/auth/callback&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the fix is not just a URL allowlist
&lt;/h2&gt;

&lt;p&gt;If previews work but production email redirects still go to the wrong URL, the problem is usually the email template using &lt;code&gt;{{ .SiteURL }}&lt;/code&gt; instead of &lt;code&gt;{{ .RedirectTo }}&lt;/code&gt; — not the allowlist. Allowlist errors return an explicit error. Wrong-URL-in-email is silent: the user just lands somewhere unexpected.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;window.location.origin&lt;/code&gt; in your &lt;code&gt;getSiteURL()&lt;/code&gt; helper returns undefined or an unexpected value server-side (it is a browser API), that is why — use &lt;code&gt;NEXT_PUBLIC_SITE_URL&lt;/code&gt; or &lt;code&gt;NEXT_PUBLIC_VERCEL_URL&lt;/code&gt; env vars instead, which work on the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Supabase Dashboard → Authentication → URL Configuration: Site URL set to production domain (not localhost).&lt;/li&gt;
&lt;li&gt;[ ] Redirect URLs allowlist includes &lt;code&gt;http://localhost:3000/**&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] Redirect URLs allowlist includes &lt;code&gt;https://yourdomain.com/**&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] Redirect URLs allowlist includes &lt;code&gt;https://*-your-team.vercel.app/**&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;NEXT_PUBLIC_SITE_URL&lt;/code&gt; set to production domain in Vercel production environment.&lt;/li&gt;
&lt;li&gt;[ ] Email templates updated to use &lt;code&gt;{{ .RedirectTo }}&lt;/code&gt; not &lt;code&gt;{{ .SiteURL }}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] Test: open a fresh Vercel preview URL, click "Forgot password", verify email link goes to the preview URL (not production).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/post/supabase-auth-redirect-fix" rel="noopener noreferrer"&gt;Supabase auth redirect fix&lt;/a&gt; — related redirect issues with email confirmation and OAuth.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/post/supabase-email-confirmation-fix" rel="noopener noreferrer"&gt;Supabase email confirmation fix&lt;/a&gt; — email template issues that break the confirmation flow.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/post/magic-link-production-checklist" rel="noopener noreferrer"&gt;Magic link production checklist&lt;/a&gt; — covers the SITE_URL and email template setup for magic link auth.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/guides/deploying-nextjs-supabase-production" rel="noopener noreferrer"&gt;Deploying Next.js and Supabase to production&lt;/a&gt; — full production setup including environment variables per environment.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.iloveblogs.blog/post/fix-supabase-redirectto-not-working-vercel-preview" rel="noopener noreferrer"&gt;https://www.iloveblogs.blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>authentication</category>
      <category>vercel</category>
      <category>deployment</category>
    </item>
    <item>
      <title>Supabase getClaims() vs getSession() in server code: the silent auth bug</title>
      <dc:creator>Mahdi BEN RHOUMA</dc:creator>
      <pubDate>Thu, 02 Jul 2026 16:13:33 +0000</pubDate>
      <link>https://dev.to/mahdi_benrhouma_fe1c6005/supabase-getclaims-vs-getsession-in-server-code-the-silent-auth-bug-2i81</link>
      <guid>https://dev.to/mahdi_benrhouma_fe1c6005/supabase-getclaims-vs-getsession-in-server-code-the-silent-auth-bug-2i81</guid>
      <description>&lt;p&gt;I found this bug during a security audit of a client app. The dashboard was "protected" — a check at the top of every Server Component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&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;session&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;getSession&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;session&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="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="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem: a user could tamper with the cookie, swap in an expired token, and the session check still passed. The page loaded. The data was there.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;getSession()&lt;/code&gt; reads from cookie storage. It does not verify the token is still valid.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Supabase docs actually say
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://supabase.com/docs/guides/auth/server-side/nextjs" rel="noopener noreferrer"&gt;Supabase SSR auth guide&lt;/a&gt; is explicit:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Never trust &lt;code&gt;supabase.auth.getSession()&lt;/code&gt; inside server code such as Proxy. It isn't guaranteed to revalidate the Auth token."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The reason: &lt;code&gt;getSession()&lt;/code&gt; loads the session object (access token, refresh token, expiry) &lt;strong&gt;directly from local storage&lt;/strong&gt; — which in Next.js SSR means from the cookie. It returns whatever is in the cookie without checking signatures or expiry against the Auth server.&lt;/p&gt;

&lt;p&gt;This is fine on the client where the session is managed by the SDK. It is a security hole on the server where you are making trust decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three auth functions and when to use each
&lt;/h2&gt;

&lt;h3&gt;
  
  
  getClaims() — for auth checks in server code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&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="nx"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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;getClaims&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validates the JWT signature locally using WebCrypto API against the project's cached JWKS (public keys). Returns the decoded JWT claims without a network call. The Supabase docs: "It's safe to trust &lt;code&gt;getClaims()&lt;/code&gt; because it validates the JWT signature against the project's published public keys every time."&lt;/p&gt;

&lt;p&gt;Use this in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;proxy.ts&lt;/code&gt; / &lt;code&gt;proxy.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Server Components&lt;/li&gt;
&lt;li&gt;Server Actions (&lt;code&gt;"use server"&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Route Handlers&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  getUser() — when you need the freshest user record
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&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="nx"&gt;error&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Makes a network call to the Auth server. Returns the most current user object, including any metadata changes since the token was issued. Slower than &lt;code&gt;getClaims()&lt;/code&gt; but authoritative.&lt;/p&gt;

&lt;p&gt;Use this when you need to show or act on user metadata that might have changed after login (role changes, email verification status).&lt;/p&gt;

&lt;h3&gt;
  
  
  getSession() — only safe on the client
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ Fine in a Client Component&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;session&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;getSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the client, the Supabase SDK manages session refresh automatically. The session in local storage is kept current. The risk of stale data is low.&lt;/p&gt;

&lt;p&gt;On the server, the session comes from a cookie that the server cannot refresh — and that you cannot trust without signature validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The correct pattern for server-side auth
&lt;/h2&gt;



&lt;p&gt;```js filename="proxy.ts"&lt;br&gt;
import { createServerClient } from '@supabase/ssr'&lt;br&gt;
import { NextResponse } from 'next/server'&lt;br&gt;
import type { NextRequest } from 'next/server'&lt;/p&gt;

&lt;p&gt;export async function proxy(request: NextRequest) {&lt;br&gt;
  let supabaseResponse = NextResponse.next({ request })&lt;/p&gt;

&lt;p&gt;const supabase = createServerClient(&lt;br&gt;
    process.env.NEXT_PUBLIC_SUPABASE_URL!,&lt;br&gt;
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,&lt;br&gt;
    {&lt;br&gt;
      cookies: {&lt;br&gt;
        getAll() { return request.cookies.getAll() },&lt;br&gt;
        setAll(cookiesToSet) {&lt;br&gt;
          cookiesToSet.forEach(({ name, value }) =&amp;gt;&lt;br&gt;
            request.cookies.set(name, value))&lt;br&gt;
          supabaseResponse = NextResponse.next({ request })&lt;br&gt;
          cookiesToSet.forEach(({ name, value, options }) =&amp;gt;&lt;br&gt;
            supabaseResponse.cookies.set(name, value, options))&lt;br&gt;
        },&lt;br&gt;
      },&lt;br&gt;
    }&lt;br&gt;
  )&lt;/p&gt;

&lt;p&gt;// ✅ getClaims() — validates JWT locally, no network call&lt;br&gt;
  const { data: claims } = await supabase.auth.getClaims()&lt;/p&gt;

&lt;p&gt;if (!claims &amp;amp;&amp;amp; !request.nextUrl.pathname.startsWith('/login')) {&lt;br&gt;
    const url = request.nextUrl.clone()&lt;br&gt;
    url.pathname = '/login'&lt;br&gt;
    return NextResponse.redirect(url)&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;return supabaseResponse&lt;br&gt;
}&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;




```js filename="app/dashboard/page.js"
import { createClient } from '@/lib/supabase/server'
import { redirect } from 'next/navigation'

export default async function Dashboard() {
  const supabase = await createClient()

  // ✅ getClaims() in a Server Component
  const { data: claims } = await supabase.auth.getClaims()

  if (!claims) {
    redirect('/login')
  }

  const userId = claims.sub

  const { data: profile } = await supabase
    .from('profiles')
    .select('*')
    .eq('id', userId)
    .single()

  return &amp;lt;div&amp;gt;{profile.name}&amp;lt;/div&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  The CDN / ISR cache leak
&lt;/h2&gt;

&lt;p&gt;This is a separate but related danger. The Supabase SSR docs warn:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Caching of HTTP responses can cause users to receive another user's session."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When Next.js ISR or a CDN caches a response that includes &lt;code&gt;Set-Cookie&lt;/code&gt; headers (the Supabase session cookie), a subsequent user requesting the same cached response receives those headers — effectively signed in as the previous user.&lt;/p&gt;

&lt;p&gt;The fix: &lt;strong&gt;never cache authenticated responses at the CDN layer&lt;/strong&gt;. Add cache control headers to any response that touches auth:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```js filename="proxy.ts"&lt;br&gt;
supabaseResponse.headers.set('Cache-Control', 'private, no-store')&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Or set it in `next.config.mjs` for all authenticated routes:



```js filename="next.config.mjs"
async headers() {
  return [
    {
      source: '/dashboard/:path*',
      headers: [{ key: 'Cache-Control', value: 'private, no-store' }],
    },
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  When getClaims() is not enough
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;getClaims()&lt;/code&gt; validates the JWT signature but it trusts the JWT's expiry claim. If you have revoked a user's session on the Supabase side (admin dashboard, logout from all devices), the JWT may still be valid until it expires (typically 1 hour).&lt;/p&gt;

&lt;p&gt;For critical operations — changing passwords, processing payments, deleting accounts — use &lt;code&gt;getUser()&lt;/code&gt; which makes a live check against the Auth server:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```js filename="app/account/delete/actions.js"&lt;br&gt;
'use server'&lt;/p&gt;

&lt;p&gt;import { createClient } from '@/lib/supabase/server'&lt;/p&gt;

&lt;p&gt;export async function deleteAccount() {&lt;br&gt;
  const supabase = await createClient()&lt;/p&gt;

&lt;p&gt;// For destructive operations: use getUser() for a live auth check&lt;br&gt;
  const { data: { user }, error } = await supabase.auth.getUser()&lt;/p&gt;

&lt;p&gt;if (error || !user) {&lt;br&gt;
    throw new Error('Unauthorized')&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;await supabase.from('profiles').delete().eq('id', user.id)&lt;br&gt;
}&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


## Checklist

- [ ] Replace all `getSession()` calls in server code with `getClaims()`.
- [ ] Replace `getSession()` in Server Actions with `getClaims()`.
- [ ] For critical mutations: use `getUser()` instead of `getClaims()`.
- [ ] Ensure authenticated page responses include `Cache-Control: private, no-store`.
- [ ] Do not cache any response that includes a `Set-Cookie` auth header at the CDN layer.
- [ ] Verify: an expired or tampered cookie causes a redirect to `/login`, not a successful page load.

## Related

- [Supabase auth complete session and middleware guide](https://www.iloveblogs.blog/guides/supabase-auth-complete-session-middleware-guide) — the full wiring of Supabase SSR with Next.js, including the cookies configuration.
- [Handle Supabase auth errors in Next.js middleware](https://www.iloveblogs.blog/post/handle-supabase-auth-errors-middleware) — error handling patterns for the auth layer.
- [Supabase auth production: 11 lessons](https://www.iloveblogs.blog/post/supabase-auth-production-year-11-lessons) — broader production auth lessons from running Supabase in production.
- [Supabase RLS silent failures](https://www.iloveblogs.blog/post/supabase-rls-silent-failures-debug) — the companion bug: your auth passes but your data queries return nothing.


---
*Originally published at [https://www.iloveblogs.blog](https://www.iloveblogs.blog/post/fix-supabase-getclaims-vs-getsession-server-auth)*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>supabase</category>
      <category>authentication</category>
      <category>servercomponents</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Next.js proxy.js matcher not working: static assets and auth gaps</title>
      <dc:creator>Mahdi BEN RHOUMA</dc:creator>
      <pubDate>Thu, 02 Jul 2026 13:51:51 +0000</pubDate>
      <link>https://dev.to/mahdi_benrhouma_fe1c6005/nextjs-proxyjs-matcher-not-working-static-assets-and-auth-gaps-1j7l</link>
      <guid>https://dev.to/mahdi_benrhouma_fe1c6005/nextjs-proxyjs-matcher-not-working-static-assets-and-auth-gaps-1j7l</guid>
      <description>&lt;p&gt;When I migrated from &lt;code&gt;middleware.ts&lt;/code&gt; to &lt;code&gt;proxy.ts&lt;/code&gt; after upgrading to Next.js 16, my staging environment immediately broke. Static assets — CSS bundles, JS chunks, optimized images — stopped loading. Everything returned a 302 redirect to the login page.&lt;/p&gt;

&lt;p&gt;The cause: I copied the auth logic but did not update the matcher. Without the right matcher, &lt;code&gt;proxy.js&lt;/code&gt; runs on &lt;strong&gt;every request&lt;/strong&gt;, including the requests your browser makes to load the page itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed in Next.js 16
&lt;/h2&gt;

&lt;p&gt;In Next.js 16.0.0, the &lt;code&gt;middleware&lt;/code&gt; file convention was deprecated and renamed to &lt;code&gt;proxy&lt;/code&gt;. Per the &lt;a href="https://nextjs.org/docs/app/api-reference/file-conventions/proxy" rel="noopener noreferrer"&gt;official docs (v16.2.9)&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;File: &lt;code&gt;middleware.ts&lt;/code&gt; → &lt;code&gt;proxy.ts&lt;/code&gt; (or &lt;code&gt;.js&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Function: &lt;code&gt;export function middleware()&lt;/code&gt; → &lt;code&gt;export function proxy()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Type: &lt;code&gt;NextProxy&lt;/code&gt; (new type, replaces inline function typing)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The codemod handles the rename:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @next/codemod@canary middleware-to-proxy &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API — &lt;code&gt;NextRequest&lt;/code&gt;, &lt;code&gt;NextResponse&lt;/code&gt;, the &lt;code&gt;config.matcher&lt;/code&gt; shape — is unchanged.&lt;/p&gt;

&lt;h2&gt;
  
  
  The matcher problem
&lt;/h2&gt;

&lt;p&gt;The most common issue after migration: forgetting to scope the matcher. The docs are explicit:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Without a matcher, Proxy runs on &lt;strong&gt;every request&lt;/strong&gt;, including static files (&lt;code&gt;_next/static&lt;/code&gt;), image optimizations (&lt;code&gt;_next/image&lt;/code&gt;), and assets in the &lt;code&gt;public/&lt;/code&gt; folder."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So this naive proxy:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```js filename="proxy.js"&lt;br&gt;
import { NextResponse } from 'next/server'&lt;/p&gt;

&lt;p&gt;export function proxy(request) {&lt;br&gt;
  const session = request.cookies.get('session')&lt;br&gt;
  if (!session) {&lt;br&gt;
    return NextResponse.redirect(new URL('/login', request.url))&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


...will redirect unauthenticated requests for `/_next/static/chunks/main.js` to `/login`. Your page loads with no CSS, no JavaScript, and no images.

## The recommended negative matcher

Exclude paths that should never touch proxy:



```js filename="proxy.js"
import { NextResponse } from 'next/server'

export function proxy(request) {
  const session = request.cookies.get('session')
  if (!session) {
    return NextResponse.redirect(new URL('/login', request.url))
  }
  return NextResponse.next()
}

export const config = {
  matcher: [
    /*
     * Match all paths EXCEPT:
     * - _next/static  (JS/CSS bundles)
     * - _next/image   (image optimization)
     * - api/          (API routes)
     * - favicon, sitemap, robots
     * - public assets (.png, .svg, .ico etc.)
     */
    '/((?!api|_next/static|_next/image|favicon\\.ico|sitemap\\.xml|robots\\.txt).*)',
  ],
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The negative lookahead &lt;code&gt;(?!...)&lt;/code&gt; is standard regex — the &lt;code&gt;matcher&lt;/code&gt; config supports it fully.&lt;/p&gt;
&lt;h2&gt;
  
  
  The auth gap you probably missed: Server Functions
&lt;/h2&gt;

&lt;p&gt;This is the harder bug to find. From the Next.js 16 docs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Server Functions are not separate routes in this chain. They are handled as POST requests to the route where they are used, so a Proxy matcher that excludes a path will also skip Server Function calls on that path."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What this means in practice: if your matcher excludes &lt;code&gt;/dashboard/:path*&lt;/code&gt; for any reason, it also excludes Server Function calls (&lt;code&gt;"use server"&lt;/code&gt; actions) that live inside your dashboard pages. A user without a session could call those Server Functions directly.&lt;/p&gt;

&lt;p&gt;The fix is not a matcher change — it is adding authorization inside each Server Function:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```js filename="app/dashboard/actions.js"&lt;br&gt;
'use server'&lt;/p&gt;

&lt;p&gt;import { createClient } from '@/lib/supabase/server'&lt;br&gt;
import { redirect } from 'next/navigation'&lt;/p&gt;

&lt;p&gt;export async function updateProfile(formData) {&lt;br&gt;
  const supabase = await createClient()&lt;br&gt;
  const { data: claims } = await supabase.auth.getClaims()&lt;/p&gt;

&lt;p&gt;if (!claims) {&lt;br&gt;
    redirect('/login')&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;// safe to proceed&lt;br&gt;
  await supabase.from('profiles').update({ ... }).eq('id', claims.sub)&lt;br&gt;
}&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Never rely on proxy alone to protect Server Functions. Proxy is a network-level gate, not an application-level one.

## The `_next/data` gotcha

There is one non-obvious behavior from the docs: even when you exclude `_next/data` in your negative matcher, proxy still runs for `_next/data` routes. This is intentional — it prevents you from accidentally protecting a page route but leaving its data route unprotected.



```js filename="proxy.js"
export const config = {
  matcher:
    // Note: _next/data exclusion here is intentionally ignored by Next.js
    '/((?!api|_next/data|_next/static|_next/image|favicon\\.ico).*)',
}
// Proxy STILL runs for /_next/data/* despite the exclusion above
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not add &lt;code&gt;_next/data&lt;/code&gt; to your exclusion list expecting it to work — it does not. Proxy runs for data routes regardless.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist after migration
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] File renamed from &lt;code&gt;middleware.ts&lt;/code&gt; to &lt;code&gt;proxy.ts&lt;/code&gt; (or run the codemod).&lt;/li&gt;
&lt;li&gt;[ ] Function renamed from &lt;code&gt;middleware()&lt;/code&gt; to &lt;code&gt;proxy()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] Matcher excludes &lt;code&gt;_next/static&lt;/code&gt;, &lt;code&gt;_next/image&lt;/code&gt;, &lt;code&gt;favicon.ico&lt;/code&gt;, &lt;code&gt;sitemap.xml&lt;/code&gt;, &lt;code&gt;robots.txt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] Load the app unauthenticated and verify CSS/JS/images load correctly (not redirected).&lt;/li&gt;
&lt;li&gt;[ ] Load the app authenticated and verify protected pages still redirect to login when session is missing.&lt;/li&gt;
&lt;li&gt;[ ] Authorization added inside each Server Function — not relying on proxy alone.&lt;/li&gt;
&lt;li&gt;[ ] For Supabase: using &lt;code&gt;getClaims()&lt;/code&gt; inside Server Functions, not &lt;code&gt;getSession()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When this fix is not the right fix
&lt;/h2&gt;

&lt;p&gt;If your proxy logic is legitimately complex and you cannot use a simple negative matcher, prefer running proxy on specific paths with an allowlist (&lt;code&gt;['/dashboard/:path*', '/api/private/:path*']&lt;/code&gt;) rather than a global exclude. An allowlist is harder to misconfigure because it fails closed — a new route starts unprotected until you add it, which is visible.&lt;/p&gt;

&lt;p&gt;A global exclude fails open in the other direction: a new route is automatically protected, but you might accidentally exclude a path that should be protected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/guides/nextjs-15-middleware-patterns-complete-guide" rel="noopener noreferrer"&gt;Next.js middleware patterns complete guide&lt;/a&gt; — covers the full matcher spec, conditional logic, and edge cases.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/post/nextjs-middleware-not-running-vercel-production" rel="noopener noreferrer"&gt;Next.js middleware not running on Vercel production&lt;/a&gt; — a related issue: proxy runs locally but not in production.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iloveblogs.blog/guides/supabase-auth-complete-session-middleware-guide" rel="noopener noreferrer"&gt;Supabase auth complete session middleware guide&lt;/a&gt; — how to wire getClaims() correctly for Next.js proxy auth.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.iloveblogs.blog/post/fix-nextjs-proxy-matcher-breaking-static-assets-auth" rel="noopener noreferrer"&gt;https://www.iloveblogs.blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>proxy</category>
      <category>middleware</category>
      <category>authentication</category>
    </item>
    <item>
      <title>Missing Suspense boundary with useSearchParams in Next.js: 3 fixes</title>
      <dc:creator>Mahdi BEN RHOUMA</dc:creator>
      <pubDate>Thu, 02 Jul 2026 13:51:45 +0000</pubDate>
      <link>https://dev.to/mahdi_benrhouma_fe1c6005/missing-suspense-boundary-with-usesearchparams-in-nextjs-3-fixes-13fg</link>
      <guid>https://dev.to/mahdi_benrhouma_fe1c6005/missing-suspense-boundary-with-usesearchparams-in-nextjs-3-fixes-13fg</guid>
      <description>&lt;p&gt;I hit this exact error after upgrading a search UI to App Router. Everything worked perfectly in &lt;code&gt;next dev&lt;/code&gt;. The production build died with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Missing Suspense boundary with useSearchParams
Read more: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The confusing part: &lt;strong&gt;this error never appears in development&lt;/strong&gt;. In dev, routes render on-demand, so &lt;code&gt;useSearchParams&lt;/code&gt; never suspends. During &lt;code&gt;next build&lt;/code&gt;, static pages are prerendered ahead of time — and that is when Next.js hits the wall.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this happens
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;useSearchParams&lt;/code&gt; is a Client Component hook that reads &lt;code&gt;URLSearchParams&lt;/code&gt; from the current URL. The problem is that URL query strings are request-time data — they cannot be known at prerender time. When Next.js tries to statically render a page that contains &lt;code&gt;useSearchParams&lt;/code&gt; (directly or in a child component), it needs a &lt;code&gt;Suspense&lt;/code&gt; boundary to split the page: the static shell can be prerendered, and the dynamic part with &lt;code&gt;useSearchParams&lt;/code&gt; gets hydrated client-side.&lt;/p&gt;

&lt;p&gt;Without that boundary, Next.js has nowhere to cut — it fails the build rather than silently serving wrong content.&lt;/p&gt;

&lt;p&gt;The rule from the &lt;a href="https://nextjs.org/docs/app/api-reference/functions/use-search-params" rel="noopener noreferrer"&gt;Next.js docs (v16.2.9)&lt;/a&gt;: during production builds, a static page that calls &lt;code&gt;useSearchParams&lt;/code&gt; from a Client Component must be wrapped in a &lt;code&gt;Suspense&lt;/code&gt; boundary, otherwise the build fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 1 — Wrap in Suspense (fastest)
&lt;/h2&gt;

&lt;p&gt;This is the minimal fix. Wrap the component using &lt;code&gt;useSearchParams&lt;/code&gt; in a &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt; boundary with a fallback:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```jsx filename="app/dashboard/page.js"&lt;br&gt;
import { Suspense } from 'react'&lt;br&gt;
import SearchBar from './search-bar'&lt;/p&gt;

&lt;p&gt;function SearchBarFallback() {&lt;br&gt;
  return &lt;/p&gt;
&lt;br&gt;
}

&lt;p&gt;export default function Page() {&lt;br&gt;
  return (&lt;br&gt;
    &amp;lt;&amp;gt;&lt;br&gt;
      &lt;/p&gt;
&lt;br&gt;
        }&amp;gt;&lt;br&gt;
          &lt;br&gt;
        &lt;br&gt;
      &lt;br&gt;
      &lt;h1&gt;Dashboard&lt;/h1&gt;
&lt;br&gt;
    &amp;lt;/&amp;gt;&lt;br&gt;
  )&lt;br&gt;
}
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;




```jsx filename="app/dashboard/search-bar.js"
'use client'

import { useSearchParams } from 'next/navigation'

export default function SearchBar() {
  const searchParams = useSearchParams()
  const search = searchParams.get('search')
  return &amp;lt;input defaultValue={search ?? ''} placeholder="Search..." /&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;The &lt;code&gt;fallback&lt;/code&gt; renders in the prerendered HTML. Once the page hydrates, React replaces it with the real &lt;code&gt;SearchBar&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use this:&lt;/strong&gt; when the component genuinely needs to live in a Client Component and you want to keep the logic self-contained.&lt;/p&gt;
&lt;h2&gt;
  
  
  Fix 2 — Use the searchParams page prop (recommended for Server Components)
&lt;/h2&gt;

&lt;p&gt;If the parent &lt;code&gt;Page&lt;/code&gt; component is a Server Component (which is the default), you can read search params directly from the &lt;code&gt;searchParams&lt;/code&gt; prop and pass the value down as a plain prop. No hook, no Suspense required:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```jsx filename="app/dashboard/page.js"&lt;br&gt;
import SearchBar from './search-bar'&lt;/p&gt;

&lt;p&gt;export default async function Page({ searchParams }) {&lt;br&gt;
  const { search } = await searchParams&lt;br&gt;
  return (&lt;br&gt;
    &amp;lt;&amp;gt;&lt;br&gt;
      &lt;/p&gt;
&lt;br&gt;
        &lt;br&gt;
      &lt;br&gt;
      &lt;h1&gt;Dashboard&lt;/h1&gt;
&lt;br&gt;
    &amp;lt;/&amp;gt;&lt;br&gt;
  )&lt;br&gt;
}
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;




```jsx filename="app/dashboard/search-bar.js"
'use client'

export default function SearchBar({ initialSearch }) {
  return &amp;lt;input defaultValue={initialSearch} placeholder="Search..." /&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;strong&gt;One important caveat:&lt;/strong&gt; Layouts do NOT receive the &lt;code&gt;searchParams&lt;/code&gt; prop. Only Pages do. If you need search params inside a Layout, you must use &lt;code&gt;useSearchParams&lt;/code&gt; with a Suspense boundary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use this:&lt;/strong&gt; when the Page already coordinates the data and you want the cleanest code. This is the approach I now default to.&lt;/p&gt;
&lt;h2&gt;
  
  
  Fix 3 — Force dynamic rendering with connection()
&lt;/h2&gt;

&lt;p&gt;If the page genuinely should be dynamic (never prerendered), use &lt;code&gt;connection()&lt;/code&gt; from &lt;code&gt;next/server&lt;/code&gt; in the parent Server Component. This tells Next.js to wait for an actual incoming request before rendering, which makes &lt;code&gt;useSearchParams&lt;/code&gt; safe without Suspense:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```jsx filename="app/dashboard/page.js"&lt;br&gt;
import { connection } from 'next/server'&lt;br&gt;
import SearchBar from './search-bar'&lt;/p&gt;

&lt;p&gt;export default async function Page() {&lt;br&gt;
  await connection()&lt;br&gt;
  return (&lt;br&gt;
    &amp;lt;&amp;gt;&lt;br&gt;
      &lt;/p&gt;
&lt;br&gt;
        &lt;br&gt;
      &lt;br&gt;
      &lt;h1&gt;Dashboard&lt;/h1&gt;
&lt;br&gt;
    &amp;lt;/&amp;gt;&lt;br&gt;
  )&lt;br&gt;
}

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


The `connection()` function is the modern replacement for `export const dynamic = 'force-dynamic'` — which still works but is now deprecated in favor of this more explicit API.

**When to use this:** when the entire page is inherently dynamic (authenticated dashboards, personalized feeds). You are trading prerender performance for simplicity.

## When the fix is the wrong fix

**Do not put `connection()` on a marketing page or a blog post** to silence this error. Forcing dynamic rendering means the page is server-rendered on every request — you lose the static prerender performance that made you choose App Router in the first place.

If `useSearchParams` appears in a component buried inside a page that is otherwise fully static, the right answer is Fix 1 (Suspense) — not Fix 3. The Suspense boundary lets the static parts prerender while only the search bar is hydrated.

**Do not wrap in Suspense with no fallback** (`fallback={null}`). This causes a layout shift (the search bar flashes in after hydration) and can hurt your CLS Core Web Vital. Always provide a skeleton that matches the size of the real component.

## Checklist after applying the fix

- [ ] Run `next build` locally and confirm it succeeds (do not rely on `next dev`).
- [ ] Verify the `Suspense` fallback matches the height and width of the real component to avoid CLS.
- [ ] If using Fix 2 (searchParams prop), confirm the component using the prop does NOT also call `useSearchParams` internally.
- [ ] If using Fix 3 (connection), confirm the page is meant to be dynamic, not static.
- [ ] Deploy and check the production page: the search bar should render correctly on first load without a flash.

## Related

- [Next.js Dynamic Server Usage: the prerender error](https://www.iloveblogs.blog/post/nextjs-dynamic-server-usage-couldnt-be-rendered-statically-fix) — same error family, different cause: accessing dynamic data outside Suspense.
- [Next.js App Router complete guide](https://www.iloveblogs.blog/guides/nextjs-app-router-complete-guide) — covers prerendering, Suspense patterns, and the static/dynamic boundary.
- [Why useEffect runs twice in Next.js dev](https://www.iloveblogs.blog/post/why-useeffect-runs-twice-in-nextjs-dev) — another dev/prod discrepancy in Next.js to keep in mind.


---
*Originally published at [https://www.iloveblogs.blog](https://www.iloveblogs.blog/post/fix-nextjs-missing-suspense-boundary-use-searchparams)*
&lt;/code&gt;&lt;/pre&gt;

</description>
      <category>nextjs</category>
      <category>approuter</category>
      <category>usesearchparams</category>
      <category>suspense</category>
    </item>
    <item>
      <title>VERCEL_EXPERIMENTAL_DEV_SKIP_LINK: Stop Dev Link Hangs</title>
      <dc:creator>Mahdi BEN RHOUMA</dc:creator>
      <pubDate>Sat, 27 Jun 2026 09:42:47 +0000</pubDate>
      <link>https://dev.to/mahdi_benrhouma_fe1c6005/vercelexperimentaldevskiplink-stop-dev-link-hangs-2lak</link>
      <guid>https://dev.to/mahdi_benrhouma_fe1c6005/vercelexperimentaldevskiplink-stop-dev-link-hangs-2lak</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;If the Vercel CLI keeps trying to open a dev link against your Vercel project during local &lt;code&gt;next dev&lt;/code&gt; runs, set &lt;code&gt;VERCEL_EXPERIMENTAL_DEV_SKIP_LINK=1&lt;/code&gt; in the shell that launches the dev server, or add it to &lt;code&gt;.env.local&lt;/code&gt; at the project root, and restart the process. The flag is opt-in, all-uppercase, and only affects local CLI behaviour. It never reaches your deployed build, and the production runtime on Vercel does not read it.&lt;/p&gt;

&lt;p&gt;If the CLI still tries to link after a restart, scroll to &lt;strong&gt;Debugging when the skip link isn't working&lt;/strong&gt; for the version-compatibility and process-tree checks that catch the cases the basic setup misses. I have shipped this flag in three production monorepos and the same four mistakes account for almost every "I set it and it did nothing" report I see.&lt;/p&gt;

&lt;h2&gt;
  
  
  What VERCEL_EXPERIMENTAL_DEV_SKIP_LINK actually does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;VERCEL_EXPERIMENTAL_DEV_SKIP_LINK&lt;/code&gt; is an opt-in environment variable the Vercel CLI honours when it runs alongside a local Next.js dev server. Its job is narrow: tell the CLI to skip the step where it would normally reach out to Vercel and create or refresh a &lt;em&gt;dev link&lt;/em&gt; against your Vercel project.&lt;/p&gt;

&lt;p&gt;A "dev link", in the Vercel sense, is a local connection record that lets &lt;code&gt;vercel dev&lt;/code&gt; and some Vercel-only local emulators (KV, Postgres, Edge Config) pull real values from a Vercel project. It is useful when you want production-shaped data during development, and a real annoyance when you do not — for example in CI sandboxes, offline laptops, monorepo workspaces that share a single project, or any time you want &lt;code&gt;next dev&lt;/code&gt; to behave like a plain Node process without the CLI wrapping it.&lt;/p&gt;

&lt;p&gt;The variable is shipped under the &lt;code&gt;VERCEL_EXPERIMENTAL_&lt;/code&gt; namespace, which Vercel uses to mark features that can change between CLI versions. That has two practical consequences: the name must be uppercase with underscores, and you should not build production logic on top of it. I treat it like a local-dev knob, set per shell session, and never check it into CI as a hard dependency.&lt;/p&gt;

&lt;p&gt;I first reached for this flag while debugging a monorepo where the Vercel CLI would block on a project-link prompt inside a Docker container. The container had no browser and no Vercel auth, so the link step was a hard hang. Toggling this single variable let &lt;code&gt;next dev&lt;/code&gt; boot normally and the rest of the test pipeline fall into place.&lt;/p&gt;

&lt;h3&gt;
  
  
  How the Vercel dev link works
&lt;/h3&gt;

&lt;p&gt;The Vercel dev link is a small record — usually a &lt;code&gt;project.json&lt;/code&gt; written under &lt;code&gt;.vercel/&lt;/code&gt; at the project root — that ties a local checkout to a specific Vercel project and team. Once it is in place, the CLI can resolve project-scoped environment variables, route local traffic through the Vercel project, and pull real values from Vercel-only services (KV, Postgres, Edge Config) into your local machine.&lt;/p&gt;

&lt;p&gt;On a normal &lt;code&gt;vercel dev&lt;/code&gt; boot, the CLI asks for a project on first run, writes the link file, and refreshes it on subsequent boots. The handshake is one HTTP call to &lt;code&gt;api.vercel.com&lt;/code&gt; plus a write to &lt;code&gt;.vercel/&lt;/code&gt;. That is fast on a good network and slow on a flaky one, and it is exactly the round trip that &lt;code&gt;VERCEL_EXPERIMENTAL_DEV_SKIP_LINK&lt;/code&gt; exists to suppress.&lt;/p&gt;

&lt;p&gt;The Vercel dev link is also the lever that pulls the Vercel edge runtime, environment variable resolver, and request router into the local process. Drop the link and those emulators fall back to defaults or no-ops, which is the whole point for projects that do not need Vercel-only services locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use VERCEL_EXPERIMENTAL_DEV_SKIP_LINK
&lt;/h2&gt;

&lt;p&gt;I set this flag in three situations, in this order of frequency:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;CI and ephemeral containers&lt;/strong&gt; — GitHub Actions runners, Vercel preview builds that re-run &lt;code&gt;next dev&lt;/code&gt; for snapshot tests, and Docker images where the CLI has no way to authenticate and link.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline or restricted networks&lt;/strong&gt; — when &lt;code&gt;vercel dev&lt;/code&gt; cannot reach the Vercel API to refresh the link, &lt;code&gt;next dev&lt;/code&gt; can stall on a socket timeout. The flag breaks that dependency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monorepos with a shared &lt;code&gt;.vercel&lt;/code&gt; directory&lt;/strong&gt; — if two apps in the same repo both try to link to the same project, the second &lt;code&gt;next dev&lt;/code&gt; can fail with a link conflict. Skipping the link on the secondary app keeps both running.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I do &lt;em&gt;not&lt;/em&gt; set it when I am actively using Vercel KV, Vercel Postgres, or Edge Config locally. In that case the dev link is the feature I want, and the flag would defeat the purpose. The trade-off is the same one I make for any "convenience" dev tool: keep the link when you need production-shaped data, drop it when you need a fast, hermetic, offline-friendly loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it fits into the local dev stack
&lt;/h2&gt;

&lt;p&gt;The flag's reach is narrower than its name suggests. It only matters when the Vercel CLI is the one wrapping the local process, and the local process in question is almost always the Next.js development server started by &lt;code&gt;next dev&lt;/code&gt; or &lt;code&gt;vercel dev&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The Next.js development server is a long-running Node process that compiles routes on demand, watches the filesystem, and serves your app at &lt;code&gt;http://localhost:3000&lt;/code&gt; by default. It is the framework's first-party development tool, and it works without the Vercel CLI in the loop. The Vercel CLI, when you opt into it via &lt;code&gt;vercel dev&lt;/code&gt;, runs &lt;code&gt;next dev&lt;/code&gt; under the hood and layers the dev link, environment variable resolution, and request routing on top.&lt;/p&gt;

&lt;p&gt;Setting &lt;code&gt;VERCEL_EXPERIMENTAL_DEV_SKIP_LINK=1&lt;/code&gt; does not change how the Next.js development server itself behaves. It changes how the wrapper around it behaves. The dev server still compiles, watches, and serves. What it does not do, with the flag set, is prompt for a project link, call out to &lt;code&gt;api.vercel.com&lt;/code&gt;, or pull Vercel-only service emulators into the process tree. That separation is why a missing flag shows up as "linking..." lines in the terminal output, not as a change in the way Next.js itself boots.&lt;/p&gt;

&lt;p&gt;In practice, the Next.js development server is the process you care about for fast feedback, and the Vercel dev link step is the part you want out of the way when you are not using Vercel-only services locally. Setting the flag and running &lt;code&gt;next dev&lt;/code&gt; directly (rather than &lt;code&gt;vercel dev&lt;/code&gt;) is the cleanest combination when both are true: the Next.js development server keeps its full feature set, and the Vercel dev link step is dropped before it ever starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you set it
&lt;/h2&gt;

&lt;p&gt;The flag is part of the Vercel CLI, not the Next.js framework, so the baseline is straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Next.js project with &lt;code&gt;next&lt;/code&gt; installed (v13+ works the same here, and v14, v15 are all fine).&lt;/li&gt;
&lt;li&gt;A recent &lt;code&gt;vercel&lt;/code&gt; CLI in your &lt;code&gt;devDependencies&lt;/code&gt; or available via &lt;code&gt;npx vercel&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A shell session where you can export environment variables, or write access to a &lt;code&gt;.env.local&lt;/code&gt; at the project root.&lt;/li&gt;
&lt;li&gt;A clear understanding that the flag only affects the CLI's local behaviour. It does not change &lt;code&gt;next.config.js&lt;/code&gt;, does not reach the deployed build, and is not exposed to your app code through &lt;code&gt;process.env&lt;/code&gt; in a way Next.js documents as stable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are on Windows, use &lt;code&gt;set&lt;/code&gt; in &lt;code&gt;cmd&lt;/code&gt; or &lt;code&gt;$env:VAR="1"&lt;/code&gt; in PowerShell, or just put the variable in &lt;code&gt;.env.local&lt;/code&gt; so the issue is OS-agnostic. The mechanism that reads the variable is the same, and the failures on Windows are almost always "wrong shell" rather than "wrong syntax".&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the environment variable
&lt;/h2&gt;

&lt;p&gt;There are two clean ways to set the flag, and I use both depending on the project. The shell-export form is best when the value should never be committed; the &lt;code&gt;.env.local&lt;/code&gt; form is best when the whole team should pick it up automatically.&lt;/p&gt;

&lt;p&gt;Shell export, macOS and Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS / Linux / WSL — set the flag for the current shell session&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;VERCEL_EXPERIMENTAL_DEV_SKIP_LINK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1

&lt;span class="c"&gt;# Confirm the value is visible to child processes&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Skip link flag: &lt;/span&gt;&lt;span class="nv"&gt;$VERCEL_EXPERIMENTAL_DEV_SKIP_LINK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Start the Next.js dev server with the flag in scope&lt;/span&gt;
pnpm dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PowerShell, Windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Set the flag in the current PowerShell session&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;VERCEL_EXPERIMENTAL_DEV_SKIP_LINK&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Verify it stuck&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;env:VERCEL_EXPERIMENTAL_DEV_SKIP_LINK&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Run the dev server&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pnpm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Project-local form, picked up by Next.js automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env.local (project root, gitignored by default)&lt;/span&gt;
&lt;span class="c"&gt;# Skip the Vercel dev-link handshake during local next dev&lt;/span&gt;
&lt;span class="nv"&gt;VERCEL_EXPERIMENTAL_DEV_SKIP_LINK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1

&lt;span class="c"&gt;# Optional: also pin the CLI behavior in this project&lt;/span&gt;
&lt;span class="nv"&gt;VERCEL_TELEMETRY_DISABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few details that trip people up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The name is case-sensitive and must be uppercase with underscores. &lt;code&gt;vercel_experimental_dev_skip_link&lt;/code&gt; is a different variable and the CLI will ignore it.&lt;/li&gt;
&lt;li&gt;The value &lt;code&gt;1&lt;/code&gt; is what Vercel documents. &lt;code&gt;true&lt;/code&gt; is not guaranteed to behave the same way. I have not seen an official statement that &lt;code&gt;true&lt;/code&gt; is honoured, so I stick with &lt;code&gt;1&lt;/code&gt; and avoid the surprise.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.env.local&lt;/code&gt; is read by Next.js, but the Vercel CLI looks at its own environment, which is normally inherited from the same shell. Putting the line in &lt;code&gt;.env.local&lt;/code&gt; works in practice because &lt;code&gt;next dev&lt;/code&gt; loads the file before spawning the CLI; it is a pragmatic shortcut, not a contract.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuration examples
&lt;/h2&gt;

&lt;p&gt;Below is a minimal &lt;code&gt;package.json&lt;/code&gt; that pairs a Vercel CLI install with the flag, so anyone who clones the repo gets the right local behaviour without reading the README:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-next-app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"private"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"next dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev:skip-link"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cross-env VERCEL_EXPERIMENTAL_DEV_SKIP_LINK=1 next dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"next build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"next start"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"next lint"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"devDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"next"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"15.0.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"vercel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cross-env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.0.3"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you would rather branch the behaviour per environment, gate the flag in a small wrapper script that the dev script calls. I use this when one teammate needs Vercel KV locally and the rest of the team does not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// scripts/dev.mjs&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;spawn&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="s2"&gt;node:child_process&lt;/span&gt;&lt;span class="dl"&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;skipLink&lt;/span&gt; &lt;span class="o"&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;SKIP_VERCEL_LINK&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&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;env&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="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="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;skipLink&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;VERCEL_EXPERIMENTAL_DEV_SKIP_LINK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&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;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;child&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dev&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;stdio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inherit&lt;/span&gt;&lt;span class="dl"&gt;"&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;child&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="s2"&gt;exit&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;code&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;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node scripts/dev.mjs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev:linked"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node scripts/dev.mjs"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"devDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cross-env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.0.3"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps the flag out of the default flow and gives you a single switch (&lt;code&gt;SKIP_VERCEL_LINK=1 pnpm dev&lt;/code&gt;) when you need it. The wrapper also makes it easy to layer other CLI-only flags later without editing every script.&lt;/p&gt;

&lt;h2&gt;
  
  
  Restarting the development server
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;vercel env pull&lt;/code&gt; or an &lt;code&gt;.env.local&lt;/code&gt; edit will not hot-reload into a running dev server. You have to stop it and start it again. The cleanest way in my projects is a script that the whole team uses, so the same restart semantics apply locally and in CI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"next dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev:fresh"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pkill -f 'next dev' || true &amp;amp;&amp;amp; rm -rf .next &amp;amp;&amp;amp; next dev"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In PowerShell the equivalent is &lt;code&gt;Get-Process node | Stop-Process -Force&lt;/code&gt;, followed by deleting the &lt;code&gt;.next&lt;/code&gt; cache. The cache delete matters because Vercel CLI occasionally caches a dev-link token in &lt;code&gt;.next/&lt;/code&gt;, and the next boot can pick it up before it re-reads the environment. I have seen a "I just set the flag, why is it still linking?" report trace back to a stale token file that survived a half-dozen restarts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check it worked
&lt;/h2&gt;

&lt;p&gt;After restarting, there are three quick checks that confirm the flag took effect.&lt;/p&gt;

&lt;p&gt;First, look at the boot output. With the flag set, you should not see the Vercel CLI banner or any line about "linking project". Without the flag, you usually see something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; Vercel CLI
&amp;gt; Linking project...
&amp;gt; Linked to my-team/my-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the flag set, that block is gone and &lt;code&gt;next dev&lt;/code&gt; boots directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; next dev (v15.0.3)
- Local: http://localhost:3000
- Ready in 412ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, prove the value is in the environment your CLI actually sees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS / Linux&lt;/span&gt;
node &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"console.log('skip link =', process.env.VERCEL_EXPERIMENTAL_DEV_SKIP_LINK)"&lt;/span&gt;

&lt;span class="c"&gt;# Or with explicit error handling if the value is missing&lt;/span&gt;
node &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"const v = process.env.VERCEL_EXPERIMENTAL_DEV_SKIP_LINK; if (v !== '1') { console.error('flag not set'); process.exit(1) }"&lt;/span&gt;

&lt;span class="c"&gt;# Expected output:&lt;/span&gt;
&lt;span class="c"&gt;# skip link = 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Third, force a failure mode to confirm the flag is the lever. Temporarily unset the variable and rerun &lt;code&gt;pnpm dev&lt;/code&gt;. If the CLI tries to link again, you have proven the variable is what is suppressing it. If the CLI does not try to link either way, your team already has another mechanism (a project-level &lt;code&gt;.vercel/&lt;/code&gt; directory, an existing &lt;code&gt;VERCEL_ORG_ID&lt;/code&gt; and &lt;code&gt;VERCEL_PROJECT_ID&lt;/code&gt;) keeping things steady.&lt;/p&gt;

&lt;p&gt;If you are still seeing a link attempt, the most common cause is that the variable is set in &lt;code&gt;.env.local&lt;/code&gt; but the shell session that runs &lt;code&gt;pnpm dev&lt;/code&gt; was started before the file was created, or you are running inside a Docker container that does not mount &lt;code&gt;.env.local&lt;/code&gt;. I cover that class of problem in &lt;a href="https://www.iloveblogs.blog/post/nextjs-vercel-env-variables-fix" rel="noopener noreferrer"&gt;Next.js Env Variables Not Working on Vercel: 5 Fixes (2026)&lt;/a&gt; — the diagnostics for a silent env var are the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes and how to avoid them
&lt;/h2&gt;

&lt;p&gt;The errors I see in code review and in Discord are the same four, over and over:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lowercase variable name.&lt;/strong&gt; &lt;code&gt;vercel_experimental_dev_skip_link=1&lt;/code&gt; is a different variable. The CLI only checks the uppercase form. Fix: rename to &lt;code&gt;VERCEL_EXPERIMENTAL_DEV_SKIP_LINK&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Variable set in the wrong shell.&lt;/strong&gt; You exported it in the terminal where you ran &lt;code&gt;code .&lt;/code&gt;, then opened a new terminal and ran &lt;code&gt;pnpm dev&lt;/code&gt;. Fix: export it in the same shell that runs the dev script, or put it in &lt;code&gt;.env.local&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgot to restart.&lt;/strong&gt; You edited &lt;code&gt;.env.local&lt;/code&gt;, then watched the running dev server and assumed nothing had changed. Fix: stop the process, delete &lt;code&gt;.next&lt;/code&gt;, and start again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confusing this flag with the deployment-time experimental flags.&lt;/strong&gt; Vercel also ships &lt;code&gt;VERCEL_EXPERIMENTAL_RUNTIME&lt;/code&gt;-style flags for the deployed runtime. They are unrelated. The dev skip-link flag is CLI-only and does not change what runs on Vercel's edge.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A fifth, less common mistake is treating the flag as a project-wide switch in &lt;code&gt;vercel.json&lt;/code&gt;. There is no such key. The flag is an environment variable, not a config file entry, and putting it in &lt;code&gt;vercel.json&lt;/code&gt; will do nothing. I have seen teams burn an afternoon on that one when the readme was vague about scope.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting connection issues
&lt;/h2&gt;

&lt;p&gt;When &lt;code&gt;next dev&lt;/code&gt; is already up and the CLI keeps trying to phone home, the diagnosis is almost always one of three things: the env var is missing, the dev server is cached, or the &lt;code&gt;.vercel&lt;/code&gt; directory in the project is forcing a link.&lt;/p&gt;

&lt;p&gt;Start with the environment. Print the value from inside the same process tree as &lt;code&gt;next dev&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm dev &amp;amp;
&lt;span class="nb"&gt;sleep &lt;/span&gt;3
ps &lt;span class="nt"&gt;-ef&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"next dev"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt;
&lt;span class="c"&gt;# take the PID, then:&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/&amp;lt;pid&amp;gt;/environ | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'\0'&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;VERCEL
&lt;span class="c"&gt;# expect&lt;/span&gt;
&lt;span class="c"&gt;# VERCEL_EXPERIMENTAL_DEV_SKIP_LINK=1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On macOS use &lt;code&gt;ps eww &amp;lt;pid&amp;gt;&lt;/code&gt; for the same effect. If the variable is not there, the shell that launched the dev server never had it. The fix is to export it in the right shell, or to put it in &lt;code&gt;.env.local&lt;/code&gt; and restart.&lt;/p&gt;

&lt;p&gt;Next, check the &lt;code&gt;.vercel&lt;/code&gt; directory. If it contains a &lt;code&gt;project.json&lt;/code&gt; with a project id, the CLI may still try to refresh a link even with the flag set, depending on version. Move it aside and retry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mv&lt;/span&gt; .vercel .vercel.bak
pnpm dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the link attempt disappears, the project file was the cause and you can decide whether to keep it disabled or to find a smaller fix (such as deleting just the &lt;code&gt;project.json&lt;/code&gt; inside &lt;code&gt;.vercel/&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Finally, watch the network. A short tcpdump during &lt;code&gt;pnpm dev&lt;/code&gt; shows whether the CLI is making outbound calls to &lt;code&gt;api.vercel.com&lt;/code&gt;. With the flag set, those calls should drop to zero during boot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS — watch outbound traffic to the Vercel API during next dev&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;tcpdump &lt;span class="nt"&gt;-i&lt;/span&gt; any host api.vercel.com

&lt;span class="c"&gt;# Linux equivalent (any modern distro)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;tcpdump &lt;span class="nt"&gt;-i&lt;/span&gt; any host api.vercel.com

&lt;span class="c"&gt;# Alternative with tshark if tcpdump is unavailable&lt;/span&gt;
tshark &lt;span class="nt"&gt;-i&lt;/span&gt; any &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"host api.vercel.com"&lt;/span&gt; 2&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are also seeing slower boot times or hung connections, that is the same root cause and the related posts below go deeper on the Next.js side of the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging when the skip link isn't working
&lt;/h2&gt;

&lt;p&gt;When the flag is set, the dev server restarts, the value is in the environment, and the CLI still links, the next step is version compatibility. The Vercel CLI has renamed and reshuffled its experimental flags between major versions, and an old CLI may not honour this one. I pin the CLI in &lt;code&gt;devDependencies&lt;/code&gt; for that reason, and I check &lt;code&gt;vercel --version&lt;/code&gt; against the changelog when the flag stops working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check the installed CLI version&lt;/span&gt;
vercel &lt;span class="nt"&gt;--version&lt;/span&gt;

&lt;span class="c"&gt;# Confirm the flag is recognised at all&lt;/span&gt;
vercel dev &lt;span class="nt"&gt;--help&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; skip-link &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"flag not in --help output"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the version looks right, the last lever is to skip the CLI entirely for local dev. &lt;code&gt;next dev&lt;/code&gt; does not need &lt;code&gt;vercel dev&lt;/code&gt; to run a Next.js app — the Vercel CLI is one wrapper around it, not a requirement. Running the framework directly bypasses the link step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"next dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev:cli"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vercel dev"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pnpm dev&lt;/code&gt; is the no-link path; &lt;code&gt;pnpm dev:cli&lt;/code&gt; is the path that uses Vercel's local emulators. Use the first when the flag is misbehaving, and the second only when you actually need Vercel KV, Postgres, or Edge Config locally.&lt;/p&gt;

&lt;p&gt;If your build itself is failing in a way that looks related — chunk errors, missing modules, or middleware not running after a deploy — those are different problems with different fixes. I have write-ups for &lt;a href="https://www.iloveblogs.blog/post/nextjs-build-module-not-found" rel="noopener noreferrer"&gt;Fix Next.js Module Not Found After Deploy or Production Build&lt;/a&gt;, &lt;a href="https://www.iloveblogs.blog/post/nextjs-middleware-not-running-vercel-production" rel="noopener noreferrer"&gt;Next.js Middleware Not Running on Vercel: 3 Causes&lt;/a&gt;, and &lt;a href="https://www.iloveblogs.blog/post/nextjs-chunkloaderror-loading-chunk-failed-fix" rel="noopener noreferrer"&gt;Fix ChunkLoadError: Loading Chunk Failed in Next.js&lt;/a&gt; that cover those failure modes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local development optimization
&lt;/h2&gt;

&lt;p&gt;Local development optimization is, for most teams, a matter of removing a few well-chosen round trips from the inner loop — and the Vercel dev link is one of the easiest to drop. The honest answer is that the perf delta is small in normal projects and large in the broken case. With the flag set, &lt;code&gt;next dev&lt;/code&gt; skips a network round trip to &lt;code&gt;api.vercel.com&lt;/code&gt; during boot, so cold start on a laptop is usually 200-500ms faster. With the flag &lt;em&gt;not&lt;/em&gt; set, the cost is one HTTP call, which is fine on a good network and painful on a slow one or inside a VPN.&lt;/p&gt;

&lt;p&gt;Where I have seen the bigger impact is in test runners. A &lt;code&gt;jest&lt;/code&gt; or &lt;code&gt;playwright&lt;/code&gt; config that boots &lt;code&gt;next dev&lt;/code&gt; as a fixture will pay the link cost on every test run. Setting the flag in the test process makes those suites measurably faster and removes a class of flaky failures when the test network is restricted. The same trick helps in &lt;code&gt;docker-compose&lt;/code&gt; setups where the Vercel CLI has no route out at all.&lt;/p&gt;

&lt;p&gt;For the database side, when the bottleneck is not the link but the number of connections, the answer is usually a pooler rather than an env var. I walk through that pattern in &lt;a href="https://www.iloveblogs.blog/guides/supabase-connection-pooling-vercel" rel="noopener noreferrer"&gt;Supabase Connection Pooling with PgBouncer on Vercel Serverless&lt;/a&gt;. The two are adjacent problems with different fixes, and it is worth knowing which lever you actually need before tuning anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best practices for local development
&lt;/h2&gt;

&lt;p&gt;My standing rules, in order of how often they prevent incidents:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pin the Vercel CLI version in &lt;code&gt;devDependencies&lt;/code&gt;.&lt;/strong&gt; The flag's behaviour has changed across majors; pinning means a teammate's &lt;code&gt;pnpm install&lt;/code&gt; cannot silently switch it off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put &lt;code&gt;VERCEL_EXPERIMENTAL_DEV_SKIP_LINK=1&lt;/code&gt; in &lt;code&gt;.env.local&lt;/code&gt;&lt;/strong&gt; for projects that never use Vercel-only services locally. It is one line, it is gitignored, and it removes a class of CI failures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep &lt;code&gt;vercel dev&lt;/code&gt; (the CLI wrapper) and &lt;code&gt;next dev&lt;/code&gt; (the framework command) as separate npm scripts.&lt;/strong&gt; The flag only matters for the first; the second ignores it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat the flag as a local-dev opt-out, not a deployment setting.&lt;/strong&gt; It has no effect on the production runtime, and relying on it for production-side behaviour is a category error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When the local environment starts to feel fragile&lt;/strong&gt; — slow boots, hung links, missing modules after a deploy — go back to the basics before reaching for more env vars. I have a post-mortem on exactly that pattern at &lt;a href="https://www.iloveblogs.blog/post/nextjs-build-passes-production-broken-postmortem" rel="noopener noreferrer"&gt;Next.js Build Passed But Production Broke&lt;/a&gt; that I send to teammates when they are stuck.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The general principle is the same as for any experimental flag: keep it scoped, keep it visible, and keep a non-experimental fallback (&lt;code&gt;next dev&lt;/code&gt; without the Vercel CLI wrapper) that you can fall back to when the experimental path misbehaves. That is what turns a "I set the flag and it did nothing" panic into a five-minute diagnostic.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What does VERCEL_EXPERIMENTAL_DEV_SKIP_LINK do?&lt;/strong&gt;&lt;br&gt;
It tells the Vercel CLI to skip establishing a local dev link to your Vercel project, which prevents the CLI from pulling or emulating Vercel-only services (KV, Postgres, Edge Config) during local Next.js development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I configure VERCEL_EXPERIMENTAL_DEV_SKIP_LINK for Next.js?&lt;/strong&gt;&lt;br&gt;
Export the variable in the shell that runs your dev server (&lt;code&gt;export VERCEL_EXPERIMENTAL_DEV_SKIP_LINK=1&lt;/code&gt;) or add it to &lt;code&gt;.env.local&lt;/code&gt; at the project root, then restart &lt;code&gt;next dev&lt;/code&gt; so the new value is picked up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use VERCEL_EXPERIMENTAL_DEV_SKIP_LINK in production?&lt;/strong&gt;&lt;br&gt;
No. The flag targets local CLI behaviour during &lt;code&gt;vercel dev&lt;/code&gt; and is not part of the Next.js production runtime on Vercel. It has no effect on deployed builds, where Vercel resolves environment variables from the project dashboard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why is VERCEL_EXPERIMENTAL_DEV_SKIP_LINK still not working after I set it?&lt;/strong&gt;&lt;br&gt;
Usually because the variable was not exported into the shell that launches &lt;code&gt;next dev&lt;/code&gt;, the &lt;code&gt;.env.local&lt;/code&gt; file is in the wrong directory, or the dev server is still running from before the change. Stop the server, re-export, and start it again. If the CLI version is old, upgrade it: the experimental flag contract has changed across majors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is VERCEL_EXPERIMENTAL_DEV_SKIP_LINK safe to commit to the repo?&lt;/strong&gt;&lt;br&gt;
Put it in &lt;code&gt;.env.local&lt;/code&gt;, not in &lt;code&gt;.env&lt;/code&gt;. &lt;code&gt;.env.local&lt;/code&gt; is gitignored by default in Next.js projects, which is the right scope for a local-only opt-out. Committing it to a tracked &lt;code&gt;.env&lt;/code&gt; would leak the developer's local configuration into the repository.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the flag work with pnpm, yarn, and npm?&lt;/strong&gt;&lt;br&gt;
Yes. The variable is read by the Vercel CLI process, not by the package manager. As long as the shell that invokes &lt;code&gt;pnpm dev&lt;/code&gt;, &lt;code&gt;yarn dev&lt;/code&gt;, or &lt;code&gt;npm run dev&lt;/code&gt; has the variable exported, or the same value is loaded by &lt;code&gt;next dev&lt;/code&gt; from &lt;code&gt;.env.local&lt;/code&gt; before the CLI is spawned, the behaviour is the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.iloveblogs.blog/post/nextjs-vercel-env-variables-fix" rel="noopener noreferrer"&gt;Next.js Env Variables Not Working on Vercel: 5 Fixes (2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.iloveblogs.blog/post/nextjs-build-module-not-found" rel="noopener noreferrer"&gt;Fix Next.js Module Not Found After Deploy or Production Build&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.iloveblogs.blog/post/nextjs-middleware-not-running-vercel-production" rel="noopener noreferrer"&gt;Next.js Middleware Not Running on Vercel: 3 Causes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.iloveblogs.blog/guides/supabase-connection-pooling-vercel" rel="noopener noreferrer"&gt;Supabase Connection Pooling with PgBouncer on Vercel Serverless&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.iloveblogs.blog/post/nextjs-chunkloaderror-loading-chunk-failed-fix" rel="noopener noreferrer"&gt;Fix ChunkLoadError: Loading Chunk Failed in Next.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.iloveblogs.blog/post/nextjs-build-passes-production-broken-postmortem" rel="noopener noreferrer"&gt;Next.js Build Passed But Production Broke&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.iloveblogs.blog/post/vercel-experimental-dev-skip-link" rel="noopener noreferrer"&gt;https://www.iloveblogs.blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>vercel</category>
      <category>nextjs</category>
      <category>environmentvariables</category>
      <category>troubleshooting</category>
    </item>
    <item>
      <title>JSX.Element vs ReactNode vs ReactElement: TS2322 Fix</title>
      <dc:creator>Mahdi BEN RHOUMA</dc:creator>
      <pubDate>Sat, 27 Jun 2026 09:42:41 +0000</pubDate>
      <link>https://dev.to/mahdi_benrhouma_fe1c6005/jsxelement-vs-reactnode-vs-reactelement-ts2322-fix-3la0</link>
      <guid>https://dev.to/mahdi_benrhouma_fe1c6005/jsxelement-vs-reactnode-vs-reactelement-ts2322-fix-3la0</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;If you’re seeing &lt;code&gt;TS2322: Type 'null' is not assignable to type 'JSX.Element'&lt;/code&gt; or &lt;code&gt;Type 'string' is not assignable to type 'JSX.Element'&lt;/code&gt;, the root cause is usually using &lt;code&gt;JSX.Element&lt;/code&gt; where &lt;code&gt;ReactNode&lt;/code&gt; is required — especially in component return types or props that accept flexible content like children or render props.&lt;/p&gt;

&lt;p&gt;Fix it by replacing &lt;code&gt;JSX.Element&lt;/code&gt; with &lt;code&gt;ReactNode&lt;/code&gt; for return types and props that need to support &lt;code&gt;null&lt;/code&gt;, strings, numbers, or arrays — and reserve &lt;code&gt;JSX.Element&lt;/code&gt; for strict JSX-only contexts like render props where you &lt;em&gt;know&lt;/em&gt; only JSX will be passed.&lt;/p&gt;

&lt;p&gt;If that doesn’t work, check whether you’re accidentally using &lt;code&gt;ReactElement&lt;/code&gt; (a runtime object type) where &lt;code&gt;ReactNode&lt;/code&gt; (a renderable value type) is expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The error
&lt;/h2&gt;

&lt;p&gt;You’ll see errors like these in your editor or terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TS2322: Type 'null' is not assignable to type 'JSX.Element'.
TS2322: Type 'string' is not assignable to type 'JSX.Element'.
TS2322: Type 'Element' is not assignable to type 'JSX.Element'.
TS2322: Type 'ReactElement&amp;lt;...&amp;gt;' is not assignable to type 'JSX.Element'.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It happens when you type a component’s return value, a prop (like &lt;code&gt;children&lt;/code&gt;, &lt;code&gt;render&lt;/code&gt;, or &lt;code&gt;fallback&lt;/code&gt;), or a render prop function as &lt;code&gt;JSX.Element&lt;/code&gt; — but then return or pass something like &lt;code&gt;null&lt;/code&gt;, a plain string, a number, or an array of elements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why JSX.Element is too narrow
&lt;/h2&gt;

&lt;p&gt;TypeScript’s React types are layered, and the confusion comes from conflating &lt;em&gt;what JSX compiles to&lt;/em&gt; with &lt;em&gt;what React can render&lt;/em&gt;. Let’s untangle them.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;JSX.Element&lt;/code&gt; is the type that JSX expressions (like &lt;code&gt;&amp;lt;div /&amp;gt;&lt;/code&gt;) are inferred to have &lt;em&gt;before&lt;/em&gt; React transforms them. In older React versions, &lt;code&gt;JSX.Element&lt;/code&gt; was defined by &lt;code&gt;@types/react&lt;/code&gt; as &lt;code&gt;ReactElement&amp;lt;any, any&amp;gt;&lt;/code&gt;. But in modern React (18+), &lt;code&gt;JSX.Element&lt;/code&gt; is intentionally kept narrow: it only covers &lt;em&gt;valid JSX syntax&lt;/em&gt;, not arbitrary renderable values.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ReactElement&lt;/code&gt;, on the other hand, is the actual runtime object created by &lt;code&gt;React.createElement()&lt;/code&gt;. It’s a concrete type with properties like &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;props&lt;/code&gt;, and &lt;code&gt;key&lt;/code&gt;. You rarely need to use it directly — and using it as a prop type is almost always wrong.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ReactNode&lt;/code&gt; is the union type React uses internally to define what can be rendered: &lt;code&gt;ReactNode = ReactElement | string | number | boolean | null | undefined | ReactNode[] | Portal | Fragment&lt;/code&gt;. It’s the &lt;em&gt;correct&lt;/em&gt; type for component return values and most props that accept content.&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="c1"&gt;// src/components/Card.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&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="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ Wrong: JSX.Element excludes null, strings, numbers, arrays&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&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;content&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="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ← TS2322: Type 'null' is not assignable to type 'JSX.Element'&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&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;title&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;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&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;content&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;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ Also wrong: JSX.Element doesn’t accept arrays of elements&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;List&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="nx"&gt;items&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;item&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;item&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;item&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="c1"&gt;// ← TS2322&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TypeScript rejects &lt;code&gt;null&lt;/code&gt; and arrays here because &lt;code&gt;JSX.Element&lt;/code&gt; is &lt;em&gt;not&lt;/em&gt; a superset of &lt;code&gt;ReactNode&lt;/code&gt;. It’s a subset — and only covers &lt;em&gt;single, non-null JSX elements&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The switch
&lt;/h2&gt;

&lt;p&gt;Replace &lt;code&gt;JSX.Element&lt;/code&gt; with &lt;code&gt;ReactNode&lt;/code&gt; in return types and props that need flexibility. Reserve &lt;code&gt;JSX.Element&lt;/code&gt; only for cases where you &lt;em&gt;explicitly want&lt;/em&gt; to forbid non-JSX values (like render props that must return JSX).&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="c1"&gt;// src/components/Card.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&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="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Correct: ReactNode accepts null, strings, numbers, arrays&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&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;content&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="kc"&gt;null&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;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&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;title&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;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&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;content&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;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Also correct: ReactNode handles arrays naturally&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;List&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="nx"&gt;items&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;item&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;item&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;item&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="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ For render props: use JSX.Element *only* if you want JSX-only&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;renderHeader&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="nl"&gt;renderHeader&lt;/span&gt;&lt;span class="p"&gt;:&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;JSX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ← strict JSX-only&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;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;renderHeader&lt;/span&gt;&lt;span class="p"&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;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ For flexible content (like children): use ReactNode&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&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;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"box"&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;children&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;div&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;p&gt;That single change addresses the cause because &lt;code&gt;ReactNode&lt;/code&gt; is the official type React uses to define what can be rendered — it’s designed to match runtime behavior, not just JSX syntax.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step by step
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open the file where you see the TS2322 error.&lt;/li&gt;
&lt;li&gt;Find the component or function with the incorrect type (e.g., &lt;code&gt;(): JSX.Element&lt;/code&gt; or &lt;code&gt;{ render: JSX.Element }&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Replace &lt;code&gt;JSX.Element&lt;/code&gt; with &lt;code&gt;ReactNode&lt;/code&gt; in return types and props like &lt;code&gt;children&lt;/code&gt;, &lt;code&gt;fallback&lt;/code&gt;, &lt;code&gt;empty&lt;/code&gt;, or &lt;code&gt;render&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Save and run &lt;code&gt;tsc --noEmit&lt;/code&gt; to confirm no errors.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Component props
&lt;/h2&gt;

&lt;p&gt;Typing &lt;strong&gt;component props&lt;/strong&gt; is the most common place developers reach for &lt;code&gt;JSX.Element&lt;/code&gt; and immediately hit TS2322. A prop describes a &lt;em&gt;slot&lt;/em&gt; the consumer fills — and that slot may legitimately receive &lt;code&gt;null&lt;/code&gt;, a string, a number, an array, or a fragment, not just a single JSX element.&lt;/p&gt;

&lt;p&gt;The pattern to internalize: when a prop represents &lt;em&gt;renderable content&lt;/em&gt;, type it as &lt;code&gt;ReactNode&lt;/code&gt;. When it represents &lt;em&gt;a function that must produce JSX&lt;/em&gt;, type it as &lt;code&gt;JSX.Element&lt;/code&gt;.&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="c1"&gt;// Common component props patterns&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Content slot: ReactNode&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ContainerProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Optional sections: ReactNode (not JSX.Element)&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CardProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;footer&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Render prop producing JSX: JSX.Element enforces a JSX-only return&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TableProps&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;renderRow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&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;JSX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;renderEmpty&lt;/span&gt;&lt;span class="p"&gt;:&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;JSX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Render prop that may return null/string: ReactNode&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ListProps&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;renderItem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&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;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;renderEmpty&lt;/span&gt;&lt;span class="p"&gt;?:&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;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ Common mistake: JSX.Element on a content prop&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BadProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ← rejects strings like icon="save", and null&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rules of thumb for component props:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;children&lt;/code&gt; and any "slot" prop&lt;/strong&gt; (&lt;code&gt;header&lt;/code&gt;, &lt;code&gt;footer&lt;/code&gt;, &lt;code&gt;body&lt;/code&gt;, &lt;code&gt;fallback&lt;/code&gt;, &lt;code&gt;empty&lt;/code&gt;, &lt;code&gt;overlay&lt;/code&gt;, &lt;code&gt;tooltip&lt;/code&gt;): use &lt;code&gt;ReactNode&lt;/code&gt;. Optional slots should be &lt;code&gt;ReactNode | undefined&lt;/code&gt; (or just &lt;code&gt;ReactNode&lt;/code&gt; — it already includes &lt;code&gt;undefined&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Render props where the consumer must return JSX&lt;/strong&gt; (&lt;code&gt;renderRow&lt;/code&gt;, &lt;code&gt;renderHeader&lt;/code&gt;, &lt;code&gt;renderCell&lt;/code&gt;): use &lt;code&gt;JSX.Element&lt;/code&gt; to enforce a strict JSX return.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Render props that may return a string, number, or &lt;code&gt;null&lt;/code&gt;&lt;/strong&gt; (&lt;code&gt;renderLabel&lt;/code&gt;, &lt;code&gt;renderEmpty&lt;/code&gt;): use &lt;code&gt;ReactNode&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid &lt;code&gt;ReactElement&lt;/code&gt; as a prop type.&lt;/strong&gt; It’s a runtime object shape (&lt;code&gt;{ type, props, key }&lt;/code&gt;) — consumers don’t think in those terms, and it’s not assignable from arbitrary JSX in modern React.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re publishing a component library, defaulting to &lt;code&gt;ReactNode&lt;/code&gt; for every content prop is the most forgiving and version-stable choice. It accepts everything a React render can produce, and you won’t break consumers when they pass a conditional &lt;code&gt;null&lt;/code&gt; or a fragment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Function return types
&lt;/h2&gt;

&lt;p&gt;The second hot spot is &lt;strong&gt;function return types&lt;/strong&gt; — both for React component functions and for helper functions that build JSX. The error pattern is always the same: a function is annotated as &lt;code&gt;(): JSX.Element&lt;/code&gt; (or &lt;code&gt;: ReactElement&amp;lt;...&amp;gt;&lt;/code&gt;), but the body returns &lt;code&gt;null&lt;/code&gt;, a primitive, or an array.&lt;/p&gt;

&lt;p&gt;The correct function return type depends on what the function is allowed to produce:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ReactElement&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="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ React component: ReactNode covers every legal render output&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Greeting&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="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&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;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// valid — ReactNode includes null&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`Welcome, &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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// valid — string&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;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hello, &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;name&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;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// valid — JSX&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ Component returning null with JSX.Element annotation&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Greeting&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="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nx"&gt;JSX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Element&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;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ← TS2322&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;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hello, &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;name&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;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Helper that always returns a single element: JSX.Element is fine&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Spinner&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;size&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nx"&gt;JSX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Element&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;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"spinner"&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ Helper that may return null: use ReactNode&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;MaybeSpinner&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&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;loading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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="nc"&gt;Spinner&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ Don't use ReactElement as a return type — it's an object shape, not a renderable value&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Broken&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;ReactElement&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;div&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt; &lt;span class="c1"&gt;// not assignable from arbitrary JSX in modern React&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Best practices for function return types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React component functions:&lt;/strong&gt; annotate as &lt;code&gt;ReactNode&lt;/code&gt;, or omit the annotation entirely and let TypeScript infer it. Inference usually picks the right type and adapts if you change the return.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Helper functions that always return a single element:&lt;/strong&gt; &lt;code&gt;JSX.Element&lt;/code&gt; is appropriate because you control the return and the narrowness is desirable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Helper functions that may return &lt;code&gt;null&lt;/code&gt;, a string, or a number:&lt;/strong&gt; use &lt;code&gt;ReactNode&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Render prop functions:&lt;/strong&gt; choose &lt;code&gt;JSX.Element&lt;/code&gt; if you require JSX, &lt;code&gt;ReactNode&lt;/code&gt; if you permit non-JSX returns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid &lt;code&gt;ReactElement&lt;/code&gt; as a return type on components or helpers.&lt;/strong&gt; It’s a concrete object type, not a renderable value type, and it doesn’t match what JSX expressions produce in modern React.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful shortcut: in most cases, you can simply &lt;em&gt;omit&lt;/em&gt; the return type annotation on React components. TypeScript will infer a &lt;code&gt;JSX.Element&lt;/code&gt; for the happy path and &lt;code&gt;null&lt;/code&gt;/&lt;code&gt;undefined&lt;/code&gt; for early returns, and the resulting inferred type is structurally compatible with &lt;code&gt;ReactNode&lt;/code&gt;. The annotation only matters when you want to &lt;em&gt;constrain&lt;/em&gt; what the function may return — which is exactly the situation where &lt;code&gt;ReactNode&lt;/code&gt; (broad) or &lt;code&gt;JSX.Element&lt;/code&gt; (narrow) gives you the behavior you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tsc &lt;span class="nt"&gt;--noEmit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you previously had TS2322 on &lt;code&gt;null&lt;/code&gt;, &lt;code&gt;string&lt;/code&gt;, or &lt;code&gt;array&lt;/code&gt; returns, those are gone now.&lt;/p&gt;

&lt;p&gt;If you’re still seeing errors, two common variants exist:&lt;/p&gt;

&lt;h3&gt;
  
  
  Variant A — Using &lt;code&gt;ReactElement&lt;/code&gt; instead of &lt;code&gt;ReactNode&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;You might see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TS2322: Type 'Element' is not assignable to type 'ReactElement'.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This happens when you type a prop as &lt;code&gt;ReactElement&lt;/code&gt; but pass JSX (which TypeScript infers as &lt;code&gt;Element&lt;/code&gt;, not &lt;code&gt;ReactElement&lt;/code&gt;). Fix it by switching to &lt;code&gt;ReactNode&lt;/code&gt; — or, if you &lt;em&gt;must&lt;/em&gt; use &lt;code&gt;ReactElement&lt;/code&gt;, explicitly cast with &lt;code&gt;as ReactElement&lt;/code&gt; (but avoid this unless you control the source).&lt;/p&gt;

&lt;h3&gt;
  
  
  Variant B — Strict JSX mode in Next.js or Vite
&lt;/h3&gt;

&lt;p&gt;In some setups (like Next.js 13+ with the &lt;code&gt;"jsx": "react-jsx"&lt;/code&gt; transform), &lt;code&gt;JSX.Element&lt;/code&gt; is aliased to &lt;code&gt;React.JSX.Element&lt;/code&gt;, which is &lt;em&gt;slightly&lt;/em&gt; different from &lt;code&gt;React.JSX.Element&lt;/code&gt; in older versions. If you’re mixing libraries (e.g., React 18 + old &lt;code&gt;@types/react&lt;/code&gt;), you might get TS2322 on valid JSX.&lt;/p&gt;

&lt;p&gt;Fix it by ensuring &lt;code&gt;@types/react&lt;/code&gt; and &lt;code&gt;react&lt;/code&gt; versions match (e.g., both v18.3+), and use &lt;code&gt;ReactNode&lt;/code&gt; universally — it’s the most stable cross-version choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going forward
&lt;/h2&gt;

&lt;p&gt;The confusion is mostly historical: early React docs leaned on &lt;code&gt;ReactElement&lt;/code&gt; loosely, and &lt;code&gt;JSX.Element&lt;/code&gt; got cargo-culted as a “safe” type. &lt;code&gt;ReactNode&lt;/code&gt; is the right one for almost everything — it’s what &lt;code&gt;React.FC&lt;/code&gt; used to use (though &lt;code&gt;React.FC&lt;/code&gt; itself is now discouraged).&lt;/p&gt;

&lt;p&gt;Rules to stop reaching for the wrong type:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;For return types:&lt;/strong&gt; Always use &lt;code&gt;ReactNode&lt;/code&gt; (or omit the return type entirely — TypeScript infers it correctly).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For props:&lt;/strong&gt; Use &lt;code&gt;ReactNode&lt;/code&gt; for &lt;code&gt;children&lt;/code&gt;, &lt;code&gt;fallback&lt;/code&gt;, &lt;code&gt;empty&lt;/code&gt;, &lt;code&gt;render&lt;/code&gt;, &lt;code&gt;childrenRenderer&lt;/code&gt;, etc. Use &lt;code&gt;JSX.Element&lt;/code&gt; &lt;em&gt;only&lt;/em&gt; for render props where you want to enforce JSX-only input (e.g., &lt;code&gt;&amp;lt;DataTable renderHeader={() =&amp;gt; &amp;lt;h1 /&amp;gt;} /&amp;gt;&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For libraries:&lt;/strong&gt; If you’re publishing a component library, explicitly type props with &lt;code&gt;ReactNode&lt;/code&gt; — it’s the most compatible choice across React versions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I cover this in detail in &lt;a href="https://www.iloveblogs.blog/post/typescript-javascript-migration-guide-2026" rel="noopener noreferrer"&gt;JS to TypeScript: Incremental Migration, No Full Rewrite&lt;/a&gt;, where we refactor legacy React components without breaking existing behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What’s the difference between JSX.Element and ReactElement in practice?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;JSX.Element&lt;/code&gt; is the &lt;em&gt;type&lt;/em&gt; that JSX expressions compile to — it’s a compile-time construct. &lt;code&gt;ReactElement&lt;/code&gt; is the &lt;em&gt;runtime object&lt;/em&gt; created by &lt;code&gt;React.createElement()&lt;/code&gt;. In practice, &lt;code&gt;JSX.Element&lt;/code&gt; is often aliased to &lt;code&gt;ReactElement&lt;/code&gt;, but in modern React, &lt;code&gt;JSX.Element&lt;/code&gt; is intentionally narrow, while &lt;code&gt;ReactElement&lt;/code&gt; is a specific object shape. You rarely need &lt;code&gt;ReactElement&lt;/code&gt; directly — and using it as a prop type is almost always a mistake.&lt;/p&gt;

&lt;h3&gt;
  
  
  When should I use ReactNode instead of JSX.Element as a return type?
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;ReactNode&lt;/code&gt; for &lt;em&gt;all&lt;/em&gt; component return types unless you have a strong reason to restrict rendering to JSX only (e.g., a strict render prop API). &lt;code&gt;ReactNode&lt;/code&gt; supports &lt;code&gt;null&lt;/code&gt;, strings, numbers, arrays, and fragments — which covers every valid React render scenario. &lt;code&gt;JSX.Element&lt;/code&gt; only supports single JSX elements, so it fails on &lt;code&gt;null&lt;/code&gt;, strings, and arrays.&lt;/p&gt;

&lt;h3&gt;
  
  
  How should I type component props in TypeScript React components?
&lt;/h3&gt;

&lt;p&gt;For component props that accept renderable content (&lt;code&gt;children&lt;/code&gt;, &lt;code&gt;header&lt;/code&gt;, &lt;code&gt;footer&lt;/code&gt;, &lt;code&gt;fallback&lt;/code&gt;, &lt;code&gt;render&lt;/code&gt;, &lt;code&gt;icon&lt;/code&gt;, &lt;code&gt;overlay&lt;/code&gt;, &lt;code&gt;tooltip&lt;/code&gt;, etc.), use &lt;code&gt;ReactNode&lt;/code&gt;. It accepts &lt;code&gt;null&lt;/code&gt;, strings, numbers, arrays, and JSX — everything a render can produce. Use &lt;code&gt;JSX.Element&lt;/code&gt; only for render prop functions where you want to enforce a JSX-only return. Avoid &lt;code&gt;ReactElement&lt;/code&gt; as a prop type: it’s a runtime object shape (&lt;code&gt;{ type, props, key }&lt;/code&gt;), not a renderable value, and it’s not assignable from arbitrary JSX in modern React.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the best function return type for React components in TypeScript?
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;ReactNode&lt;/code&gt; as the function return type for React components, or omit the annotation entirely so TypeScript infers it. &lt;code&gt;ReactNode&lt;/code&gt; covers &lt;code&gt;null&lt;/code&gt;, strings, numbers, arrays, fragments, and JSX — every legal render output. &lt;code&gt;JSX.Element&lt;/code&gt; is too narrow for components because it rejects &lt;code&gt;null&lt;/code&gt; and primitives, which is the most common cause of &lt;code&gt;TS2322&lt;/code&gt; on component return types.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I fix 'Type is not assignable to type JSX.Element' errors?
&lt;/h3&gt;

&lt;p&gt;First, check if the value is &lt;code&gt;null&lt;/code&gt;, a string, a number, or an array — if so, replace &lt;code&gt;JSX.Element&lt;/code&gt; with &lt;code&gt;ReactNode&lt;/code&gt;. If the value is a &lt;code&gt;ReactElement&lt;/code&gt; (e.g., from &lt;code&gt;React.cloneElement&lt;/code&gt;), you likely need &lt;code&gt;ReactNode&lt;/code&gt; or &lt;code&gt;ReactElement&lt;/code&gt; — but &lt;code&gt;ReactNode&lt;/code&gt; is safer for most use cases. If you’re using &lt;code&gt;React.cloneElement&lt;/code&gt;, type the result as &lt;code&gt;ReactElement&lt;/code&gt;, not &lt;code&gt;JSX.Element&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What return type should I use for components that might return null?
&lt;/h3&gt;

&lt;p&gt;Always &lt;code&gt;ReactNode&lt;/code&gt;. &lt;code&gt;ReactNode&lt;/code&gt; explicitly includes &lt;code&gt;null&lt;/code&gt; and &lt;code&gt;undefined&lt;/code&gt; — it’s designed for conditional rendering. &lt;code&gt;JSX.Element&lt;/code&gt; does not, which is why &lt;code&gt;return null&lt;/code&gt; fails with &lt;code&gt;JSX.Element&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I properly type children props in React components?
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;{ children: ReactNode }&lt;/code&gt;. This is the official React pattern and matches how &lt;code&gt;React.createElement&lt;/code&gt; handles children. Avoid &lt;code&gt;JSX.Element&lt;/code&gt; or &lt;code&gt;ReactElement&lt;/code&gt; — they exclude strings, numbers, and arrays, which are common &lt;code&gt;children&lt;/code&gt; values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.iloveblogs.blog/post/typescript-could-not-find-declaration-file-module-fix" rel="noopener noreferrer"&gt;Fix TS7016: Could Not Find Declaration File for Module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.iloveblogs.blog/post/typescript-module-has-no-exported-member-fix" rel="noopener noreferrer"&gt;Fix TS2305: Module Has No Exported Member in TypeScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.iloveblogs.blog/post/typescript-property-has-no-initializer-fix" rel="noopener noreferrer"&gt;Fix TS2564: Property Has No Initializer in TypeScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.iloveblogs.blog/guides/what-is-typescript-and-why-should-i-use-it-instead-of-javascript" rel="noopener noreferrer"&gt;What is TypeScript and why should I use it instead of JavaSc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.iloveblogs.blog/post/jsxelement-vs-reactnode-vs-reactelement" rel="noopener noreferrer"&gt;https://www.iloveblogs.blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>react</category>
      <category>troubleshooting</category>
    </item>
    <item>
      <title>TypeScript Getter Setter Errors: TS1056, TS1028, TS2378 Fix</title>
      <dc:creator>Mahdi BEN RHOUMA</dc:creator>
      <pubDate>Sat, 27 Jun 2026 09:41:59 +0000</pubDate>
      <link>https://dev.to/mahdi_benrhouma_fe1c6005/typescript-getter-setter-errors-ts1056-ts1028-ts2378-fix-5f43</link>
      <guid>https://dev.to/mahdi_benrhouma_fe1c6005/typescript-getter-setter-errors-ts1056-ts1028-ts2378-fix-5f43</guid>
      <description>&lt;p&gt;Nine times out of ten, &lt;code&gt;TS1056&lt;/code&gt;, &lt;code&gt;TS1028&lt;/code&gt;, or &lt;code&gt;TS2378&lt;/code&gt; on a getter or setter means &lt;code&gt;"target": "ES3"&lt;/code&gt; in &lt;code&gt;tsconfig.json&lt;/code&gt;. TypeScript won't emit accessors below ES5 because they compile to &lt;code&gt;Object.defineProperty&lt;/code&gt; — an ES5 primitive. Set &lt;code&gt;"target": "ES5"&lt;/code&gt; or higher and rebuild.&lt;/p&gt;

&lt;p&gt;If the error persists after that, there are two more cases below — getter returning &lt;code&gt;undefined&lt;/code&gt;, and a type mismatch between getter and setter.&lt;/p&gt;

&lt;h2&gt;
  
  
  The errors
&lt;/h2&gt;

&lt;p&gt;You'll see one or more of these errors in your terminal or IDE when compiling or saving a file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
error TS1056: Accessors are only available when targeting ECMAScript 5 and higher.

error TS1028: 'get' and 'set' accessors are not allowed in an ambient context.

error TS2378: A 'get' accessor must return a value.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It happens when you define a getter or setter inside a class, like this:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&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;this&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="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="nf"&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&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;Here, &lt;code&gt;private _name&lt;/code&gt; is a backing field guarded by TypeScript's property visibility modifiers—&lt;code&gt;public&lt;/code&gt;, &lt;code&gt;private&lt;/code&gt;, and &lt;code&gt;protected&lt;/code&gt;—which control who can read or write each member. The public getter methods and setter methods form a controlled read/write surface around that private field, so callers interact with &lt;code&gt;user.name&lt;/code&gt; instead of touching &lt;code&gt;_name&lt;/code&gt; directly. When any part of this contract breaks—an ECMAScript target version that's too low, ambient-context misuse, or a mismatch between the getter's and setter's types—the compiler raises one of the three errors above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;TypeScript's &lt;code&gt;get&lt;/code&gt; and &lt;code&gt;set&lt;/code&gt; accessors are syntactic sugar over JavaScript's &lt;code&gt;Object.defineProperty&lt;/code&gt;, which only works reliably in ES5 and later. If your &lt;code&gt;tsconfig.json&lt;/code&gt; compiler options set &lt;code&gt;"target"&lt;/code&gt; to an ECMAScript target version like &lt;code&gt;"ES3"&lt;/code&gt;, or to a higher version like &lt;code&gt;"ES2015"&lt;/code&gt; without enabling &lt;code&gt;"lib": ["ES5"]&lt;/code&gt;, the compiler refuses to emit accessor syntax because it cannot guarantee runtime support.&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;// src/types/user.ts&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// This line triggers TS1056 if target &amp;lt; ES5&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&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;this&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="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// This line triggers TS2378 if return type is void or missing&lt;/span&gt;

  &lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="nf"&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&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;TypeScript also forbids accessors in ambient contexts—like &lt;code&gt;.d.ts&lt;/code&gt; declaration files or &lt;code&gt;declare class&lt;/code&gt; blocks—because they describe runtime behavior that must be implemented elsewhere. For example:&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;// types/user.d.ts&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ❌ TS1028: not allowed in ambient context&lt;/span&gt;

  &lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="nf"&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="kr"&gt;string&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;This fails because declaration files must declare &lt;em&gt;signatures&lt;/em&gt;, not implementations. The compiler enforces this to prevent accidental omission of accessor logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix — bump your ES target
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ES5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"lib"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ES5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DOM"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"outDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"rootDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src"&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single change addresses the cause because ES5+ targets include native support for getter/setter syntax, and TypeScript's emitter now safely converts them to &lt;code&gt;Object.defineProperty&lt;/code&gt; calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step by step
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Open &lt;code&gt;tsconfig.json&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Locate the &lt;code&gt;"compilerOptions"&lt;/code&gt; block.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add or update &lt;code&gt;"target": "ES5"&lt;/code&gt; (or &lt;code&gt;"ES2016"&lt;/code&gt;, &lt;code&gt;"ES2020"&lt;/code&gt;, etc.).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure &lt;code&gt;"lib"&lt;/code&gt; includes &lt;code&gt;"ES5"&lt;/code&gt; or higher (e.g., &lt;code&gt;"ES2015"&lt;/code&gt; implies ES5+).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save and restart your dev server (&lt;code&gt;npm run dev&lt;/code&gt;) or re-run &lt;code&gt;tsc&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For declaration files, replace accessors with property signatures:&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;// types/user.d.ts&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ✅ Ambient property, no get/set&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then implement the actual class in a &lt;code&gt;.ts&lt;/code&gt; file:&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;// src/user.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;User&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;UserDef&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;../types/user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;UserDef&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&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;this&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="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="nf"&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&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;h2&gt;
  
  
  Check it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
npx tsc &lt;span class="nt"&gt;--noEmit&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Found 0 errors.&lt;/code&gt; — done. Still seeing errors? Two other variants:&lt;/p&gt;

&lt;h3&gt;
  
  
  Variant A — Getter returns &lt;code&gt;undefined&lt;/code&gt; or &lt;code&gt;void&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;TypeScript infers the return type from the body. If your getter has no return statement, or returns &lt;code&gt;void&lt;/code&gt;, you'll get &lt;code&gt;TS2378&lt;/code&gt;. These accessor return types must be explicit and consistent: the getter method's return type defines the property's read type, and the setter method's parameter type defines the write type. When those accessor return types and parameter types drift apart, the compiler raises &lt;code&gt;TS2378&lt;/code&gt; to prevent silent coercion bugs.&lt;/p&gt;

&lt;p&gt;Fix by ensuring the getter returns a value matching the expected type:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_debug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;boolean&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_debug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ✅ Explicit return&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="nf"&gt;debug&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;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_debug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&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;h3&gt;
  
  
  Variant B — Setter parameter type mismatch
&lt;/h3&gt;

&lt;p&gt;If the setter's parameter type is incompatible with the getter's return type, TypeScript reports &lt;code&gt;TS2378&lt;/code&gt; or &lt;code&gt;TS2345&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Fix by aligning types:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Temperature&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_celsius&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;celsius&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;number&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_celsius&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// ❌ Wrong: setter expects string, getter returns number&lt;/span&gt;

  &lt;span class="c1"&gt;// set celsius(value: string) { this._celsius = parseFloat(value); }&lt;/span&gt;

  &lt;span class="c1"&gt;// ✅ Correct: both use number&lt;/span&gt;

  &lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="nf"&gt;celsius&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="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_celsius&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&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;h2&gt;
  
  
  Preventing regressions
&lt;/h2&gt;

&lt;p&gt;Below ES5, TypeScript falls back to &lt;code&gt;__defineGetter__&lt;/code&gt; and &lt;code&gt;__defineSetter__&lt;/code&gt; — deprecated, non-standard, and the wrong choice for anything targeting modern runtimes. ES5+ guarantees &lt;code&gt;Object.defineProperty&lt;/code&gt;, which is what you want.&lt;/p&gt;

&lt;p&gt;To catch a future target drift in CI, add this ESLint rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"rules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"@typescript-eslint/ban-ts-comment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"no-undef"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"off"&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"parserOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"ecmaVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, run &lt;code&gt;tsc --noEmit&lt;/code&gt; in your CI pipeline. I cover this in detail in &lt;a href="https://www.iloveblogs.blog/post/typescript-javascript-migration-guide-2026" rel="noopener noreferrer"&gt;JS to TypeScript: Incremental Migration, No Full Rewrite&lt;/a&gt;, where I show how to gradually adopt accessors without breaking existing code.&lt;/p&gt;

&lt;p&gt;For deeper type safety, especially when working with Supabase or Next.js, see &lt;a href="https://www.iloveblogs.blog/guides/nextjs-supabase-type-safety-guide" rel="noopener noreferrer"&gt;Complete Type Safety Guide for Next.js and Supabase with TypeScript&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.iloveblogs.blog/guides/what-is-typescript-and-why-should-i-use-it-instead-of-javascript" rel="noopener noreferrer"&gt;What is TypeScript and why should I use it instead of JavaScript&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.iloveblogs.blog/guides/interfaces-vs-types-in-typescript" rel="noopener noreferrer"&gt;Interfaces vs Types in TypeScript: 2026 Best Practices&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.iloveblogs.blog/post/typescript-javascript-migration-guide-2026" rel="noopener noreferrer"&gt;JS to TypeScript: Incremental Migration, No Full Rewrite&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.iloveblogs.blog/guides/how-to-convert-a-string-to-number-in-typescript" rel="noopener noreferrer"&gt;How to Convert a String to Number in TypeScript: 2026 Fix&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.iloveblogs.blog/guides/nextjs-supabase-type-safety-guide" rel="noopener noreferrer"&gt;Complete Type Safety Guide for Next.js and Supabase with TypeScript&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.iloveblogs.blog/post/typescript-property-has-no-initializer-fix" rel="noopener noreferrer"&gt;Fix TS2564: Property Has No Initializer in TypeScript&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these adjustments—appropriate ECMAScript target versions, well-configured &lt;code&gt;tsconfig.json&lt;/code&gt; compiler options, carefully chosen property visibility modifiers, and aligned accessor return types—your TypeScript accessors, including getter methods and setter methods, will compile cleanly and behave predictably at runtime, keeping your build green.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.iloveblogs.blog/post/get-and-set-in-typescript" rel="noopener noreferrer"&gt;https://www.iloveblogs.blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>troubleshooting</category>
    </item>
    <item>
      <title>Docker Tutorial 2026: Dockerfile, Compose + Production Setup</title>
      <dc:creator>Mahdi BEN RHOUMA</dc:creator>
      <pubDate>Sat, 27 Jun 2026 09:41:52 +0000</pubDate>
      <link>https://dev.to/mahdi_benrhouma_fe1c6005/docker-tutorial-2026-dockerfile-compose-production-setup-2802</link>
      <guid>https://dev.to/mahdi_benrhouma_fe1c6005/docker-tutorial-2026-dockerfile-compose-production-setup-2802</guid>
      <description>&lt;p&gt;Three things break in every first Docker setup: builds that run 10+ minutes even when you changed one line, containers that log &lt;code&gt;ECONNREFUSED 127.0.0.1:5432&lt;/code&gt; while the database is clearly alive, and Postgres data that vanishes on every &lt;code&gt;compose down&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I hit all three containerizing a Next.js + Supabase stack for &lt;a href="https://www.iloveblogs.blog/post/build-fullstack-app-nextjs-supabase-step-by-step" rel="noopener noreferrer"&gt;Build a Full-Stack App with Next.js and Supabase in 20 Minutes&lt;/a&gt;. The fix wasn't fancier tooling — it was three patterns: a &lt;code&gt;.dockerignore&lt;/code&gt;, a multi-stage &lt;code&gt;Dockerfile&lt;/code&gt;, and a &lt;code&gt;docker-compose.yml&lt;/code&gt; with named networks and named volumes. Each one is small; together they make the difference between a stack you fight and one you forget is there.&lt;/p&gt;

&lt;p&gt;If you already know why containers lose data and just want the config, skip to Docker Compose.&lt;/p&gt;

&lt;p&gt;Assumes Node 20+, Docker 24+, and the V2 Compose plugin (&lt;code&gt;docker compose&lt;/code&gt;, with a space). The legacy &lt;code&gt;docker-compose&lt;/code&gt; Python tool is deprecated and won't be covered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Docker dev environments break
&lt;/h2&gt;

&lt;p&gt;The root cause of all three failures above is the same: treating the container as a black box instead of a process with its own filesystem, network namespace, and PID namespace.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;docker build&lt;/code&gt; is slow, you usually have &lt;code&gt;COPY . .&lt;/code&gt; before &lt;code&gt;npm ci&lt;/code&gt;, which invalidates the dependency layer on every source change. When two services can't reach each other, the app is connecting to &lt;code&gt;localhost&lt;/code&gt; — which inside a container is the container itself, not the host or other containers. When data disappears, you used an anonymous volume that Docker is happy to recreate empty on the next &lt;code&gt;up&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The mental model that fixes everything is short. &lt;strong&gt;Filesystem isolation&lt;/strong&gt; means anything written inside a container is gone on &lt;code&gt;rm&lt;/code&gt; unless it's in a named volume. &lt;strong&gt;Network isolation&lt;/strong&gt; means containers only see each other if they're attached to the same Docker network. &lt;strong&gt;Layer caching&lt;/strong&gt; means Dockerfile order matters — instructions that change rarely must come first, instructions that change every commit must come last. Every pattern in this guide is a concrete expression of one of those three invariants.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites — Install Docker on Your System
&lt;/h2&gt;

&lt;h3&gt;
  
  
  macOS
&lt;/h3&gt;

&lt;p&gt;Download Docker Desktop from &lt;code&gt;docker.com/products/docker-desktop/&lt;/code&gt;, drag it to Applications, and launch it once to grant the privileged helper. Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nt"&gt;--version&lt;/span&gt;
docker compose version
docker context &lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see &lt;code&gt;Docker version 24.x&lt;/code&gt; (or newer) and &lt;code&gt;Docker Compose version v2.x.x&lt;/code&gt;. The &lt;code&gt;default&lt;/code&gt; context should point at &lt;code&gt;desktop-linux&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Windows 11
&lt;/h3&gt;

&lt;p&gt;Docker Desktop requires WSL 2. Enable it from PowerShell as Administrator, then reboot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
wsl &lt;span class="nt"&gt;--set-default-version&lt;/span&gt; 2
wsl &lt;span class="nt"&gt;--update&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install Docker Desktop from the same installer. The first launch takes a minute while it provisions the WSL 2 distro. After that, run the same three &lt;code&gt;docker&lt;/code&gt; and &lt;code&gt;docker compose&lt;/code&gt; commands above to confirm.&lt;/p&gt;

&lt;h3&gt;
  
  
  Linux (Ubuntu 24.04+)
&lt;/h3&gt;

&lt;p&gt;Use Docker's official apt repo rather than the &lt;code&gt;docker.io&lt;/code&gt; package, which lags by months:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; ca-certificates curl gnupg
&lt;span class="nb"&gt;sudo install&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 0755 &lt;span class="nt"&gt;-d&lt;/span&gt; /etc/apt/keyrings
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://download.docker.com/linux/ubuntu/gpg &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sudo &lt;/span&gt;gpg &lt;span class="nt"&gt;--dearmor&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /etc/apt/keyrings/docker.gpg
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;a+r /etc/apt/keyrings/docker.gpg
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"deb [arch=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dpkg &lt;span class="nt"&gt;--print-architecture&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; /etc/os-release &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$VERSION_CODENAME&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; stable"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/docker.list &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Log out and back in for the &lt;code&gt;docker&lt;/code&gt; group change to take effect, then verify with the same three commands as macOS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dockerfile Basics — Build Your First Image
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;Dockerfile&lt;/code&gt; is a recipe. Every instruction creates a layer; layers are cached by their content hash. The single most important rule: put things that change rarely at the top, things that change every commit at the bottom.&lt;/p&gt;

&lt;p&gt;Here is a minimal &lt;code&gt;Dockerfile&lt;/code&gt; for a Node app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# syntax=docker/dockerfile:1&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:20-alpine&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PORT=3000&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "dist/server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to notice. First, &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;package-lock.json&lt;/code&gt; are copied &lt;em&gt;before&lt;/em&gt; the source — so &lt;code&gt;npm ci&lt;/code&gt; runs only when those files change, not on every source edit. Second, the syntax directive at the top enables BuildKit features on any Docker version, which we rely on later.&lt;/p&gt;

&lt;p&gt;The matching &lt;code&gt;.dockerignore&lt;/code&gt; is just as important:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;node_modules&lt;/span&gt;
.&lt;span class="n"&gt;git&lt;/span&gt;
.&lt;span class="n"&gt;gitignore&lt;/span&gt;
.&lt;span class="n"&gt;env&lt;/span&gt;
.&lt;span class="n"&gt;env&lt;/span&gt;.*
&lt;span class="n"&gt;Dockerfile&lt;/span&gt;
&lt;span class="n"&gt;docker&lt;/span&gt;-&lt;span class="n"&gt;compose&lt;/span&gt;*.&lt;span class="n"&gt;yml&lt;/span&gt;
&lt;span class="n"&gt;README&lt;/span&gt;.&lt;span class="n"&gt;md&lt;/span&gt;
&lt;span class="n"&gt;coverage&lt;/span&gt;
&lt;span class="n"&gt;dist&lt;/span&gt;
.&lt;span class="n"&gt;next&lt;/span&gt;
.&lt;span class="n"&gt;vscode&lt;/span&gt;
.&lt;span class="n"&gt;idea&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you skip &lt;code&gt;.dockerignore&lt;/code&gt;, two bad things happen. &lt;code&gt;COPY . .&lt;/code&gt; pulls in &lt;code&gt;node_modules&lt;/code&gt; from the host, which can be the wrong architecture and will explode inside the container. And the build context sent to the Docker daemon includes your entire &lt;code&gt;.git&lt;/code&gt; history, &lt;code&gt;.env&lt;/code&gt; secrets, and the previous build's &lt;code&gt;dist&lt;/code&gt; folder — making builds measurably slower.&lt;/p&gt;

&lt;p&gt;Build and run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; myapp:dev &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000 &lt;span class="nt"&gt;--name&lt;/span&gt; myapp myapp:dev
curl http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see your app's HTML. The image will be around 180 MB on Alpine. For a Next.js app specifically, &lt;code&gt;next start&lt;/code&gt; works in the container but &lt;code&gt;next dev&lt;/code&gt; does not, because in-container file watching breaks on bind-mounted volumes — covered below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Stage Builds — Optimize for Production
&lt;/h2&gt;

&lt;p&gt;A single-stage Dockerfile leaves dev tooling, build tools, and source files in the final image. A multi-stage build trims that down to the runtime only.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# syntax=docker/dockerfile:1&lt;/span&gt;

&lt;span class="c"&gt;# ---- deps ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;deps&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci

&lt;span class="c"&gt;# ---- build ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=deps /app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# ---- runtime ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runtime&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PORT=3000&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=deps /app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/public ./public&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;-S&lt;/span&gt; app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; adduser &lt;span class="nt"&gt;-S&lt;/span&gt; app &lt;span class="nt"&gt;-G&lt;/span&gt; app
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; app&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "dist/server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The named stages (&lt;code&gt;deps&lt;/code&gt;, &lt;code&gt;build&lt;/code&gt;, &lt;code&gt;runtime&lt;/code&gt;) let later stages &lt;code&gt;COPY --from=&lt;/code&gt; only what they need. &lt;code&gt;runtime&lt;/code&gt; never sees source files, never sees &lt;code&gt;typescript&lt;/code&gt;, never sees &lt;code&gt;next build&lt;/code&gt; output that shouldn't ship. The &lt;code&gt;USER app&lt;/code&gt; directive drops root, which matters for any image that talks to the network — many scanners flag root-running images as critical.&lt;/p&gt;

&lt;p&gt;Build only the final stage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;--target&lt;/span&gt; runtime &lt;span class="nt"&gt;-t&lt;/span&gt; myapp:1.0.0 &lt;span class="nb"&gt;.&lt;/span&gt;
docker images myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final image typically lands between 120 and 160 MB depending on what you copy. If you're shipping to a SaaS platform the way I describe in &lt;a href="https://www.iloveblogs.blog/guides/building-saas-nextjs-supabase" rel="noopener noreferrer"&gt;Complete Guide to Building SaaS with Next.js and Supabase&lt;/a&gt;, that's small enough to cold-start in a few seconds on a free tier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker Compose — Orchestrate Multi-Service Apps
&lt;/h2&gt;

&lt;p&gt;Container orchestration is the automated coordination of container lifecycles: starting them in order, replacing failed ones, scaling replicas up and down, and routing traffic to the healthy instances. Docker Compose is an orchestrator — just a small one, scoped to a single host. It reads a single declarative YAML file and starts every service in dependency order, which is exactly what local dev and small single-host deployments need. The V2 plugin syntax (&lt;code&gt;docker compose&lt;/code&gt;, with a space) is what we use here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;runtime&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp:dev&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/app&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NODE_ENV=development&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./src:/app/src:ro&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;

  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16-alpine&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${POSTGRES_USER:?POSTGRES_USER must be set in .env}&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in .env}&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pgdata:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${POSTGRES_USER}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-d&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;app"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pgdata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to notice. First, the app's &lt;code&gt;DATABASE_URL&lt;/code&gt; points at &lt;code&gt;db&lt;/code&gt;, not &lt;code&gt;localhost&lt;/code&gt; — &lt;code&gt;db&lt;/code&gt; is the service name, which Compose registers as a DNS entry on the shared network. Second, &lt;code&gt;pgdata&lt;/code&gt; is a &lt;em&gt;named&lt;/em&gt; volume declared at the bottom, so it survives &lt;code&gt;docker compose down&lt;/code&gt;. Postgres uses &lt;code&gt;/var/lib/postgresql/data&lt;/code&gt; as its data directory; that's the path we mount. The &lt;code&gt;healthcheck&lt;/code&gt; block on &lt;code&gt;db&lt;/code&gt; plus &lt;code&gt;condition: service_healthy&lt;/code&gt; on &lt;code&gt;app&lt;/code&gt; means the app doesn't try to connect before Postgres accepts connections. The &lt;code&gt;${POSTGRES_USER}&lt;/code&gt; and &lt;code&gt;${POSTGRES_PASSWORD&lt;/code&gt;} placeholders are read from a project-level &lt;code&gt;.env&lt;/code&gt; file (Compose picks it up automatically); the &lt;code&gt;:?...&lt;/code&gt; suffix makes Compose fail fast with a readable error if either variable is missing, so a forgotten &lt;code&gt;.env&lt;/code&gt; never silently falls back to a default.&lt;/p&gt;

&lt;p&gt;Bring it up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
docker compose ps
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt; app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you outgrow a single host, the same Compose spec is portable to larger orchestrators like Kubernetes, Docker Swarm, or managed platforms such as Fly.io, Render, and AWS ECS. The image you built above does not change — orchestration is a runtime concern, not an image concern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Networking — Connect Containers Securely
&lt;/h2&gt;

&lt;p&gt;Docker networking is the layer that gives each container its own network namespace and decides which other containers, hosts, and external networks it can talk to. Every container gets at least one virtual interface on a Docker-managed network; if you do nothing, that network is the default &lt;code&gt;bridge&lt;/code&gt; and it only reaches the outside world through a NAT on the host. Two containers on the same user-defined network can talk to each other by name; two containers on different networks cannot. Almost every "my service can't connect" bug is a misconfiguration of this layer.&lt;/p&gt;

&lt;p&gt;Compose creates a default network and attaches every service to it. You don't need to declare it explicitly — but you should, because explicit networks give you control over isolation when you grow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml (excerpt)&lt;/span&gt;
&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;

  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
    &lt;span class="c1"&gt;# no ports — db is not exposed to the host&lt;/span&gt;

  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7-alpine&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;app&lt;/code&gt; sits on both networks because it needs to be reached from the host (port 3000) &lt;em&gt;and&lt;/em&gt; reach the data tier. &lt;code&gt;db&lt;/code&gt; and &lt;code&gt;redis&lt;/code&gt; are only on &lt;code&gt;backend&lt;/code&gt; and have no &lt;code&gt;ports&lt;/code&gt; block, so they aren't reachable from the host at all — only from &lt;code&gt;app&lt;/code&gt;. This is the simplest security boundary Compose gives you for free.&lt;/p&gt;

&lt;p&gt;To inspect the network at runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker network &lt;span class="nb"&gt;ls
&lt;/span&gt;docker network inspect &amp;lt;project&amp;gt;_backend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inspect output lists every connected container, the subnet, and the gateway. If a service can't reach another, this is the first place to look — &lt;code&gt;ContainerConfig&lt;/code&gt; should show the right container IDs attached.&lt;/p&gt;

&lt;h3&gt;
  
  
  Service discovery — DNS on the Compose network
&lt;/h3&gt;

&lt;p&gt;Service discovery is the mechanism that lets a container find its peers by name instead of by hardcoded IP. On every user-defined Docker network, Docker runs an embedded DNS server that registers each container's service name as an A record pointing at its current IP. When &lt;code&gt;app&lt;/code&gt; resolves &lt;code&gt;db&lt;/code&gt;, the request never leaves the Docker host; the embedded resolver returns the live address of the &lt;code&gt;db&lt;/code&gt; container, and the connection is forwarded across the bridge.&lt;/p&gt;

&lt;p&gt;This is why &lt;code&gt;db&lt;/code&gt; works in &lt;code&gt;DATABASE_URL&lt;/code&gt; but &lt;code&gt;localhost&lt;/code&gt; does not. &lt;code&gt;localhost&lt;/code&gt; resolves to the container itself; &lt;code&gt;db&lt;/code&gt; is registered as an A record by the embedded DNS server. There is no &lt;code&gt;links:&lt;/code&gt; block, no &lt;code&gt;/etc/hosts&lt;/code&gt; editing, and no need to run Consul or etcd for a local dev stack. Scale a service to multiple replicas and the same hostname round-robins across them.&lt;/p&gt;

&lt;p&gt;To see the records at runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;app nslookup db
docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;app getent hosts redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a hostname fails to resolve, the two services are almost certainly on different networks. Check with &lt;code&gt;docker network inspect &amp;lt;project&amp;gt;_backend&lt;/code&gt; and move the service onto the right one, or add an explicit &lt;code&gt;networks:&lt;/code&gt; block so the dependency is visible in the file rather than implicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Volumes &amp;amp; Environment Variables — Persist Data and Config
&lt;/h2&gt;

&lt;p&gt;Docker volumes are the storage primitive that lets a container's filesystem outlive the container itself. A container's writable layer is torn down on &lt;code&gt;rm&lt;/code&gt;; a volume is a directory managed by Docker (on the host, in &lt;code&gt;/var/lib/docker/volumes/...&lt;/code&gt;) that one or more containers can mount. Volumes are the only safe way to keep database files, uploaded assets, and other state across rebuilds, restarts, and image upgrades.&lt;/p&gt;

&lt;p&gt;There are three volume shapes and they behave very differently.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Syntax&lt;/th&gt;
&lt;th&gt;Survives &lt;code&gt;down&lt;/code&gt;?&lt;/th&gt;
&lt;th&gt;Use for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Named&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pgdata:/var/lib/postgresql/data&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Database data, anything persistent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bind&lt;/td&gt;
&lt;td&gt;&lt;code&gt;./src:/app/src&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes (host-owned)&lt;/td&gt;
&lt;td&gt;Live source code, hot reload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anonymous&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/var/lib/postgresql/data&lt;/code&gt; (no name)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Almost never — avoid&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Anonymous volumes look like a short convenience, but Docker gives them a random hash and drops them when the container goes. That's how I lost a Postgres database in 2017 and learned this lesson.&lt;/p&gt;

&lt;p&gt;For environment variables, three patterns work in Compose. A literal block (fine for dev), an &lt;code&gt;.env&lt;/code&gt; file (better for secrets), and the host shell (best for CI). For real secrets in production, prefer Docker secrets on Swarm or the orchestrator's secret store — never bake &lt;code&gt;.env&lt;/code&gt; into the image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.override.yml&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env.local&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./src:/app/src:ro&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;docker-compose.override.yml&lt;/code&gt; file is auto-merged with &lt;code&gt;docker-compose.yml&lt;/code&gt; when you run &lt;code&gt;docker compose up&lt;/code&gt;. Use it for dev-only overrides (bind mounts, dev env vars) and keep the base file clean.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices — Efficient Images and Caching
&lt;/h2&gt;

&lt;p&gt;The Dockerfile patterns that buy you the most:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Order by change frequency.&lt;/strong&gt; &lt;code&gt;package.json&lt;/code&gt; and lockfile go before source. Source before build. Build output last.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;--mount=type=cache&lt;/code&gt;&lt;/strong&gt; in BuildKit for package manager caches. For npm, replace &lt;code&gt;RUN npm ci&lt;/code&gt; with:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# syntax=docker/dockerfile:1&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/root/.npm &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;package-lock.json,target&lt;span class="o"&gt;=&lt;/span&gt;package-lock.json &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;package.json,target&lt;span class="o"&gt;=&lt;/span&gt;package.json &lt;span class="se"&gt;\
&lt;/span&gt;    npm ci
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pin base images by digest.&lt;/strong&gt; &lt;code&gt;node:20-alpine@sha256:...&lt;/code&gt; is reproducible. &lt;code&gt;node:latest&lt;/code&gt; is not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One process per container.&lt;/strong&gt; Compose can run multiple, but a single container with multiple daemons makes healthchecks and restarts harder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;.dockerignore&lt;/code&gt; aggressively.&lt;/strong&gt; Smaller build context = faster &lt;code&gt;docker build&lt;/code&gt;, smaller attack surface.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two more that I learned the hard way. &lt;strong&gt;Don't run &lt;code&gt;apt-get upgrade&lt;/code&gt; in the Dockerfile&lt;/strong&gt; — it pulls in unrelated updates and breaks reproducibility. &lt;strong&gt;Don't use &lt;code&gt;ADD&lt;/code&gt; for local files&lt;/strong&gt; — it has surprising URL and tar-extraction behavior; &lt;code&gt;COPY&lt;/code&gt; is explicit and predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security — Scan Images and Harden Containers
&lt;/h2&gt;

&lt;p&gt;A 2026-era checklist before pushing an image anywhere public:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run a vulnerability scanner. Docker Scout ships with Docker Desktop and the &lt;code&gt;docker scout&lt;/code&gt; CLI.&lt;/li&gt;
&lt;li&gt;Run as non-root via &lt;code&gt;USER&lt;/code&gt; in the final stage.&lt;/li&gt;
&lt;li&gt;Drop capabilities you don't need.&lt;/li&gt;
&lt;li&gt;Pin base images by digest.&lt;/li&gt;
&lt;li&gt;Keep secrets out of layers (&lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;ARG&lt;/code&gt;, baked files).&lt;/li&gt;
&lt;li&gt;Use a minimal base — &lt;code&gt;alpine&lt;/code&gt;, &lt;code&gt;distroless&lt;/code&gt;, or scratch where possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Quick scan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker scout cves myapp:1.0.0
docker scout recommendations myapp:1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quick hardening diff for the runtime stage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runtime&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=deps /app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;-S&lt;/span&gt; app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; adduser &lt;span class="nt"&gt;-S&lt;/span&gt; app &lt;span class="nt"&gt;-G&lt;/span&gt; app
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; app&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "dist/server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;docker scout&lt;/code&gt; reports a critical CVE in &lt;code&gt;node:20-alpine&lt;/code&gt;, the fix is to bump the base tag (or pin to a newer digest). Don't &lt;code&gt;apt-get upgrade&lt;/code&gt; inside the Dockerfile to patch it — rebuild from a patched base image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting — Common Issues and Fixes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;bind: address already in use&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Another process on the host is using the port you mapped. Find and kill it, or change the host-side mapping in Compose (&lt;code&gt;"8080:3000"&lt;/code&gt; instead of &lt;code&gt;"3000:3000"&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lsof &lt;span class="nt"&gt;-i&lt;/span&gt; :3000
&lt;span class="c"&gt;# or on Windows:&lt;/span&gt;
netstat &lt;span class="nt"&gt;-ano&lt;/span&gt; | findstr :3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;ECONNREFUSED 127.0.0.1:5432&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The app is connecting to &lt;code&gt;localhost&lt;/code&gt;, which inside the container is itself. Change the connection string to the service name (&lt;code&gt;db&lt;/code&gt;), and make sure both services are on the same Compose network. This exact trap catches everyone running Next.js + Postgres for the first time — I cover the wider Supabase flow in &lt;a href="https://www.iloveblogs.blog/guides/building-saas-nextjs-supabase" rel="noopener noreferrer"&gt;Complete Guide to Building SaaS with Next.js and Supabase&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;no space left on device&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Docker's storage driver ran out of room. Prune:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker system &lt;span class="nb"&gt;df
&lt;/span&gt;docker system prune &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;--volumes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--volumes&lt;/code&gt; removes named volumes too — only run it if you're sure you don't need them.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;next dev&lt;/code&gt; is slow or hangs inside the container
&lt;/h3&gt;

&lt;p&gt;Bind-mount the source so file changes reach the container, but exclude &lt;code&gt;node_modules&lt;/code&gt; so the host's node_modules don't shadow the container's:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./src:/app/src:ro&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/app/node_modules&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The anonymous volume &lt;code&gt;/app/node_modules&lt;/code&gt; is intentional — it shadows the bind mount at that path, keeping the in-container &lt;code&gt;node_modules&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Image rebuilds every time even though only one file changed
&lt;/h3&gt;

&lt;p&gt;A file outside &lt;code&gt;.dockerignore&lt;/code&gt; (or no &lt;code&gt;.dockerignore&lt;/code&gt; at all) is invalidating the layer. Add it and rebuild — the next build should hit the cache for every layer above the changed one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sanity-check
&lt;/h2&gt;

&lt;p&gt;After &lt;code&gt;docker compose up -d&lt;/code&gt;, run this from the host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose ps
docker compose logs &lt;span class="nt"&gt;--tail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;50 app
curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; http://localhost:3000
docker stats &lt;span class="nt"&gt;--no-stream&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;NAME      SERVICE   STATUS          PORTS
&lt;/span&gt;&lt;span class="gp"&gt;myapp-app-1   app       Up 2 minutes (healthy)   0.0.0.0:3000-&amp;gt;&lt;/span&gt;3000/tcp
&lt;span class="go"&gt;myapp-db-1    db        Up 2 minutes (healthy)   5432/tcp
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;curl&lt;/code&gt; should print &lt;code&gt;200&lt;/code&gt;. &lt;code&gt;docker stats&lt;/code&gt; should show two containers with predictable CPU and memory numbers — if &lt;code&gt;app&lt;/code&gt; is at 100% CPU constantly, you probably have a hot-reload loop or a missing dependency that triggers restart.&lt;/p&gt;

&lt;p&gt;If you're using Next.js and the image optimization endpoint complains about a hostname not being configured, that's a Next.js-level concern unrelated to Docker — the fix is in &lt;a href="https://www.iloveblogs.blog/post/nextjs-image-hostname-not-configured-fix" rel="noopener noreferrer"&gt;Fix next/image Hostname Not Configured in Next.js&lt;/a&gt;. Likewise, when the App Router tells you a page couldn't be rendered statically because it uses dynamic server data, the answer isn't Docker — it's &lt;a href="https://www.iloveblogs.blog/post/nextjs-dynamic-server-usage-couldnt-be-rendered-statically-fix" rel="noopener noreferrer"&gt;Fix Dynamic Server Usage Error in Next.js App Router&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need Docker Desktop or can I use the CLI alone?&lt;/strong&gt;&lt;br&gt;
On macOS and Windows, Docker Desktop gives you the daemon, Compose, BuildKit, and Docker Scout in one install. On Linux, the CLI + Compose plugin + BuildKit from the apt repo is enough. Docker Desktop is optional on Linux but adds a GUI and Kubernetes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should I learn Kubernetes if I'm just starting with Docker?&lt;/strong&gt;&lt;br&gt;
No. Compose is the right tool until you actually have multiple hosts or need auto-scaling. I covered that trade-off in the SaaS guide linked earlier — Compose ships the same artifact to Fly.io, Render, and Railway without rewriting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use the same &lt;code&gt;docker-compose.yml&lt;/code&gt; in CI?&lt;/strong&gt;&lt;br&gt;
Yes. &lt;code&gt;docker compose build&lt;/code&gt; works in CI runners, and the resulting images push straight to a registry. For tests, swap the bind mount for an in-memory fixture and use &lt;code&gt;docker compose run --rm app npm test&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I keep my dev stack from drifting from production?&lt;/strong&gt;&lt;br&gt;
Pin base images by digest, run the same &lt;code&gt;Dockerfile&lt;/code&gt; everywhere, and run &lt;code&gt;docker compose up&lt;/code&gt; in CI against the same &lt;code&gt;docker-compose.yml&lt;/code&gt;. Drift is almost always a missing file in &lt;code&gt;.dockerignore&lt;/code&gt; or an unpinned base image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.iloveblogs.blog/post/build-fullstack-app-nextjs-supabase-step-by-step" rel="noopener noreferrer"&gt;Build a Full-Stack App with Next.js and Supabase in 20 Minutes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.iloveblogs.blog/guides/building-saas-nextjs-supabase" rel="noopener noreferrer"&gt;Complete Guide to Building SaaS with Next.js and Supabase&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.iloveblogs.blog/post/nextjs-image-hostname-not-configured-fix" rel="noopener noreferrer"&gt;Fix next/image Hostname Not Configured in Next.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.iloveblogs.blog/post/nextjs-dynamic-server-usage-couldnt-be-rendered-statically-fix" rel="noopener noreferrer"&gt;Fix Dynamic Server Usage Error in Next.js App Router&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.iloveblogs.blog/post/nextjs-chunkloaderror-loading-chunk-failed-fix" rel="noopener noreferrer"&gt;Fix ChunkLoadError: Loading Chunk Failed in Next.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.iloveblogs.blog/post/nextjs-use-client-vs-use-server-directives" rel="noopener noreferrer"&gt;use client vs use server in Next.js: The Real Difference&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a reproducible image, a versioned Compose file, named networks with embedded DNS, and named volumes in place, the stack is ready to push to any registry and redeploy on any platform without rewriting the patterns covered above.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.iloveblogs.blog/post/docker-tutorial-2026" rel="noopener noreferrer"&gt;https://www.iloveblogs.blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>dockercompose</category>
      <category>devops</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Supabase Storage: Complete Guide to File Uploads and Management</title>
      <dc:creator>Mahdi BEN RHOUMA</dc:creator>
      <pubDate>Tue, 23 Jun 2026 22:00:15 +0000</pubDate>
      <link>https://dev.to/mahdi_benrhouma_fe1c6005/supabase-storage-complete-guide-to-file-uploads-and-management-505</link>
      <guid>https://dev.to/mahdi_benrhouma_fe1c6005/supabase-storage-complete-guide-to-file-uploads-and-management-505</guid>
      <description>&lt;h1&gt;
  
  
  Supabase Storage: Complete Guide to File Uploads and Management
&lt;/h1&gt;

&lt;p&gt;Supabase Storage provides S3-compatible object storage with built-in CDN, image transformations, and security policies. This guide covers everything from basic uploads to production-ready patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client → Supabase Storage API → S3-Compatible Storage → CDN
   ↓                                                        ↓
Upload                                              Fast Delivery
   ↓                                                        ↓
RLS Policies ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ←
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup and Configuration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Create Storage Buckets
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Create a public bucket for avatars&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;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buckets&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="n"&gt;name&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="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'avatars'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'avatars'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Create a private bucket for documents&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;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buckets&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="n"&gt;name&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="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'documents'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'documents'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Storage Policies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Allow users to upload their own avatar&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;"Users can upload their own avatar"&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;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="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="o"&gt;=&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="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Allow users to read their own avatar&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;"Users can read their own avatar"&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;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="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="o"&gt;=&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="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Allow users to update their own avatar&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;"Users can update their own avatar"&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;UPDATE&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="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="o"&gt;=&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="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Allow users to delete their own avatar&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;"Users can delete their own avatar"&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;DELETE&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="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="o"&gt;=&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="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Basic File Upload
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Simple Upload Component
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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="p"&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;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="p"&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;createClientComponentClient&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/auth-helpers-nextjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;FileUpload&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;uploading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setUploading&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="kc"&gt;false&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;fileUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setFileUrl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&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;createClientComponentClient&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;uploadFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ChangeEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setUploading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You must select a file to upload.&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;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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;fileExt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&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="nf"&gt;split&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="nf"&gt;pop&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;fileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&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;span class="nx"&gt;fileExt&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fileName&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;uploadError&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;storage&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;avatars&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;upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&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="nx"&gt;uploadError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;uploadError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// Get public URL&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;data&lt;/span&gt; &lt;span class="p"&gt;}&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="nx"&gt;storage&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;avatars&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;getPublicUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nf"&gt;setFileUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setUploading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;
        &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;uploadFile&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;disabled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;uploading&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;uploading&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Uploading&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;}
&lt;/span&gt;      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;fileUrl&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;File&lt;/span&gt; &lt;span class="nx"&gt;uploaded&lt;/span&gt; &lt;span class="nx"&gt;successfully&lt;/span&gt;&lt;span class="o"&gt;!&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;fileUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Uploaded file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;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;h2&gt;
  
  
  Advanced Upload Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Avatar Upload with Preview
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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="p"&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;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&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="p"&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;createClientComponentClient&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/auth-helpers-nextjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Image&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/image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AvatarUpload&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentAvatar&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;avatar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setAvatar&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;currentAvatar&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;uploading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setUploading&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="kc"&gt;false&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;preview&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setPreview&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&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;fileInputRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&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;createClientComponentClient&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;handleFileSelect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ChangeEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="mi"&gt;0&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;file&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="c1"&gt;// Validate file type&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;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&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;image/&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;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Please select an image file&lt;/span&gt;&lt;span class="dl"&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;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Validate file size (max 2MB)&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;File size must be less than 2MB&lt;/span&gt;&lt;span class="dl"&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;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Create preview&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FileReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onloadend&lt;/span&gt; &lt;span class="o"&gt;=&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="nf"&gt;setPreview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readAsDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&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;uploadAvatar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setUploading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&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;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fileInputRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="mi"&gt;0&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;file&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="c1"&gt;// Delete old avatar if exists&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;avatar&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;oldPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;avatar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&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;storage&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;avatars&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;remove&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;span class="nx"&gt;userId&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;span class="nx"&gt;oldPath&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;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// Upload new avatar&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileExt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&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="nf"&gt;split&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="nf"&gt;pop&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;fileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&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;span class="nx"&gt;fileExt&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&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;span class="nx"&gt;fileName&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;uploadError&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;storage&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;avatars&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;upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;cacheControl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3600&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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="nx"&gt;uploadError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;uploadError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// Get public URL&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;data&lt;/span&gt; &lt;span class="p"&gt;}&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="nx"&gt;storage&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;avatars&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;getPublicUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Update user profile&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;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;updateError&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="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;profiles&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;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;avatar_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicUrl&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&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="nx"&gt;updateError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;updateError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="nf"&gt;setAvatar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;setPreview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Avatar updated successfully!&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="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setUploading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex flex-col items-center gap-4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;relative w-32 h-32 rounded-full overflow-hidden bg-gray-200&lt;/span&gt;&lt;span class="dl"&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;preview&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;avatar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Image&lt;/span&gt;
            &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;preview&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;avatar&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Avatar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="nx"&gt;fill&lt;/span&gt;
            &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object-cover&lt;/span&gt;&lt;span class="dl"&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;
        &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;fileInputRef&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;accept&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleFileSelect&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex gap-2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;
          &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&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;fileInputRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
          &lt;span class="nx"&gt;disabled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;uploading&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nx"&gt;Choose&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;preview&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;
            &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;uploadAvatar&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;disabled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;uploading&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600&lt;/span&gt;&lt;span class="dl"&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;uploading&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Uploading...&lt;/span&gt;&lt;span class="dl"&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;Upload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="p"&gt;)}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;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;h3&gt;
  
  
  2. Drag and Drop Upload
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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="p"&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;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useCallback&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="p"&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;createClientComponentClient&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/auth-helpers-nextjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;Upload&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;lucide-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;DragDropUpload&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onUploadComplete&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;isDragging&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsDragging&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="kc"&gt;false&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;uploading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setUploading&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="kc"&gt;false&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;progress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setProgress&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="mi"&gt;0&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;createClientComponentClient&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;handleDrag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DragEvent&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopPropagation&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="nx"&gt;handleDragIn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DragEvent&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopPropagation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;setIsDragging&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&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="nx"&gt;handleDragOut&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DragEvent&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopPropagation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;setIsDragging&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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="nx"&gt;handleDrop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DragEvent&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopPropagation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;setIsDragging&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataTransfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;uploadFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&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="nx"&gt;uploadFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;File&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="nf"&gt;setUploading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;uploadedUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;for &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;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&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;fileExt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&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="nf"&gt;split&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="nf"&gt;pop&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;fileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&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;span class="nx"&gt;i&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;span class="nx"&gt;fileExt&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&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;span class="nx"&gt;fileName&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;span class="k"&gt;try&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;error&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;storage&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;documents&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;upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;cacheControl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3600&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&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;data&lt;/span&gt; &lt;span class="p"&gt;}&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="nx"&gt;storage&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;documents&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;getPublicUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nx"&gt;uploadedUrls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;setProgress&lt;/span&gt;&lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&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="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Upload error:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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="nf"&gt;setUploading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setProgress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;onUploadComplete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uploadedUrls&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;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;
      &lt;span class="nx"&gt;onDragEnter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleDragIn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;onDragLeave&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleDragOut&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;onDragOver&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleDrag&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;onDrop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleDrop&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`
        border-2 border-dashed rounded-lg p-8 text-center
        transition-colors cursor-pointer
        &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;isDragging&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;border-blue-500 bg-blue-50&lt;/span&gt;&lt;span class="dl"&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;border-gray-300&lt;/span&gt;&lt;span class="dl"&gt;'&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;span class="nx"&gt;uploading&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;opacity-50 pointer-events-none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&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;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Upload&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w-12 h-12 mx-auto mb-4 text-gray-400&lt;/span&gt;&lt;span class="dl"&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;uploading&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text-gray-600 mb-2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Uploading&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w-full bg-gray-200 rounded-full h-2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;
              &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bg-blue-500 h-2 rounded-full transition-all&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
              &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;width&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;span class="nx"&gt;progress&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;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text-gray-600 mb-2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nx"&gt;Drag&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;drop&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="nx"&gt;here&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;or&lt;/span&gt; &lt;span class="nx"&gt;click&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;select&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text-sm text-gray-400&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="na"&gt;Supports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Images&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PDFs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Documents&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;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;h3&gt;
  
  
  3. Multiple File Upload with Progress
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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="p"&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;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="p"&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;createClientComponentClient&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/auth-helpers-nextjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CheckCircle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AlertCircle&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;lucide-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;UploadFile&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uploading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;url&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;MultiFileUpload&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&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;files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setFiles&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;UploadFile&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;createClientComponentClient&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;handleFileSelect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ChangeEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;selectedFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&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="na"&gt;newFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UploadFile&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;selectedFiles&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;file&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;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}));&lt;/span&gt;
    &lt;span class="nf"&gt;setFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&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;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;newFiles&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;uploadFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;fileData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="nf"&gt;setFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prev&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;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&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;i&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;index&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="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uploading&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;f&lt;/span&gt;
    &lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;try&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;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fileData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&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;fileExt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&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="nf"&gt;split&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="nf"&gt;pop&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;fileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&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;span class="nx"&gt;file&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="s2"&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;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&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;span class="nx"&gt;fileName&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;span class="c1"&gt;// Simulate progress (in real app, use XMLHttpRequest for actual progress)&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;progressInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&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="nf"&gt;setFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prev&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;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&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;i&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;90&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="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;
        &lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;200&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;error&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;storage&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;documents&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;upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;progressInterval&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="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&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;data&lt;/span&gt; &lt;span class="p"&gt;}&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="nx"&gt;storage&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;documents&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;getPublicUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nf"&gt;setFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prev&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;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&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;i&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;index&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="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicUrl&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;
      &lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prev&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;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&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;i&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;index&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="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&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="nx"&gt;uploadAll&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &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="k"&gt;for &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;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;uploadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&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="nx"&gt;removeFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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="nf"&gt;setFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&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;i&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;index&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;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;space-y-4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;
        &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;multiple&lt;/span&gt;
        &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleFileSelect&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;block w-full text-sm text-gray-500
          file:mr-4 file:py-2 file:px-4
          file:rounded-full file:border-0
          file:text-sm file:font-semibold
          file:bg-blue-50 file:text-blue-700
          hover:file:bg-blue-100&lt;/span&gt;&lt;span class="dl"&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;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;
          &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;uploadAll&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nx"&gt;disabled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
          &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nx"&gt;Upload&lt;/span&gt; &lt;span class="nx"&gt;All&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="p"&gt;)}&lt;/span&gt;

      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;space-y-2&lt;/span&gt;&lt;span class="dl"&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;files&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;fileData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;
            &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex items-center gap-3 p-3 border rounded-lg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex items-center justify-between mb-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text-sm font-medium&lt;/span&gt;&lt;span class="dl"&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;fileData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/span&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;
                  &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&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;removeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
                  &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text-gray-400 hover:text-gray-600&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;X&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w-4 h-4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
              &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;fileData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uploading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w-full bg-gray-200 rounded-full h-2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;
                    &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bg-blue-500 h-2 rounded-full transition-all&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                    &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;width&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;span class="nx"&gt;fileData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;progress&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;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;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;fileData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex items-center gap-2 text-green-600 text-sm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CheckCircle&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w-4 h-4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
                  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Uploaded&lt;/span&gt; &lt;span class="nx"&gt;successfully&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/span&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;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;fileData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex items-center gap-2 text-red-600 text-sm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AlertCircle&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w-4 h-4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
                  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;span&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;fileData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/span&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;              &lt;span class="p"&gt;)}&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="p"&gt;))}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;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;h2&gt;
  
  
  Image Optimization
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Image Transformations
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Get optimized image URL&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getOptimizedImageUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;format&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;webp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;avif&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="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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&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="nx"&gt;storage&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;images&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;getPublicUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;width&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="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&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="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;quality&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="nx"&gt;quality&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;format&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="nx"&gt;format&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Usage&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;thumbnailUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getOptimizedImageUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-photo.jpg&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;webp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Responsive Images Component
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Image&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/image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;createClientComponentClient&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/auth-helpers-nextjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ResponsiveImageProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ResponsiveImage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; 
  &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;sizes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100vw&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; 
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ResponsiveImageProps&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;supabase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClientComponentClient&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;getImageUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&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="nx"&gt;storage&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;images&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;getPublicUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicUrl&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;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Image&lt;/span&gt;
      &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;getImageUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
      &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;sizes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;srcSet&lt;/span&gt;&lt;span class="o"&gt;=&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;span class="nf"&gt;getImageUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; 400w,
        &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;getImageUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; 800w,
        &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;getImageUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; 1200w,
        &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;getImageUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1600&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; 1600w
      `&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w-full h-auto&lt;/span&gt;&lt;span class="dl"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Security Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. File Type Validation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ALLOWED_FILE_TYPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;images&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;image/jpeg&lt;/span&gt;&lt;span class="dl"&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;image/png&lt;/span&gt;&lt;span class="dl"&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;image/webp&lt;/span&gt;&lt;span class="dl"&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;image/gif&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;documents&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;application/pdf&lt;/span&gt;&lt;span class="dl"&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;application/msword&lt;/span&gt;&lt;span class="dl"&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;text/plain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;videos&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;video/mp4&lt;/span&gt;&lt;span class="dl"&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;video/webm&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;validateFileType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;ALLOWED_FILE_TYPES&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ALLOWED_FILE_TYPES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Usage&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="nf"&gt;validateFileType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;images&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid file type. Only images are allowed.&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. File Size Limits
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MAX_FILE_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;avatar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 2MB&lt;/span&gt;
  &lt;span class="na"&gt;document&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 10MB&lt;/span&gt;
  &lt;span class="na"&gt;video&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="c1"&gt;// 100MB&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;validateFileSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;MAX_FILE_SIZE&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;MAX_FILE_SIZE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;category&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;h3&gt;
  
  
  3. Secure File Names
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generateSecureFileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;originalName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;ext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;originalName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="nf"&gt;pop&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;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&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;random&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&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;span class="nx"&gt;timestamp&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;span class="nx"&gt;random&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;span class="nx"&gt;ext&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;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Advanced Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Signed URLs for Private Files
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Generate signed URL (expires in 1 hour)&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;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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;storage&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;private-documents&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;createSignedUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;document.pdf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3600&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="nx"&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Signed URL:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signedUrl&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;h3&gt;
  
  
  2. Batch Operations
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Delete multiple files&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filesToDelete&lt;/span&gt; &lt;span class="o"&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;file1.jpg&lt;/span&gt;&lt;span class="dl"&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;file2.jpg&lt;/span&gt;&lt;span class="dl"&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;file3.jpg&lt;/span&gt;&lt;span class="dl"&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;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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;storage&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;images&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;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filesToDelete&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Move/Copy Files
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Move file&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;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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;storage&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;images&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;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;old-path/image.jpg&lt;/span&gt;&lt;span class="dl"&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;new-path/image.jpg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Copy file&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;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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;storage&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;images&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;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;source/image.jpg&lt;/span&gt;&lt;span class="dl"&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;destination/image.jpg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Production Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Configure storage policies for all buckets&lt;/li&gt;
&lt;li&gt;[ ] Implement file type validation&lt;/li&gt;
&lt;li&gt;[ ] Set file size limits&lt;/li&gt;
&lt;li&gt;[ ] Use secure file naming&lt;/li&gt;
&lt;li&gt;[ ] Enable CDN caching&lt;/li&gt;
&lt;li&gt;[ ] Implement image optimization&lt;/li&gt;
&lt;li&gt;[ ] Add upload progress indicators&lt;/li&gt;
&lt;li&gt;[ ] Handle upload errors gracefully&lt;/li&gt;
&lt;li&gt;[ ] Clean up unused files periodically&lt;/li&gt;
&lt;li&gt;[ ] Monitor storage usage&lt;/li&gt;
&lt;li&gt;[ ] Implement rate limiting&lt;/li&gt;
&lt;li&gt;[ ] Add virus scanning for user uploads&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Supabase Storage provides a complete solution for file management with built-in security, CDN delivery, and image transformations. Key takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always implement RLS policies&lt;/li&gt;
&lt;li&gt;Validate file types and sizes&lt;/li&gt;
&lt;li&gt;Use image transformations for optimization&lt;/li&gt;
&lt;li&gt;Implement proper error handling&lt;/li&gt;
&lt;li&gt;Clean up unused files regularly&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related Guides
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.iloveblogs.blog/guides/supabase-authentication-authorization" rel="noopener noreferrer"&gt;Supabase Authentication &amp;amp; Authorization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.iloveblogs.blog/guides/supabase-realtime-complete-guide" rel="noopener noreferrer"&gt;Supabase Realtime Complete Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.iloveblogs.blog/guides/building-saas-nextjs-supabase" rel="noopener noreferrer"&gt;Building SaaS with Next.js and Supabase&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Last updated: February 19, 2026&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.iloveblogs.blog/post/supabase-storage-file-uploads" rel="noopener noreferrer"&gt;https://www.iloveblogs.blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>fileupload</category>
      <category>storage</category>
      <category>cdn</category>
    </item>
  </channel>
</rss>
