* Intercept link clicks and form submissions and perform the exact same request asynchronously (AJAX)
* Wait for the server to respond with the parts of the page that need to be swapped-out (a JSON map of section IDs to plain HTML content) and, as expected, swap out those parts of the page.
This works REALLY well for, like, 80% of my use cases. With a little extra flavor functionality (such as automatic loading indicators and event listeners) I can enable like 90% of interactivity. The remaining 10% consists of things that affect only temporary state and don't require a server round-trip (e.g. a "select all" checkbox), and those I would handle with more traditional jQuery DOM manipulation.
For the most part, once I had this basic system in place, I could rely on the server to render (and re-render) all of the HTML and rarely had to write any custom Javascript. I was essentially relying on the behavior of vanilla links and forms. The site would even continue to function if you disabled Javascript, because the server would see that it wasn't AJAX and just render the whole page instead. The downside was that preserving local state (e.g. partially completed form fields) was tricky whenever I had to make a change to one of its parent containers.
Everything I learned about this approach eventually led me to appreciate what React.js is doing. I've been using it and I may not like all of its API or how heavy it feels overall, but the cycle of rendering and re-rendering different parts of the page feels very natural and is far easier to debug than most JS-heavy front-ends.