Because this is a topic that is both broad and deep, it's not efficient to try to pack everything into one entry. Instead, for this first post, I'll just cover the three most foundational topics for a front end application.
The first principle of any web application must be a strict adherence to simplicity. I beg of you that you watch this video of Rich Hickey giving a talk titled "Simple Made Easy" at Strangeloop 2011.
One of the many take-aways from that talk is this: Simple is the opposite of complex, and complex means "interconnected" or "intertwined." Simple code is not monolithic, because monoliths touch many parts of your application, inherently increasing the complexity of it. Moreover, simplicity is not how easy something is. They are not opposed ideas, but to avoid complexity, you must sometimes choose simplicity over how easy something is.
In tandem with the rejection of simplicity in favor of temporary easiness, the lost decade of development also built a culture of platform rejection. Despite building for the web, the companies and thought-leaders driving the industry pushed for less and less dependence on it. Throughout the years, this gave rise to many attempts to bridge the gap back to the platform like prop drilling, global variables (Flux, Redux, Vuex), "polymorphic" code, and on and on. Each aimed to solve a problem introduced by the fevered dogma that the way things were done before was bad, actually, and we've figured out the right new way. Never you mind all these glaring issues, we'll patch them over with the hottest new thing you need to add to your stack. Developers constantly longed for a return to the platform, but they were sold a pack of lies that the platform was broken and they needed these bloated tooling solutions (and of course their glorious leaders) to make good software.
Features that should have been implemented on top of browser-native tools were instead implemented - shoddily, slowly, and in user-hostile ways - in authored code, running through a slow compiler (sometimes multiple slow compilers!).
Indeed, JS - and the entire web platform - has been evolving (quite rapidly) while the anti-web web developers have been churning through libraries and patterns to try to re-implement the web on top of the web, while trying to avoid using the web.
There are far too many deeply technical topics to go into thoroughly here, so here is a short list of facts:
Other than a general focus on simplicity and relying on the robust platform itself, one of the most fundamental parts of building a web application is addressing how things communicate. Some of those "things" will be within the same app, and some of those "things" will be other apps, but they all need to communicate.
While "app communication" may not be the most important concept (that's undoubtedly a fervent adherence to simplicity), it is without a doubt the most important section to grasp and implement correctly, which is why most of this post is dedicated to it.
The previous sections have been - largely - conceptual. Application communication moves from concepts like "simplicity" and "using the platform" to critical direction. Failing to properly solve the problem of application communication in a versatile and flexible way that still maintains application simplicity is a matter of survival; applications that fail in this manner always have deep performance and user satisfaction problems and it is always sooner than expected and they are always complex, tangled messes that are intractable. The "dreaded full rewrite" is a trope not because it's fun, but because developers fail to architect the communication fundamental and reach for the same broken solutions.
Many supposed solutions to this problem have arisen from the ashes of what modern development torched as "the past." There were solutions that relied heavily on the concept of data changing (Flux, Redux, Vuex). There were solutions that depend on API responses - cached or not - only to eventually be essentially the same concept (GraphQL, Apollo). There were solutions that demanded extremely tight coupling, which resulted in code patterns like property drilling and logical mixins.
In nearly all of these so-called solutions, imperative commands are integrated directly into where they are needed in the moment. This is easy and convenient for developers, but leads to out-of-control bloat and - perhaps more importantly - disastrously fractured code that attempts to interface with the application in just one way, but tries to solve many problems that become increasingly specialized over time. The costs of this bloat and complexity are never borne by the developers, they are always passed onto the consumer.
The actual solution - which is foundational to the entire internet and web browsers - is to use events.
If an application strictly adheres to an event system, it cleanly separates interactions from actions, and triggers from behaviors. Safe in that separation, the application is free to add more specialized actions and behaviors, bifurcate interactions and triggers, and generally grow linearly and - critically - simply.
It may be helpful to think of this architecture as similar to one of the most successful architectures of all time: the human body.
Take a simple action that's often used as an example: touching a hot stove. There is no embedded command in the hands or fingers for "when you touch a hot stove." What happens is that the nerves in the fingers identify a trigger: "this temperature is extremely high." That trigger is passed along to the brain just like that: "the fingers have sensed an extremely high temperature." The brain then decides how to handle that. In the vast majority of people, the brain converts that message into "pain!" Consider that "pain!" is not handled by the fingers or the hands. It is the central nervous system's generic response to many events. It just so happens that the brain responds to "the fingers have sensed an extremely high temperature" with the generalized reaction of "pain!" Finally, the brain (when functioning normally) sends a message back down along the nerves: "the hand should be retracted!" This is managed by the particular appendage, which knows how to receive messages like "retract hand!"
This brain/body structure works well because each part of the body doesn't need to know what everything means. The brain knows that extreme temps mean pain and knows how to react to that, but the hand simply sends along the thing that's happening and knows how to contract muscles.
This separation is important, because it makes humans flexible. We can learn new reactions and associations without growing a new appendage. If we lose a hand, we can adjust how we live our lives because what we need to do is handled by the brain, which can adapt and send the same common reactions in unique combinations to get the same job done.
This flexibility is possible in web applications, too. Consider a similar flowchart, but for reloading some data in a display.
Note that this application does not have to connect "reloading Our Data" to the data table button, or even to any UI element at all. It's just an event that can happen as a result of anything, including user interaction. Likewise, note that the data table doesn't update when the data for it changes, it updates when the application tells it that an update occurred. That doesn't necessarily mean the data changed, just that the application considers the data updated. That's a powerful nuance that disconnects triggers from behaviors. The data table could refresh its data display at any time the
our-data:updated event is observed, regardless of the trigger.
Like a body and brain, this strict separation allows flexibility, adjustment, and remixing. New combinations of reactions are possible without affecting what triggers cause those reactions or where they originate.
This is not a comprehensive entry on this subject. The web application architecture is an extremely broad topic, and it has deeply technical discussions embedded in it.
But, for a beginning, this will have to do. Focus on these three things to eliminate the technical debt of bad early decisions that compound for years.