Makefiles coordinate your shell scripts, using a dependency tree you'd have to reimplement if you did it purely with shell scripts.
For example:
A) You have a recipe that installs dependencies if they're not up-to-date (such as "pip install").
B) You have a recipe that compiles stuff if it's not up-to-date; it's made to depend on A.
C) "make" can be an interface recipe that depends on B and does nothing else.
D) "make test" can be an interface recipe that depends on B, then runs tests.
E) "make run" can be an interface recipe that depends on B, then actually runs the code / a webserver to interact with.
Nowadays whenever I put one of these together there's 2 versions of A (one for python, one for node), 1+ versions of B (webpack build without watch, maybe others depending on the project), 3 versions of D ("test-js", "test-python", and "test" that does both, and each of them only requires the relevant parts of A and B).
Makes it trivial to ensure you're up-to-date after a "git pull" without having to waste time waiting on things that don't need updating.