TIL
Today I Learned — short, focused write-ups, usually under 300 words.
Making Stripe Webhook Handlers Idempotent After a Duplicate Purchase Grant
TIL that Stripe can and will deliver the same webhook event more than once, and a handler that reacts to checkout.session.completed by granting access will happily grant it twice — the fix was recording processed event IDs and treating the handler as a set operation, not an append.
Serving Premium Downloads Without Exposing the File's Real Storage URL
TIL how to replace a guessable direct-download link with a permission-checked, time-limited presigned URL, so the object storage path a purchased asset actually lives at is never sent to the client.
Server-Sent Events Instead of WebSockets for Live Vote Counts
TIL that live vote tallies only need server-to-client push, not a full-duplex protocol — swapping a hand-rolled WebSocket setup for Server-Sent Events dropped the reconnection logic entirely and simplified the reverse proxy config.
Live Poll Results Without WebSockets: Server-Sent Events in a Node.js API
TIL that once vote writes were race-free, the results screen still felt dead until a manual refresh — switching from client polling to Server-Sent Events made results update instantly without the bidirectional complexity WebSockets would have added for a channel that only ever sends in one direction.
Preventing Duplicate Votes in a Polling API Under Concurrent Requests
TIL that a 'check if the user already voted, then write' guard is racy under concurrent requests — the fix was a unique compound index plus an atomic $inc, so MongoDB rejects the duplicate instead of the app trying to catch it first.
A global color-scheme: dark leaks into print/PDF output unless you override it
TIL that setting color-scheme: dark once on html/body for a dark-themed site makes the browser paint its default canvas background dark everywhere that property cascades — including print and PDF output — unless @media print resets it.
Webhooks retry, so make the receiver idempotent — not the sender reliable
TIL that webhook delivery is at-least-once, not exactly-once. Instead of trying to prevent duplicate deliveries, derive a deterministic ID from the event and let the receiver refuse to redo work it already did.
A JSX expression-container attribute silently loses its value in next-mdx-remote
TIL that generating attr={"..."} into MDX source instead of attr="..." made a prop come back as an empty object on every render, with no error anywhere in the pipeline — found while writing the Obsidian-callout preprocessor for this site.
One Fuse.js index can search five different content types at once
TIL that a single flat Fuse.js index with weighted keys is enough to search blog posts, TIL entries, projects, case studies, and snippets together — no separate index or backend needed per collection.
A Dead WebSocket in a Broadcast List Breaks the Whole Broadcast Loop
TIL that looping over every connected WebSocket client and sending a message throws on the first one that's already gone, which silently drops the update for everyone still connected after it in the list — the fix is a try/except per connection, not around the loop.
A 'Hidden' S3 URL Isn't Access Control — Short-Lived Signed URLs Are
TIL that a private-looking direct download URL for a purchased file is still a public URL forever, and the fix is generating a signed, expiring link on demand after checking ownership server-side.
useOptimistic auto-reverts on failure — a hand-rolled optimistic hook doesn't
TIL why React's useOptimistic hook doesn't need explicit rollback code when a server call fails, while a manual optimistic-update hook built on plain useState does.
AEO vs SEO, and what I changed here to be more AEO-friendly
TIL: Answer Engine Optimization (AEO) optimizes for being the answer an AI cites, not just ranking in a list of blue links — and most of what makes a Next.js site AEO-friendly is stuff good SEO already wants.
MutationObserver re-triggers on your own injected DOM — guard against it
TIL a MutationObserver watching a third-party page fires again on the elements your own extension code just injected, causing an infinite injection loop unless the injection function is idempotent.
Debugging Problems That Only Appear in Production
TIL that a bug I couldn't reproduce locally turned out to depend on a real difference between environments — request concurrency — that my local setup structurally couldn't produce, no matter how hard I tried to repro it.
Using Traefik as a Reverse Proxy for a Self-Hosted Stack
TIL that manually editing an Nginx config and restarting it for every new service didn't scale past a handful of containers — Traefik's label-based routing removed that step entirely by discovering services automatically.
Using Docker for Application Deployment: The Boring Win I Underrated
TIL that 'works on my machine' stopped being a real category of bug the day I containerized the API, not because Docker is clever but because it removed an entire class of environment-drift I'd been debugging manually.
Using Framer Alongside a Development Workflow, Not Instead of One
TIL that building the Clonify Framer plugin taught me Framer is a genuinely different target than a normal web build — its plugin runtime and code-component model don't map 1:1 onto React conventions.
Turning Figma Designs Into Reusable React Components
TIL that building each screen straight from its Figma frame produced pixel-accurate but unreusable components — the fix was designing the component API from the design system's tokens, not from any one screen.
Designing a Delivery App for Unreliable Networks, Not Just Offline
TIL that 'handle offline' undersold the actual problem — the failure mode that hurt drivers most was a flaky, half-connected state, not a clean offline/online boundary.
Choosing Object Storage Instead of the Database for Application Images
TIL that storing delivery-proof photos as base64 blobs in MongoDB worked fine at low volume and turned into slow queries and ballooning backups once real usage kicked in.
Choosing Node.js Hosting for a SaaS: What Actually Mattered vs. What I Thought Would
TIL that I picked Render for a Node.js API expecting the deciding factor to be price, and the thing that actually mattered day to day turned out to be deploy simplicity and predictable cold-start behavior.
Designing a Complete Delivery-Management System as a Solo Developer
TIL that a system spanning a browser extension, a React Native app, an API, and a database only stayed maintainable solo because I treated the API as the one real product and everything else as a thin client of it.
Building Photo Proof of Delivery: Camera to Dashboard, End to End
TIL that the hard part of a photo-proof-of-delivery feature isn't the camera or the upload individually — it's making sure a completed delivery can never end up without its proof attached.
Designing a Dispatch Management Dashboard for Real Operations, Not a Generic Admin Panel
TIL that a dispatch dashboard built around 'show all the order fields' overwhelmed the person actually using it — the redesign that worked started from what a dispatcher needs to decide, not what data exists.
Writing a Reliable Regex for Extracting Delivery Times
TIL that a regex tuned against a handful of examples ('DELIVERY TIME: 2:00 PM') broke on real data within a day because of extra whitespace and inconsistent AM/PM casing I hadn't accounted for.
Extracting Delivery Information from Dynamically Rendered HTML
TIL that a content script querying the DOM immediately on page load found nothing, because the POS's order details render client-side, after the content script's own 'document_idle' point had already passed.
Route Handlers can return a plain Response, not just NextResponse
TIL you don't need NextResponse for a simple Route Handler — a standard Web API Response works fine, which matters for things like hand-rolled RSS/XML endpoints.
Building a POS Integration Without Controlling the POS
TIL that integrating with a system you don't own means designing for its changes, not just its current behavior — the architecture that survived was the one that assumed the POS would break the integration eventually.
Connecting a Chrome Extension to a Node.js API
TIL that a fetch call from a content script that worked fine in the console failed silently from the actual extension, because content scripts run in an isolated world with their own CORS and CSP rules.
Injecting Custom UI Into a Website You Don't Own
TIL that a naive injected panel kept getting wiped out by the host page's own re-renders — the fix was watching for that, not fighting it, and mounting into a container the host page has no reason to touch.
Reading Data from an Existing Web App with a Chrome Content Script
TIL that a content script reading order data straight from the DOM breaks every time the POS ships a markup change — the fix was reading from the most stable layer available, not the most convenient one.
Building a Chrome Extension with Manifest V3
TIL that MV3's move from persistent background pages to event-driven service workers means you can't hold long-lived in-memory state the way old extension tutorials assume — it gets killed and restarted constantly.
Converting API Timestamps to the User's Local Time Instead of Hardcoding Dubai Time
TIL that hardcoding a UTC+4 offset for timestamps because most orders happened to be local worked right up until a driver or a server ended up in a different timezone.
Understanding UnknownHostException in Android (and Why the API 'Worked Locally')
TIL that an Android UnknownHostException on a hostname that resolved fine everywhere else usually isn't a DNS problem at all — it's the app pointed at a hostname that only ever existed on my dev machine.
Debugging Production API Failures by Reading the Client's Error, Not the Server's Logs
TIL that when a mobile app reports 'network error' against a production API, the fastest path to the actual cause is the client-side error object, not tailing server logs that show nothing wrong.
Debugging an Android-Only FormData Upload Failure
TIL an upload that worked reliably on iOS and failed intermittently on Android came down to a missing file extension on the filename I was generating — Android's multipart parsing cared, iOS's didn't.
Uploading Images from React Native to a Node.js API
TIL that a multipart upload from React Native needs the file described as an object with uri/name/type, not sent as a raw file:// string — an easy mismatch to miss since it fails silently on some devices.
Understanding React Native File URIs Before You Try to Upload One
TIL that a file:// URI from Expo's camera isn't a file you can send as-is — it's a path into a cache directory the OS can clear, and the upload code needs to treat it that way.
Android-Specific React Native Problems That Never Show Up on iOS
TIL that a React Native feature working perfectly in the iOS simulator is not evidence it'll work on Android — cleartext traffic, back-button handling, and permission dialogs all diverge silently.
Building React Native Apps with Expo: What the Managed Workflow Actually Buys You
TIL that starting a React Native app in Expo's managed workflow paid off immediately for camera/location access, but I still had to understand what 'ejecting' would cost before I needed it.
Designing One API for Both a Web Dashboard and a Mobile App
TIL that a REST API originally shaped around the web dashboard's screens needed real redesign, not just new endpoints, once a React Native app started consuming it too.
Building Authentication Into a Full-Stack App Without Reinventing Sessions Badly
TIL that storing a JWT in localStorage for an app that also needed server-rendered authenticated pages was the wrong call, and cookie-based sessions were less work, not more.
Building Applications with Payload CMS Instead of a Custom Admin Panel
TIL that hand-rolling an admin panel for content the client needed to edit themselves cost more time than adopting Payload CMS's schema-as-code approach, even with its own learning curve.
Debugging a MongoDB/WiredTiger Cache Pressure Issue in Production
TIL that intermittent MongoDB slowdowns that don't show up in query logs can be a WiredTiger cache eviction problem, not a query problem — and the fix was a resource limit, not an index.
Designing MongoDB Schemas with Mongoose Without Fighting Yourself Later
TIL that a MongoDB collection modeled around how a screen displayed data instead of how the data actually related caused a rewrite once a second screen needed the same data differently.
Building REST APIs with Node.js and Express That Don't Fall Apart at Route 40
TIL that an Express API without a consistent response/error shape is fine at 10 routes and a real liability at 40 — the fix was boring middleware, not a framework change.
Using RTK Query for API Communication Instead of Hand-Rolled Fetching
TIL that most of my hand-written loading/error/cache state was solving a problem RTK Query already solves, and the switch removed more bugs than it added dependencies.
Building Reusable React Components Across Web and Mobile
TIL sharing components across a React web app and a React Native app only works if you split presentation from platform primitives from day one — retrofitting it later means rewriting most of your UI layer.
Moving from PHP to React: What Actually Changes
TIL the hard part of moving from PHP to React isn't JSX syntax — it's unlearning page-per-request thinking and stopping the urge to mutate the DOM yourself.