Frontend: skeleton loading instead of spinners

Spinners hide layout shifts and make an app feel slow even when it isn’t. Skeletons preserve layout and give users a sense of progress without jumping content around. I keep skeleton components simple and match the shape of the final UI. One practical

Batched writes with COPY (conceptual)

Row-by-row inserts are painfully slow for big ingests. Postgres COPY is a great bulk-ingestion tool, and in Node you can stream into COPY using libraries like pg-copy-streams. The important part is validating before you stream, because once you’re in

Prisma: avoid N+1 with include/select

Prisma makes it easy to write readable queries, but you can still create N+1 patterns by fetching parents and then fetching children in a loop. I prefer using include or select to get related data in one query when the cardinality is reasonable. I als

SQL migration safety: add column nullable, backfill, then constrain

The fastest way to surprise yourself in production is ADD COLUMN ... NOT NULL DEFAULT ... on a large table. My safe pattern is: add the column nullable with no default, backfill in batches, then add the NOT NULL constraint. If I need a default for new

GraphQL persisted queries (hash allowlist)

GraphQL endpoints can be abused with huge queries that are expensive to parse and execute. Persisted queries let clients send a hash (e.g. sha256:...) instead of the full query, and the server only executes queries it recognizes. This reduces payload

tRPC router pattern for type-safe APIs

Maintaining separate REST types and frontend client types can be a lot of overhead in TypeScript-heavy teams. With tRPC, the API types flow directly to the client, which reduces duplication and keeps refactors safe. Runtime validation still matters, s

Service worker: cache static assets safely

Offline-first is hard, but caching static assets is an easy win for repeat visits. I keep the service worker scope narrow and cache only versioned assets (hashed filenames) so I don’t accidentally serve stale HTML or API responses. The most common fai

Web Vitals reporting to an API endpoint

Synthetic performance tests don’t always match what real users experience. Web Vitals reporting gives you field data (CLS, LCP, INP, etc.). I collect vitals on the client and POST them to a lightweight endpoint, then aggregate on the backend. Sampling

Next.js middleware for auth gating

Protecting routes at the middleware layer prevents a whole class of ‘oops, we forgot to check auth on one page’ bugs. middleware.ts runs before rendering, so unauthenticated users get redirected early and you don’t waste work. I keep the logic simple:

React Hook Form + Zod resolver

Forms are where type safety and UX collide. react-hook-form keeps re-renders low, and Zod gives me a single schema I can share between frontend and backend if I want. I wire zodResolver so field-level errors show up automatically, and I keep a stable

MSW for frontend API mocking in tests

Brittle test suites often come from mocking fetch at every call site. MSW lets me mock at the network layer: components make real HTTP calls, but the test environment intercepts them and returns deterministic responses. That keeps tests closer to real

Email sending with nodemailer + templates

Email is still a core product channel, and it’s easy to ship broken messages if you don’t treat it like code. I keep templates in source control, render them with a simple templating engine, and send via nodemailer (or a provider SDK). I always includ