Not long ago, servers ruled the land of web applications. They stood tall, rendering screens for their users and offering up gigs of processing power to perform actions. You went to a URL, and that server gave you everything you needed. It would do its work behind the scenes while you clicked through a series of screens. But then the single-page applications (SPAs) came and unseated these behemoths. Everything was handled through one URL. Server-side-rendered websites were considered obsolete, a relic of a bygone age. But are they really relics? Are SPAs the next evolution of all web applications? Absolutely not. Let's talk about what server-side-rendered websites are and why they're still useful.
The Lay of the Land
Many server-side web technologies share a set of common components and relationships. There's always some variation, but the diagram below shows what makes up your standard server-side rendering framework, centered around the MVC pattern. (I'll summarize each component.) Now, this doesn't describe all server-side frameworks out there, but popular ones such as Spring MVC and ASP.NET fit this bill.
The Router
The router is commonly a front controller, taking in an HTTP request and converting it into the correct controller method to call. It also binds the request's information into the request's input model. This is where a lot of cross-cutting middleware goes, such as authentication strategies and logging. This is also where non-controller resources, such as CSS and static HTML pages, are handled.
The Controller
This is the core unit of work for a web request. The controller's main goal is to map one or more URLs to an executable model. It takes an input model representing the HTTP request and URL, and then it either changes state if it's a POST request or it returns data if it's a GET request. Ideally, these are fairly skinny, delegating most work to the model. There are usually pre- and post-request hooks that you can wire up for cross-cutting concerns, such as authorization and setting up database transactions.
The Model
The model is the meat and potatoes of the system. The goal here is to do the real work. It's as simple as that.
The View
This is the page that the user will see. It renders the model as a browser-readable page, usually HTML. It's often rendered via a template engine with its own syntax, such as Razor.
Where Server-Side Wins
Now that you understand the components of server-side rendering, let's dive into why it still has value. Even though SPAs still have a fading "cool factor," server-side rendering offers up some things out of the box that are unmatched.
Server-Side Lets the Browser Do Its Job
We have a wonderful device that can access billions of pieces of information. It can render and process millions of different tasks of varying categories. It puts thousands of relaxing games at our fingertips. And gee, golly, it's free! It's our web browser. These beautiful beasts are feature rich and let us build content across any industry that's accessible to our customers. Why would we take all this hard-earned effort and throw much of it down the drain? This is what many SPAs do! Browsers have quite a few standard features. For one, they're really good at rendering HTML. They're also fairly good at caching static assets, making our web browsing experience fast. And they automatically support backward and forward navigation. I can bookmark and continue any step in a workflow, such as saving my shopping cart before I place an order. When I render on the server side, I get all this for free. However, most SPAs fail to use these features out of the box. It's common to find SPAs that actually have less functionality than if you just spit an HTML form onto a page.
It Enables Developer Success
When people want to put their logic on the client side, the thought is often this: the server just cannot do that much. Folks think they need tons of interactivity and responsiveness. Perhaps they feel they need the freedom to build their own application framework through JavaScript. You know what? They're right, for the most part. Server-side rendering is constraining. But this is a good thing. If you've ever painted something—or even tried to regularly write blog posts—you've probably felt that paralyzing fear of "What do I make?" When all you have is a blank canvas, knowing what to create can overwhelm you. In contrast, think about paint-by-numbers coloring books. They let you create lovely, if simple, pictures, as long as you match the color to the right number. It's like this with SPAs. They unshackle you from the rules of HTTP and HTML, but at the cost of more easily messing up your design. And as developers. we have very few formalized rules in software development. We need guardrails to help guide us towards healthy decisions. Server-side rendering uses the HTTP protocol, which has a set of constraints and restrictions on how to build software. This includes things like "don't store state on the web server" and "everything needs to be bookmark-able." We'll talk more about some of these healthy constraints in a bit. We can even take advantage of powerful paradigms like REST to do some wonderful things for our customers.
Server-Side Is Not Resource-Constrained
SPAs run mostly on your web or mobile browser. This means their power is limited to the power of your device. Although this power increases every year, it's still much smaller than what a server can provide. Server-side rendering lets you take full advantage of the scaling and computation power needed to get the job done.
The PRG Cycle
One of the beautiful guardrails of server-side rendering is the POST-REDIRECT-GET pattern. This pattern dictates all workflows should follow this flow:
You GET a page, looking at the data to make a decision.
You make your decision, submitting a form and POSTING that form to the server.
The server processes the form and REDIRECTS you to a new GET, starting the process all over. This can include the original GET with input validation errors.
I can't stress enough how wonderful this has been for me to break down complex workflows. All use cases in your system can be deconstructed into a series of "look at something, make a change, look at something else, make a change," etc. Think about a banking website. Let's say you have a use case to make a payment to my mortgage lender. Immediately I start with my GET. What am I looking at to kick off this process? The account summary page seems like a good place. So I add a link to a "make payment" form. I put in the appropriate data to the form, and I can even use JavaScript here—although it's not an SPA! I then add the logic to handle the form POST, processing the payment. Finally, I think about where to REDIRECT the user when I'm done. Should I return them to the account summary page? Perhaps I show them a payment review screen. And then from that screen could be another form to cancel the payment with a form POST. So on it goes.
Separate Commands and Queries
In software, there's a principle called command-query separation. It also has a big sister named command query responsibility segregation at the system level. Server-side rendering encourages us to use the POST-REDIRECT-GET pattern, which naturally follows these principles.
Cross-Cut Where You Can
I love the idea of filtering out all infrastructure concerns until my model is only focused on the business problem at hand. This is powerful and lets us set up conventions to write code for new features incredibly fast. These cross-cutting infrastructure concerns include things like starting and committing database transactions, logging requests, authenticating users, instrumenting metrics, and more. In server-side rendering, there usually two main places you can add these concerns: middleware and the request pipeline.
Middleware
The router usually has a place to put middleware, where you can pass the HTTP request through a pipeline of behaviors, a la the chain of responsibility pattern. Cross-cutting concerns that span across both our controllers and static assets are appropriate to go here. HTTPS redirecting and user authentication are good examples.
Request Pipeline
As I stated earlier, most server-side rendering frameworks support pre- and post-controller action behaviors. This is the perfect place to put cross-cutting concerns that are specific to the nature of your business logic. Database transaction and unit of work management are great here, and so is logging.
It Lives!
Though it's been around for a while and has faded into the backdrop of discussion, server-side rendering isn't dead. It's still very powerful. Single-page applications have a place too, but they don't belong everywhere. If you've never built a server-side rendering application, I encourage you to give it a try. You may be shocked to find how easy it makes your job and how healthy your system looks.