CI has to be fast enough that developers don’t bypass it. I cache npm’s package store so we’re not re-downloading the world every run, and I split lint / test / build into separate steps so failures are obvious and logs are readable. The other non-negotiable is determinism: pin the Node version and use npm ci (not npm install) so the lockfile is respected. When builds fail, I want the failure to be reproducible locally, not a flaky mystery. Uploading test coverage as an artifact is also a nice cheap win—easy to inspect without a third-party service. The payoff is fewer ‘works on my machine’ surprises and faster feedback loops.