My advice is to set up things as best you can right from the start, to make upgrading a relatively painless process.
For example:
- Good test coverage (particularly for critical points of your application) including CI pipeline
- Use dependabot or similar to make sure you get alerts for any critical updates (like security patches)
- Practice good dependency "hygiene" - only add a dependency when you really need it, and do vetting to ensure it's well-supported, has been updated recently, etc.
- Regularly audit and remove dependencies - when a library author declares they are sunsetting their project, plan on a replacement (even if that is, worst case, a fork). Remove dependencies which are no longer used in your code.
- Separate out your development/testing/production dependencies
- Use a good dependency manager: for example in Python, use Poetry or pip-tools or similar, rather than manually updating requirements.txt files yourself
- Update early and often: other than dependabot make it a maintenance task to check for updates at least once a week. Use managers, scripts etc to make this as easy and painless as possible.
Obviously if you are inheriting a legacy project you have to deal with the cards you are dealt, but these are a good target to move towards even with an old codebase.