Frontend: normalize and display server validation errors

Server-side validation is the source of truth, but raw error payloads are rarely UI-friendly. I normalize validation errors into a Record<field, message> shape so forms can render them consistently. The tricky detail is mapping server field path

Vite env handling: explicit prefixes only

Leaking secrets into the browser bundle is an easy mistake. Vite only exposes env vars with the VITE_ prefix, and I keep that rule strict. I also define a small typed wrapper so components don’t read import.meta.env directly everywhere. The wrapper gi

Prettier config for consistent formatting

Formatting consistency matters, but humans shouldn’t be the ones enforcing it in code review. Prettier makes diffs smaller and reviews more focused on logic. I keep the config minimal and aligned with team expectations (singleQuote, trailing commas, ~

ESLint config that avoids bikeshedding

I want linting to catch bugs, not fuel style debates. I use ESLint for correctness rules (unused vars, no-floating-promises, React hooks rules) and let Prettier handle formatting. I keep overrides minimal and justified. I also treat lint as part of CI

Next.js bundle analyzer for targeted performance work

Performance work is hard when you’re guessing what’s in your bundle. The bundle analyzer turns it into a visual diff: you can see which dependencies are big and whether code splitting is working. I gate it behind an env var like ANALYZE=true so it’s o

Response compression (only when it helps)

Compression can dramatically reduce payload sizes for JSON and HTML, but it also costs CPU. I enable it with sane defaults and avoid compressing already-compressed content (like images). Compression can also hurt streaming responses and SSE, so I disa

Security headers with helmet (baseline hardening)

Most security issues aren’t exotic—they’re missing headers and unsafe defaults. helmet gives a sensible baseline: headers that reduce clickjacking risk, tighten content-type sniffing, and improve general browser hardening. I still configure CSP explic

Multipart upload streaming (busboy)

Multipart uploads can blow up memory if you parse them naively. With busboy, I stream file data as it arrives and enforce size limits and content-type checks early. I avoid writing to disk unless I need it; for many flows I stream directly to object s

SQL upsert for counters (ON CONFLICT DO UPDATE)

Counters are classic race-condition bait. If two requests read, increment, and write, you’ll lose updates under load. I prefer letting Postgres do the atomic work with an upsert: INSERT ... ON CONFLICT ... DO UPDATE to increment the existing value. Th

Frontend: copy-to-clipboard with fallback

Copying code is a core interaction in a snippets app, and it needs to work reliably across browsers. The modern API is navigator.clipboard.writeText, but it requires HTTPS and permissions in some contexts. I try it first, and if it fails I fall back t

Backend: normalize errors with a single Express handler

Without a centralized error handler, you end up with a mix of thrown errors, ad-hoc res.status(500) blocks, and inconsistent JSON shapes. I use one Express error middleware that maps known errors to stable codes and logs unknown errors with request co

Frontend: toast notifications via a small event bus

I don’t want components depending on each other just to show a toast. A tiny event bus (or context) lets any part of the app emit a toast without wiring props through five layers. The important part is keeping the API small—something like show(message