learnings
things that shifted my understanding
7 entries · newest first
solana wallet signature verification without a heavy SDK
crypto2026-04-12You don't need @solana/web3.js (which is enormous) to verify ed25519 signatures server-side. tweetnacl provides nacl.sign.detached.verify which takes three Uint8Arrays: message bytes, signature bytes, and the 32-byte public key. bs58 decodes the base58 Solana address to the key bytes. Total overhead: ~6kb for tweetnacl + ~3kb for bs58. The Phantom wallet's signMessage returns a { signature: Uint8Array } object — convert to Array.from() for JSON transport, reconstruct with new Uint8Array() on the server.
async params in Next.js 15+ route handlers
next.js2026-04-12In Next.js 15 (and 16), the params object in dynamic route handlers is a Promise, not a plain object. The correct signature is: export async function POST(_req: NextRequest, { params }: { params: Promise<{ id: string }> }) and then const { id } = await params. Using params.id directly (the old pattern) causes a runtime error. The TypeScript types reflect this change — the compiler will warn if you forget the await.
bs58 v5 vs v6 import differences
js2026-04-12bs58 v5 uses CommonJS exports: require('bs58') gives you an object with .encode and .decode directly. bs58 v6 changed to named ESM exports. The defensive import pattern that works for both: const bs58mod = require('bs58'); const decode = typeof bs58mod.decode === 'function' ? bs58mod.decode : bs58mod.default?.decode. This handles the CJS/ESM boundary without knowing which version is installed.
tailwind CSS v4 setup is different from v3
css2026-04-11Tailwind v4 uses @import "tailwindcss" at the top of globals.css instead of @tailwind base/components/utilities. The @theme block replaces tailwind.config.js for design tokens. Un-layered CSS (even simple resets like * { margin: 0 }) overrides Tailwind utilities due to cascade layer specificity — everything custom should go in @layer base, @layer components, or @layer utilities. The postcss plugin is @tailwindcss/postcss, not tailwindcss.
solana wallet injection patterns across different wallets
solana2026-04-11Phantom injects both window.phantom.solana (preferred) and window.solana (compat). Backpack injects window.backpack and window.solana. Solflare uses window.solflare and window.solana. The safest fallback chain: window.phantom?.solana ?? window.solana. All of these implement the standard Solana wallet interface — connect(), signMessage(), publicKey — but signMessage return shapes vary slightly. Phantom returns { signature: Uint8Array, publicKey: PublicKey }; others may return just Uint8Array. Destructure carefully.
supabase RLS with anon key — insertions and the update policy gap
supabase2026-04-10With Supabase's anon key, you can perform operations if RLS policies permit the anon role. INSERT with check (true) allows any anon insert. But for atomic increments (upvotes), a straight .update() with a read-then-write pattern has a race condition. The clean solution: create a SECURITY DEFINER SQL function that does the increment atomically (UPDATE ... SET upvotes = upvotes + 1 ... RETURNING upvotes) and call it via supabase.rpc(). The SECURITY DEFINER attribute makes it run with the function owner's privileges, bypassing RLS — which is safe here because the logic is self-contained.
force-dynamic with fs.readFileSync in server components
next.js2026-04-10To read a file on every request (instead of at build time), add export const dynamic = 'force-dynamic' to the route file and call readFileSync inside the component function (not at module level). Module-level calls are evaluated once during bundling. Function-level calls with force-dynamic re-execute on each request. This lets you update a JSON file (e.g., thoughts.json) and see the new content on page refresh without a rebuild.