After that, I move very very fast. I prefer to hit "small" pains in short-term that try to do a huge jump later.
This mean that as long as I'm coding in a project (I'm contractor and do freelancing) I upgrade to whatever stable version my tools trow at me ASAP. And even minor versions or sometimes betas.
Not only language(s) - I keep at the same time different codebases in different languages, one main active plus maintenance-, I try to move everything: OS, RDBMS, IDEs, Editors, Frameworks, Libraries, etc. I probably hit "upgrade" every few days per week!
ONLY after the project stabilize and I do a few changes per-year, I stop doing that. And if the project somehow accelerate again I try to port ASAP.
How crazy is this? If the ecosystem I'm doing is nuts (ie: js) very clearly this reckless behavior show that.. QUICKLY.
...and then I need to pay more attention to my depencencies (keep them small) and choses only stable stuff.
I hit breaking changes not as often. And when see something painfull then I pin versions and keep them for a while.
This mean that I move ASAP and stay current as much as possible. I slow down when enter production, and slow down/stop again when the customer(s) stop requesting work.
This demand to use logging and have some testing, fast deployment/rollback, automate backups and use source control, very soon in the project, but this is good things anyway. (and maybe have a few VM or dockerized stuff but not as many despite more than 20 years doing crazy).
So far, this have work great for me, and never get stuck for months in rewrites like in the past. I get use to rewrite fast, that compensate to the fact that I'm a slow developer.