JavaScript: client-side vs server-side, SSG vs SSR, SPA vs MPA
JavaScript started out in the browser as a way to add a bit of interactivity to pages. But nowadays, the browser isnât the only place anymore where you can run JavaScript. You can also use it on the server to generate HTML. Related to that, there are three distinct concepts that people often muddle together:
- Client-side vs server-side JavaScript
- SSG (static site generation) vs SSR (server-side rendering)
- SPA (Single-Page App) vs MPA (Multi-Page App)
Weâll look at each of those concepts in turn. The last two are orthogonal, and the following 2x2 table provides a summary over the different combinations:
| SSG (static site generation) | SSR (server-side rendering) | |
|---|---|---|
MPA |
HTML for all pages is pre-generated at build time (e.g. 11ty, Jekyll, Hugo). |
HTML is generated on the server on-demand, each time a request for a page comes in (e.g. PHP, Rails). |
| SPA | For the initial page load, HTML is generated on the client in plain SPAs (e.g. Create React App), and pre-generated when using a static site generator (e.g. Next.js SSG). For later pages, HTML is always generated on the client. | For the initial page load, HTML is generated on the server. For later pages, HTML is generated on the client. (e.g. Next.js SSR) |
Client-side vs server-side JavaScript
Since a browser is also known as a client, JavaScript that runs in the userâs browser is known as client-side JavaScript.
The basic functionality of a page should be usable even without JavaScript. Because during every page load, there is a time where JavaScript is still loading, or perhaps it fails to execute at all â maybe because the user just entered a tunnel with their mobile phone, or are using an older device, or perhaps because the developer made a small programming error causing the JavaScript to crash, or some other reason. This concept of making a website first work under suboptimal conditions, and rely on more advanced tech like client-side JavaScript only for enhancements, is known as progressive enhancement.
Thatâs why client-side JavaScript is best used sparingly. Although unfortunately, some websites download millions of lines of JavaScript every time you open them. Client-side JavaScript frameworks like React run the program to generate the HTML right in the browser â every time you open the page!
This may make sense for an app like Google Docs, Figma, or Visual Studio Code. Or perhaps even for an interactive dashboard, used only by a small number of internal people that you know will be on fast machines on a fast network, or that will load the page once and then keep it open for hours. But for most websites, it just makes everything slow for little reason â especially on mobile phones, with their limited computing power and slow networks.
However, JavaScript is a general-purpose programming language like any other. Nowadays, using e.g. Node.js or Deno, you can run JavaScript code also on your server or laptop, generate the HTML there, and only send that to your userâs browser â just like people have done for ages with other general-purpose programming languages like PHP, Ruby or Python.
This is generally known as server-side JavaScript â regardless of whether you do static site generation or run a server. The important part is that unlike client-side JavaScript, it doesnât run in the userâs browser.
In recent years, so-called meta-frameworks have sprung up for various client-side frameworks: for example Next.js or Remix for React, Nuxt for Vue.js, or SvelteKit for Svelte. Those do âserver-side renderingâ (SSR), meaning they run the HTML-generation first on the server, and then send the HTML along for the first page load. But after that, the page is still âhydratedâ on the client (and even âReact Server Componentsâ are reconciled on the client). Running all that JavaScript on the browserâs main thread causes the page to stay unresponsive for longer, as seen in metrics like Total Blocking Time. Finally, making sure all your code can be executed on the server, as well as on the client, adds a lot of complexity.
Test and improve your websiteâs performance
- WebPageTest
- PageSpeed Insights and the âLighthouseâ tab in Chromeâs dev tools
- SpeedCurveâs Web Performance Guide
Static site generation vs running a server
Mastro can be used as a static site generator â i.e. running the code to pregenerate all pages of your website ahead of time. (This is what weâll do in the first half of this guide). A static website is very fast, and doesnât require you to run a server. So no need to harden your server against load spikes or attacks. Unless you have specific requirements, this is the best way to run a modern website.
In later chapters of this guide, weâll use Mastro as a web server, which runs the code to generate a page each time a user visits the page. While more complex to operate, running a server gives you the ability to return a potentially different page each time it is visited. Weâll again look at the various ways to run Mastro later.
SPA vs MPA
As mentioned above, instead of doing all the work on the server, client-side JavaScript frameworks do it in the userâs browser. A lot of them even take that approach so far, that when a user clicks a link, the website takes control of loading the next page away from the browser, and reimplements the functionality in JavaScript. (In that case you wonât see a request of type document in your browserâs network dev tools.) This approach is called a Single-Page App (SPA), and just recently peaked in popularity.
Their theory is that the client-side JavaScript can do a better job at page navigation than the browser. While this may still be the right approach for highly interactive apps like Figma, for almost all websites, loading and executing that additional JavaScript just makes things slower and more complex â especially if navigating to a new page needs to load data from the server anyway.
One of the last remaining reasons to choose an SPA is if you need to keep cursor state in text inputs across page navigations (or positions of scrollable elements on the page). Or if you need to keep audio or video playing across page navigations â but even that can be done with an MPA. To keep things simple, Mastro only supports MPAs out of the box.
Buttery smooth navigation in MPAs
Browsers have really stepped up their game regarding page navigation.
- Using cross-document view transitions, you can get page transitions, as smooth as in any SPA, by adding two lines of CSS to your MPA. They are implemented in Chrome and Safari (and Firefox has signalled intent to ship).
- Back/forward cache is implemented in all browsers. This means for example that unless you break the bfcache, an infinite-loading list added with client-side JavaScript will still be there on browser back navigation in your MPA.
- Speculation Rules API allows you to instruct the browser to preload entire pages a user might navigate to.