πŸ§‘πŸΎβ€πŸŽ¨ Render Gateway: A Multi-use Render Server

Photo by Nikola Knezevic on Unsplash

This is part 11 of my series on server-side rendering (SSR):

  1. πŸ€·πŸ»β€β™‚οΈ What is server-side rendering (SSR)?
  2. ✨ Creating A React App
  3. 🎨 Architecting a privacy-aware render server
  4. πŸ— Creating An Express Server
  5. πŸ–₯ Our first server-side render
  6. πŸ– Combining React Client and Render Server for SSR
  7. ⚑️ Static Router, Static Assets, Serving A Server-side Rendered Site
  8. πŸ’§ Hydration and Server-side Rendering
  9. 🦟 Debugging and fixing hydration issues
  10. πŸ›‘ React Hydration Error Indicator
  11. [You are here] πŸ§‘πŸΎβ€πŸŽ¨ Render Gateway: A Multi-use Render Server

Way back in January 2020, I started blogging about server-side rendering (SSR) React. I intended it to be a short, four post series that would go into the details of what SSR is, how to do it, and the pitfalls that lie within.

This is post eleven of the four post series, and perhaps the last (at least for now). Over the course of the last ten posts, we have created a simple React app that server-side renders and successfully hydrates. Along the way, we have learned about the complexities of server-side rendering and hydration, and from this we can identify some important lessons.

  1. The initial render of client and server renders must be the same for hydration to be successful
  2. Maintaining a server-side rendering solution can become complex, especially as the app itself becomes more complex (and it doesn't help that React only reports hydration problems in its development build until React 18)
  3. Reasoning about an app to ensure we consider both the server-side and client-side behavior can be a pain

We cannot change the first point. In order for the hydration to be successful, we need our initial render to be the same on both client and server. To achieve this, we need to understand how our code will render in both contexts. However, with some clever components (and hooks, in some cases), we can simplify things to reduce the impact of the other two points. There are frameworks available such as NextJS that provide these features for us. However, I find great value in understanding the complexities of something to grasp exactly what tradeoffs third-party solutions are incurring, and at the time I was working on the SSR solution for Khan Academy, moving to NextJS was far too great a lift. So, in this series we have rolled our own solution.

First, by using components like WithSSRPlaceholder in Wonder Blocks Core, we abstract away the general complexity of understanding the process of server-side rendering to ensure our result hydrates properly.

Second, by testing our code in development in diverse browser environments we can check for things that often cause hydration errors (such as browser feature detection being used to change what gets rendered – remember, the server has no idea what the user has configured in their browser, what their screen size is, etc.).

Finally, by changing the server-side rendering solution from one that knows lots about our frontend code to one that knows as little as possible, we can build a server-side rendering approach that will work without needing to be redeployed every time we change our frontend. And that is where we are heading in this post as we created such a server to perform server-side rendering at Khan Academy.

Goliath and the Render Gateway

For more than two years, the Khan Academy backend that underpins our website and mobile apps has been undergoing a major re-architecture. We named this massive project, Goliath – part pun on the new backend language we had chosen, Go, and part pun on the absolutely colossal amount of work we had ahead of us to get the job done. You can read all about it in these posts on the Khan Academy Engineering blog:

The re-architecture was a big project, made ever more complex by the need to keep the site running smoothly for the millions of folks that rely on us as we transitioned things off the old architecture and on to the new, piece by piece1. As part of this re-architecture, we knew we needed to change the way our website was served and so I, along with the amazing team I work with, were tasked with creating a server that would render our web pages. We made a variety of decisions to simplify the work and to simplify maintenance long term:

  1. We would only support rendering pages that used our latest frontend architecture
    Supporting legacy tech-debt laden code would only perpetuate problems and would most definitely increase the complexity and volume of work to be done. By using our current frontend architecture, all the information about the site, including what page to render for which routes, would be codified within the frontend code.
  2. We would get it working first and get it working fast second
    While we made decisions all the way through to avoid performance issues, we also deliberately avoided making any performance optimizations before we knew what a working solution looked like. And we took measurements – always take measurements before and after when you are making performance improvements.
  3. We would make it generic enough to cope with the multiple changes we make to our frontend each day. We deploy many times in one day to fix bugs and release new features. Our engineers work hard to make these deployments invisible to users and we wanted to implement a solution that would support that effectively.

Our strategy was to get something working, move eligible routes over to that something one by one, and make incremental changes as we went to improve performance and fix bugs.

We knew up front that we would be using an edge cloud platform like Fastly to route the traffic and ultimately provide caching for our SSR'd pages, so we made sure that our design incorporated support for things like the Vary response header to support efficient caching (though we did not use that to begin with, no premature optimization). We went as far as including code in our frontend that could track what request headers were used by a page rendering so that we could build that Vary header with a view to utilizing it once we were at a stage where cache optimization made sense2.

After a little back-and-forth we settled on a name for this new approach to rendering our website; the Render Gateway.

What we did

We spent quite some time building the main Render Gateway code, solving many problems like:

  • How do we know what code to run?
  • How do we build a result that the cloud edge service can understand?
  • What does that result look like?

Many test implementations were stood up as we added more features, including the ability to:

  1. Verify incoming requests so that we can immediately throw away spam
  2. Add different status values and headers to the response to support redirects
  3. Track request header access and add proper support for the Vary header
  4. Log and trace requests in sufficient detail to debug issues in production

By mid-2020 we had a working server and we went live, serving the logged-out homepage and more from this new service. It worked!

It was also slow and had a massive memory leak. 😒

And so began the arduous work of performance testing and memory investigations as we worked to improve things. Our biggest performance wins came from reducing the amount and size of the JavaScript that it takes to render our site (an ongoing and effective focus for site performance in general) and from utilizing the Vary header along with our cloud edge service to reduce the amount of traffic the server needs to handle. For example, we do not gain much value from rendering pages that are for logged-in users so our cloud edge does not ask us to SSR those pages. In addition, better use of the Vary header increases our cache hit rate, leading to more logged-out users benefitting from SSR'd pages.

The Memory Leak

Sadly, the memory leak was a real pain. Every 20 to 40 production requests, an instance would hit a soft or hard memory limit and die. Google App Engine (GAE) works admirably in the face of an unstable app. It will detect the soft or hard memory limit violation, kill the service and spin up new instances as needed, even resubmitting the failed request so that the caller only sees the impact as a slower request rather than a complete failure. This meant that we could keep our leaky implementation serving production users while we investigated the problem, allowing us to continue supporting the Goliath project, albeit with a bit of a limp.

Myself and John Resig spent many hours performing memory investigations, writing multi-process render environments and more in our attempts to both track down and mitigate the memory leak. Just when we thought we had noticed what was holding onto memory, we would realise we were wrong and seek a new path. This was only exacerbated by how hard it was to generate the leak in development, especially since the Chrome dev tools used to investigate memory issues would hold onto references of the code it loaded, and our main usage of memory was that very code that we loaded dynamically. It was weeks of effort until another colleague noted a similar leak in another node service that we had in production. It turned out that the @google-cloud/debug-agent package we were using has a problem and it appears to be down to the very same v8 engine issue we encountered when using Chrome dev tools to investigate the memory issue. Once we removed that dependency, the memory leak went away and instead of crashing every 20-40 requests, each instance of the Render Gateway can handle millions of requests without a care3.

How it works

At its core, the Render Gateway is a generic express server written in JavaScript to operate in Node on Google App Engine. It takes a URL request and renders a result using a configured render environment. Because it uses an API to define that render environment, it is incredibly versatile. There are no rules to what that render environment does other than take in a request and provide a response. Here's an example from the publicly available repository4:

const {runServer} = require("../../src/gateway/index.js");

async function main() {
    const renderEnvironment = {
        render: (
            url /*: string*/,
            renderAPI /*: RenderAPI*/,
        ) /*: Promise<RenderResult>*/ =>
            Promise.resolve({
                body: `You asked us to render ${url}`,
                status: 200,
                headers: {},
            }),
    };

    runServer({
        name: "DEV_LOCAL",
        port: 8080,
        host: "127.0.0.1",

        renderEnvironment,
    });
}

main().catch((err) => {
    console.error(`Error caught from main setup: ${err}`);
});

If you were to run this code with node, you would get a server listening on port 8080 of your local machine with support for the following routes:

  • /_api/ping
    This will return pong, and provides a way to test if the server is responsive.
  • /_api/version
    This will return the value of the GAE_VERSION environment variable, something that Google App Engine sets which you can configure at deployment to specify the version of the server code being run.
  • /_ah/warmup
    Google App Engine supports a warmup handler that it sometimes runs to warm up new instances of an app when scaling. By default, this just returns OK, but the app can be configured to do additional work as needed.
  • /_render
    This performs the actual render. The URL to be rendered is specified using a url query param.

If you invoked http://localhost:8080/_render?url=http://example.com with this server running, it would respond with a 200 status code and the text You asked us to render http://example.com.

The magic is the render environment, which in this case is a very simple object with a single render function:

const renderEnvironment = {
    render: (
        url /*: string*/,
        renderAPI /*: RenderAPI*/,
    ) /*: Promise<RenderResult>*/ =>
        Promise.resolve({
            body: `You asked us to render ${url}`,
            status: 200,
            headers: {},
        }),
};

The Render Gateway source also includes an environment implementation that uses JSDOM, allowing you to construct a more complex environment. However, it does nothing specifically related to React because how your code actually renders server-side is up to you and how you configure it. In fact, because it is built on express, you can plug-and-play the various pieces used to build the main startGateway call to implement your own approach if you so desire, even if you don't want to use Google App Engine.

At Khan Academy, we have a custom render environment that uses some organizational conventions and custom header values populated by our cloud edge service to identify which version of our frontend code is needed. The render environment then downloads (or retrieves from cache) that code and executes it within an isolated node environment to produce the body, status, and response headers (including the aforementioned Vary header) for the result. This is then sent in response to the caller. All the code executed to actually produce a result is from the downloaded code at the time of the request. To support this, we have some conventions, components, and frameworks that allow developers to access request header values, set response header values, and update the response status code from within our frontend code in a manner that feels natural (for example, a <Redirect/> component abstracts away the work of setting the status code and the Location header as needed). This means that our engineers, when working on our frontend code, do not need to context switch between thinking about client-side rendering and server-side rendering; instead, they have idioms to hand that enable them to build frontend user experiences that just work.

Our simple app revisited

Now to come full circle, we can envisage what our server-side rendering solution might look like using the Render Gateway. Instead of importing the client-side code at build time, we could leverage a render environment using JSDOM to dynamically load the code when a request is made, decoupling our server from our client.

I have made some changes to demonstrate this concept of using a manifest. However, this change still assumes a client build local to the server. If we wanted to make this entirely client-build agnostic, we would change our render environment to download the files (including the manifest) from a CDN instead. The GAE_VERSION environment value, or some header we receive could indicate the version of our frontend we need. We can then look up a manifest in our CDN to tell us the URLs of files we need, download them, execute them, and invoke the rendering process to get a result.

For now, if we are in production, we look for ../client/build/ folder to load the manifest and then load the files from that same folder; in development, we defer to the client webpack server. So, in a way, the development setup is closer to our envisaged CDN-based setup, with webpack acting as that third-party file host.

Take a look at the branch and think about how you might modify things to use a CDN for production. Note that the render-gateway code is currently specific to Google App Engine.

Some final SSR thoughts

Server-side rendering is great for providing search engines with a more complete version of your page when they come crawling your site. It is also great at showing more of your page to your users on first display. And if used unnecessarily, it is a great way to sloooooooow the delivery of your site 😱.

If you always SSR a page before serving it to users, you could wait quite a while for that page to finally land in front of the user. The real value of SSR is only realised when it is coupled with caching so that an SSR result can be re-used for multiple requests. This can be easy to setup with a service like CloudFlare or Fastly, but to do it right and get the best cache hits without compromising your users data or the utility of your site can take a little more work. You will want to familiarise yourself with things like the Vary response header, edge-side includes, and other useful concepts. Not to mention performance and other site metrics so that you can measure the impact of your SSR strategy and make sure it is serving its purpose without hindering your site or your users.

Whatever you choose to do next, I hope this series on server-side rendering with React has demystified the topic and provided you with some helpful resources as you consider SSR and what it may mean to your current or next project – please stop by and let me know about your SSR adventures in the comments. In the meantime, as the React team works more on React and features like Suspense, the server-side rendering story, like so many software developments stories, is going to change.

For now, thank you for joining me on this SSR journey. When I started, I thought I knew everything I needed to know about SSR in order to tell you everything you needed to know about it. It should come as no surprise to any of us that I still have things to learn.

  1. The pandemic that showed up right after we started also contributed to the complexity of the project as more and more folks around the world turned to us to support their education []
  2. The Vary response header allows a server to tell a cache like the one Fastly provides with headers in the request were used to generate that response. Along with the URL and other considerations, this tells the cache what header values need to match for a cached page to be used versus requesting a new response from our server []
  3. At the time of writing, that issue is still open although there is ongoing movement to suggest it may soon be resolved, or made redundant with the removal of that feature from Google's offering []
  4. There are currently no NPM packages to install for this, though I hope to change that – instead, the dist is included in the repo and we install via commit SHA []

πŸ–₯ Our first server-side render

Photo by Markus Spiske on Unsplash

This is part 5 of my series on server-side rendering (SSR):

  1. πŸ€·πŸ»β€β™‚οΈ What is server-side rendering (SSR)?
  2. ✨ Creating A React App
  3. 🎨 Architecting a privacy-aware render server
  4. πŸ— Creating An Express Server
  5. [You are here] πŸ–₯ Our first server-side render
  6. πŸ– Combining React Client and Render Server for SSR
  7. ⚑️ Static Router, Static Assets, Serving A Server-side Rendered Site
  8. πŸ’§ Hydration and Server-side Rendering
  9. 🦟 Debugging and fixing hydration issues
  10. πŸ›‘ React Hydration Error Indicator
  11. πŸ§‘πŸΎβ€πŸŽ¨ Render Gateway: A Multi-use Render Server

Over the last few weeks, we have been chipping away at server-side rendering and how to implement it. In the last post, we created a server; in this one, we will see if we can make that server render a page containing some server-side rendered React. If you recall from last time, there are two ways we can approach implementing our render server:

  1. Standalone with a way to pass our React app to it
  2. Integrated so that it knows all about our React app

Both approaches start from common origins; they both need a server that can render a React component inside a page. By the end of this post, we should be able to request a URL from our server and receive a rendered HTML page with some rendered React embedded inside of it. This will give us some fundamentals that we can then use next time to finally render our client-side application.


πŸ–Œ Rendering React on the server

app.get("/*", (req, res) => res.send("Hello World!"));

The server that we made last time will be the basis for our solution. Above is our current route handler for the server. Regardless of the get request, the server responds with Hello World!. What we want to do is to replace Hello World! with the rendered React component tree embedded within an HTML page.

When rendering in a browser, our React application is mounted so that it will dynamically update based on events like mouse movements, network requests, etc. When we are rendering on the server, we do not want all that. In fact, we do not even have a DOM like the browser does in which to create elements and event handlers and the like1. Instead of mounting the React application, we want to capture the very first render of the application and stop. React provides a methods for doing things like this in the React DOM package. We are going to use its renderToString method2.

The renderToString method takes a React component and gives us back a string of the markup that is initially rendered by that component. Before we can try it, we need to add the appropriate packages to our server: react and react-dom.

yarn add react react-dom

Now, in theory, we can render some React. Sadly, just updating our route handler with a component as shown below will not work.

app.get("/*", (req, res) => res.send(
    renderToString(<div>Hello World!</div>)
));

If we add this and then run our server with yarn start, we get a rather cryptic error output.

app.get("/*", (req, res) => res.send(renderToString(<div>Hello World!</div>)));
                                                    ^

SyntaxError: Unexpected token <
    at Module._compile (internal/modules/cjs/loader.js:723:23)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)
    at Module.load (internal/modules/cjs/loader.js:653:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
    at Function.Module._load (internal/modules/cjs/loader.js:585:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:831:12)
    at startup (internal/bootstrap/node.js:283:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:623:3)

The issue is, our server has no idea how to handle JSX syntax (the embedded HTML-like description of our React component; <div>Hello World!</div>). Our client-side application works because the create-react-app package sets up some tools to process JSX files and turn them into valid JavaScript that can be understood by a modern browser. Our server-side application does not have any of that magic and so it does not work.

Rather than spending time to add that magic into our server, it feels more appropriate to assume our server is just another browser and that our client-side code will already be transformed into JavaScript by the time we see it3. Instead, just to test our React rendering, we can replace the JSX with its transpiled counterpart, which is a call to React.createElement. React.createElement takes the component being rendered and its props. In our case, we are rendering an HTML div element. These are special cases where a string is used to represent them, rather than a real React component type. Therefore, our simple JSX example becomes; note how the text is passed as the children of the component.

app.get("/*", (req, res) => res.send(
    renderToString(React.createElement("div", { children: "Hello World!" }))),
);

If we now yarn start our server application, it runs and when we visit http://localhost:3000, we see our Hello World! text. This is great. It means that given a suitably transpiled React component, we can server-side render it. Now that we have the ability to render a component, we need to embed that rendered component inside a full HTML page.

πŸ“„ The Page Template

OK, so we have some HTML that represents our rendered component. Now we need to put that into an HTML page, we need to think about what that page looks like. What does the page include? We can revisit the React app we made and see for ourselves.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="/logo192.png" />
    <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
    <link rel="manifest" href="/manifest.json" />
    <!--
      Notice the use of  in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  <script src="/static/js/bundle.js"></script><script src="/static/js/0.chunk.js"></script><script src="/static/js/main.chunk.js"></script></body>
</html>

Above is the development-time HTML template that is used with our simple React app. I have highlighted some important sections.

  1. The head containing page metadata, including title, description, favicon, etc.
  2. Scaffold body to provide a mounting point for our React component
  3. Scripts

The head content is static4 and the scripts are inserted by the build operation of our client app. The bit that matters to us is the mounting point for our React app, <div id="root"></div>, as this is where anything we render will need to be inserted by our server-side rendering operation.

Of course, we want to do all this with production code. To see how that affects things, we can run yarn build in our React app. Running this creates a build folder with all sorts of things in it, including a slightly different version of our HTML template (we will perhaps consider the other files another time).

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="/logo192.png" />
    <link rel="manifest" href="/manifest.json" />
    <title>React App</title>
    <link href="/static/css/main.b0083702.chunk.css" rel="stylesheet" />
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <script>
      !(function(f) {
           // SNIPPED FOR CLARITY
      })([]);
    </script>
    <script src="/static/js/2.78e6b881.chunk.js"></script>
    <script src="/static/js/main.dcbf6a7c.chunk.js"></script>
  </body>
</html>

This looks a little different than what we had before, but it is not as different as you may think. We still have the head element metadata (though this time it includes a CSS file, which was not there in the development version), we still have the scripts (though there is now some inlined scripting that wasn't there before, which I have snipped out just to make things a little more readable), and most importantly, we still have our mounting point, <div id="root"></div>.

Given this information, we can update our server application to return a full page containing our rendered component. For our purposes here, we will hard code a simple page template. Eventually, we can replace this simple template and the React component being rendered with the production output of our client application.

πŸ–Ό Rendering the page and the component together

Using what we have learned here, I have modified the server as follows.

const express = require("express");
const React = require("react");
const {renderToString} = require("react-dom/server");

const port = 3000;
const app = express();

const pageTemplate = `<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta
      name="description"
      content="SSR result"
    />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>
`;

const renderPage = (reactComponent) => {
    const renderedComponent = renderToString(reactComponent);
    return pageTemplate.replace('<div id="root"></div>', `<div id="root">${renderedComponent}</div>`);
};

app.get("/*", (req, res) => res.send(
    renderPage(React.createElement("div", {children: "Hello World!"})),
));

app.listen(port, () => console.log(`Example app listening on port ${port}!`));

We have a page template string called pageTemplate. Then we have a renderPage method that does a simple replace operation to replace <div id="root"></div> in our template with the same div containing our rendered React component. Finally, in the get handler, the renderPage method is invoked with our React component.

If we yarn start this version of the server and visit http://localhost:3000, viewing the resultant page source gives us the following HTML.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta
      name="description"
      content="SSR result"
    />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"><div data-reactroot="">Hello World!</div></div>
  </body>
</html>

On the highlighted line, you can see the inserted server-side rendered React code. Success! We haven't even loaded any scripts client-side to see this result. Of course, if we want an app that a user can interact with, we are going to need to change that. Join me next time when we work out how to integrate our server with our client-side application in order to get our very first server-side rendered app. Of course, that does not mean we will have reached our destination on this server-side rendering adventure; on the contrary, it feels like we have barely begun.

Thanks again for reading. I hope that something you find here is useful. Please comment as you see fit. πŸ’

  1. We could introduce a DOM using a library like JSDOM. However, that is an extra step that we would like to avoid as it increases the latency of our server-side rendering process. Instead, we should aim to make sure our React app does not rely on there being a DOM present at all. []
  2. It is worth looking at the other options, such as renderToNodeStream, to see what they can offer you and your specific SSR challenges []
  3. You may notice that this starts to lead us down one of our two paths; does the server know all about the client code, or does it get blindly provided somehow? []
  4. For now, let's assume that the page metadata (item 1, above) remains static; while we can certainly build in a mechanisms to make this dynamic, such as changing page title when the selected route is different, that will over-complicate things at this stage. []

πŸ€·πŸ»β€β™‚οΈ What is server-side rendering (SSR)?

Featured image modified from photo by Andre Mouton on Unsplash

This is part 1 of my series on server-side rendering (SSR):

  1. [You are here] πŸ€·πŸ»β€β™‚οΈ What is server-side rendering (SSR)?
  2. ✨ Creating A React App
  3. 🎨 Architecting a privacy-aware render server
  4. πŸ— Creating An Express Server
  5. πŸ–₯ Our first server-side render
  6. πŸ– Combining React Client and Render Server for SSR
  7. ⚑️ Static Router, Static Assets, Serving A Server-side Rendered Site
  8. πŸ’§ Hydration and Server-side Rendering
  9. 🦟 Debugging and fixing hydration issues
  10. πŸ›‘ React Hydration Error Indicator
  11. πŸ§‘πŸΎβ€πŸŽ¨ Render Gateway: A Multi-use Render Server

One of my main responsibilities at work involves server-side rendering (SSR). From managing the services that perform SSR to the client components that developers use to build SSR-able frontends, I have my focus on many pieces of our frontend infrastructure. In this series of posts, I want to share some of the things I have learned and perhaps demystify this mostly fantastic approach to creating performant, stable, web experiences.

When the Internet started coming alive the first time, a lot of the magic was implemented on servers (aka server-side or "on the backend") that built HTML pages to deliver to web browsers. During this period, impressive collections of user interface components were created to make developing these server-based web apps easier and more reliable. Sadly, sites often felt a clunky and slow because the browser just was not equipped to do much beyond rendering the HTML it was given; JavaScript execution was too slow for anything very meaningful. Even button clicks in the browser would cause a new request to the server that would then generate a whole new page for the browser.

Then Google Chrome and the v8 JavaScript engine came along and changed everything. Browsers became blessed with speedy JavaScript engines. That meant we could do a lot of this work in the browser (aka client-side or "on the frontend") and develop applications that could properly divide presentation (the application running in the browser) from data (the database and CRUD1 operations running in the backend). From this new power came the concept of the single page app (SPA), where one page comes from the server and then does most of its work client-side, deferring to the backend only when data is read or written. Often, the page is received from the server in an initial state and then subsequent data requests may populate that page (imagine your Facebook feed loading) to get it ready for you to use. However, this can mean that the time to interactive – the length of time before a user can actually use the page – is long. This affects all sorts of things, but particularly user retention. Folks don't like waiting and if they wait too long, they become frustrated and eventually bounce2.

Much like in the backend era prior to faster frontend JavaScript execution, new frameworks and user interface components have appeared that help to create powerful web apps using browser-based JavaScript. Things like Angular, Ember, and React (there are more – there are always more3). However, there can still be a mismatch between backend and frontend. To get a nice experience for our users, code runs on the backend to build an initial page and then that is handed to the frontend, which promptly takes over. Sometimes, this transition is nice and smooth, but other times it is not. More importantly, there are at least two different code paths for generating the page; at least one backend one and at least one frontend one4.

Having more than one code path trying to do equivalent work is hard to maintain. A change in one place may or may not need a change in the other, and either way, careful quality engineering is needed to make sure bugs are not introduced. The shift away from web apps executing entirely on the backend but rendered on the frontend to being executed mostly on the frontend with a bit of backend increased the complexity of the code for anyone that wanted a performant, engaging web site. You had a choice; either keep the separation of frontend for presentation and backend for data, and have a slower initial website experience, or blur the line and have more complex code, but a nicer user experience. Thankfully, folks thought about this and like those responsible for React, came up with a solution – server-side rendering (SSR).

Thanks to the JavaScript Revolution that started with v8, we now are able to run JavaScript outside of our web browsers (using NodeJS, for example). This creates some interesting opportunities for running the same code in both the frontend and backend. This does not mean that all the code would run in two places – we may want to keep the CRUD operations as a backend thing; however, being able to run our presentation code in both places means we can overcome some of that delay when a user first visits a page of our site. We can use the same JavaScript that would render our page in the web browser to render a version of our page on the server and then let the browser take over, all with a single codebase5.

Server-side rendering (SSR) – The rendering of a web page on a server rather than in a browser

And in the context of what I want to write about, that is server-side rendering (more commonly referred to as SSR, at least by me, anyway) – the rendering of a web page on a server rather than in a browser. In fact, it's so similar to rendering in a web browser, I have started to refer to the server responsible for SSR as a server-side browser. This tends to reframe how folks think of problems they face and how to start thinking about frontend code not as "does this run in the server or the client?" but "what browsers does this have to support?". It turns out that second question is much more familiar to most frontend developers than the first.

For now, I will leave things there. I think this post is quite long enough. Thank you for reading. Over the next few posts, we will look at creating an app using React that supports SSR, as well as a backend browser to perform that SSR, and the implications that SSR has when it comes to writing frontend code.

  1. Create Read Update Delete []
  2. there's a reason it's called "bounce rate" []
  3. While I was writing this, I expect three more frontend frameworks came into being and at least one died []
  4. There are cases where different parts of the same page are rendered by different services; front or backend – talk about complicated []
  5. Not only that, but the server response could be cached with a CDN (content delivery network) to make our sites even faster! []

The Need For Speed

Hopefully, those who are regular visitors to this blog1 have noticed a little speed boost of late. That is because I recently spent several days overhauling the appearance and performance with the intent of making the blog less frustrating and a little more professional. However, the outcome of my effort turned out to have other pleasant side effects.

I approached the performance issues as I would when developing software; I used data. In fact, it was data that drove me to look at it in the firstΒ place. Like many websites, this siteΒ uses Google Analytics, which allows me to poke around the usage of my site,Β see which of the many topics I have covered are ofΒ interest to people, what search terms bring people here (assuming people allow their search terms to be shared), and how the site is performing on various platforms and browsers. One day I happened to notice that my page load speeds, especially on mobile platforms, were pretty bad and that there appeared to be a direct correlation between the speed of pages loading and the likelihood that a visitor to the site would view more than one page before leaving2 . Thankfully, Google provides via their free PageSpeed InsightsΒ product, tips on how to improve the site. Armed with these tips, I set out to improve things.

Google PageSpeed Insights
Google PageSpeed Insights

Now, in hindsight, I wish I had been far more methodical and documented every stepβ€” it would have made for a great little series of blog entries or at least improved this oneΒ β€”but I did not, so instead, I want to summarise some of the tasks I undertook. Hopefully, this will be a useful overview forΒ others who want to tackle performance on their own sites. The main changes I madeΒ can be organized into server configuration, site configuration, and content.

The simplest to resolve from a technical perspective was content, although it remains the last one to be completed mainly due to the time involved. It turns out that I got a little lazy when writing some of my original posts and did not compress images as much as I probably should have. The larger an image file is, the longer it takes to download, and this is only amplified by less powerful mobile devices. For new posts, I have been resolving this as I go by using a tool called PNGGauntlet to compress my images as either JPEG or PNG before uploading them to the site. Sadly, for images already uploaded to the site, I could only find plugins that ran on Apache (my installation of WordPress is on IIS for reasons that I might go into another time), would cost a small fortune to process all the images, or had reviews that implied the plugin might work great or might just corrupt my entire blog. I decided that for now, to leave things as they are and update images manually when I get the opportunity. This means, unfortunately, it will take a while. Thankfully, the server configuration options helped me out a little.

On the server side, there were two things that helped. The first, to ensure that the server compressed content before sending it to the web browser, did not help with the images, but it did greatly reduce the size of the various text files (HTML, CSS, and JavaScript) that get downloaded to render the site. However, the second change made a huge difference for repeat visitors. This was to make sure that the server told the browser how long it could cache content for before it needed to be downloaded again. Doing this ensured that repeat visitors to the site would not needΒ to download all the CSS, JS, images, and other assets on every visit.

With the content and the server configuration modified to improve performance, the next and most important focus was the WordPress site itself.Β The biggest change was to introduce caching. WordPress generates HTML from PHP code. This takes time, so by caching the HTML it produces, the speed at which pages are available for visitors is greatly increased. A lot of caching solutions for WordPress are developed with Apache deployments in mind. Thankfully,Β I found that with some special IIS-specific tweaking, WP Super Cache works great3 .

At this point, the site was noticeably quicker and almost all the PageSpeed issues were eliminated. To finish off the rest,Β I added a few plugins and got rid of oneΒ as well. I used the Autoptimize plugin to concatenate, minify, compress, and perform other magic on the HTML, CSS, and JS files (this improved download times just a touch more by reducing the number of files the browser mustΒ request, and reducing the size of those files), I added JavaScript to Footer, a plugin that moves JavaScript to after the fold so that the content appears before the JavaScript is loaded, I updated the ad code (from Google) to use their latest asynchronous version, and I removed the social media plugin I was using, which was not only causing poor performance but was also doing some nasty things with cookies.

Along this journey of optimizing my site, I also took the opportunity to tidy up the layout, audit the cookies that are used, improve the way advertisers can target my ads, and add a sitemap generatorΒ to improve some of the ways Google (and other search engines) can crawl the site4. In all, it took about fiveΒ days to get everything up and running in my spare time.

So, was it worth it?

Before and after
Before and after

From my perspective, it was definitely worth it (please let me know your perspective in the comments). The image above shows the average page load, server response, and page download times before the changes (from January through April – top row) and after the changes (June – bottom row). While the page download time has only decreased slightly, the other changes show a large improvement. Though I cannot tell for certain what changes were specifically responsible (nor what role, if any,Β the posts I have been writing have played5 ), I have not only seen the speed improve, but I have also seen roughly a 50-70% increase in visitors (especially from Russia, for some reason), a three-fold increase in ad revenue6, and a small decrease in Bounce Rate, among other changes.

I highly recommend taking the time to look at performance for your own blog. While there are still things that, if addressed, could improve mine (such as hosting on a dedicated server), and there are some things PageSpeed suggested to fix that are outside of my control, I am very pleased with where I am right now. As so many times in my life before, this has led me to the inevitableΒ thought, "what if I had done this sooner?"

  1. hopefully, there are regular visitors []
  2. The percentage of visitors thatΒ leave after viewing only one page is known as the Bounce Rate []
  3. Provided you don't do things like enable compressing in WP Super Cache and IIS at the same time, for example. This took me a while to understand but the browser is only going to strip away one layer of that compression, so all it sees is garbled nonsense. []
  4. Some of these things I might blog about another time if there is interest (the cookie audit was an interesting journey of its own). []
  5. though I possibly could with some deeper use of Google Analytics []
  6. If that is sustained, I will be able to pay for the hosting of my blog from ad revenue for the first time []

jQuery validation of select elements where multiple="multiple"

I was recently working on a simple feature in our web client at work. Part of the feature required that the user be able to select one to many items from a multiple select list. My first stab at this worked great; I added the required class to the select element and voila, the user was limited to at least one item. However, I thought it would be nice to give a custom error message with a little more context than just "this field is required" and perhaps provide the ability to limit the selection to a variable minimum and maximum length.

I quickly discovered that I don't understand the jQuery Validation plug-in and the documentation seems to be written by someone who thinks I might know way more than I actually do1. My next step was to find examples but they were all related to comparing element values on more standard input elements, which just served to confuse me further, so I embarked on a quest to find out how to solve my problem and write a blog about it.

Now, I'm sure that what I am about to document will seem like child's play to the jQuery ninjas out there, but for every ninja equipped with the skills to silently dispatch every henchman in the room, there's someone like me who just wants to get out of the room alive. So, please bear that in mind as you read on and whether a ninja or just a survivor, please comment.

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>My jQuery real good fun time yeah page</title>
    <script src="http://code.jquery.com/jquery-1.9.1.js" type="text/javascript"></script>
    <script src="http://ajax.aspnetcdn.com/ajax/jquery.validate/1.11.1/jquery.validate.js" type="text/javascript"></script>
    <script type="text/javascript">
        /* Placeholder for my amazing coding skills. */
    </script>
</head>
<body>
    <form id="mySuperCoolForm">
        <fieldset>
            <legend>Select some stuff</legend>
            <select name="things" multiple="multiple">
                <option>thing 1</option>
                <option>thing 2</option>
                <option>thing 3</option>
                <option>thing 4</option>
                <option>thing 5</option>
            </select>
            <input type="submit" />
        </fieldset>
    </form>
</body>
</html>

This is the simple HTML page that I'm going to work with. There's nothing complex here, just a form with a select and a submit input. I have also added some script imports for jQuery and the jQuery validate plug-in and an empty script element for our validation definitions to live.

To this, we want to add some validation to make sure that at least one element in the list is selected when the form is submitted. First, just to check things are wired up correctly, we will just specify the select named "things" as being required. Rather than do this by just adding the required class, let's use some scripting, that way we can manipulate the script as we go.

$(function () {
    $('#mySuperCoolForm').validate({
        rules:{
            things:{
                required:true
            }
        }
    });
});

This code is not doing much. It is telling the jQuery validate plug-in to attach its validation to our form, mySuperCoolForm with the rule that our field, things is required. If you try this out and click the submit button without selecting anything, you'll get a message stating, "This field is required." Not a very descriptive message, so let's fix that by adding a message for our "things are required" rule.

$(function () {
    $('#mySuperCoolForm').validate({
        rules: {
            things: {
                required: true
            }
        },
        messages: {
            things: {
                required: 'Please select at least one thing.'
            }
        }
    });
});

Again, not a very complex change. I have added a messages field to the JSON object being passed to the validate method. This field provides messages that should be displayed when validation rules fail. In this case, we are stating we want at least one thing to be selected whenever our required rule fails. You should note the correlation here between the name of our field, things and the rules and messages that are attached to it. This is important to understanding how to manipulate the jQuery validation. When a rule fails to validate for a named field, the message under the same named field with the matching name to that failed rule will be displayed.

That works nicely so it's job done, right? Not quite. You see, while this works for the cases where one to many items need selecting, it feels a little hacky. I'd rather say explicitly "I want x items selected" rather than rely on the required rule working the way it does for multiple select scenarios. I like being explicit. So, let's get explicit2.

Validating with minlength

It just so happens that the jQuery validation plug-in has another rule, minlength that appears to be exactly what we want. Thinking this, I naively wrote some code to require at least two items. I also included some string formatting for the error message so that the number of things in the message matched the number required by the rule.

$(function () {
    $('#mySuperCoolForm').validate({
        rules: {
            things: {
                minlength: 2
            }
        },
        messages: {
            things: {
                minlength: $.format('Please select at least {0} things.')
            }
        }
    });
});

Now, if we select two things, we can submit just fine, but if we select just one thing, we get told we need at least two. So far so good. However, if we select nothing, the form passes validation and submits fine! What? We stated a minimum of two and yet zero passes. That makes no sense. It made even less sense when I set my minlength to 1 and it didn't appear to do any validation at all. Everything was feeling so intuitive, what happened? By looking at the code, it becomes clearer.

The minlength rule looks like this:

minlength: function( value, element, param ) {
			var length = $.isArray( value ) ? value.length : this.getLength($.trim(value), element);
			return this.optional(element) || length >= param;
		}

If you put a breakpoint here, you'll discover that when nothing is selected, value is null, but that's okay because length becomes `0`Β in that circumstance anyway. The problem is on the second line of the function, which I have highlighted. It checks to see if the element is optional or not and if it is optional, the element passes validation regardless. A quick look at the optional function shows that it calls the required rule and returns the inverse of its result. When we have no items selected, required returns false which optional turns to true which incorrectly says our data is valid. However, when only one item is selected, required returns true which means the `length >= param` checkΒ occurs and our validation fails as we would like. While this behaviour is probably intuitive for say, string input fields where no value and values of a certain minimum length make sense, this is confusing when using minlength with select fields.

To get our validation working as we would like, we have to apply both the required and minlength rules as follows:

$(function () {
    $('#mySuperCoolForm').validate({
        rules: {
            things: {
                required: true,
                minlength: 2
            }
        },
        messages: {
            things: {
                required: 'Please select at least 2 things.'
                minlength: $.format('Please select at least {0} things.')
            }
        }
    });
});

Unfortunately, now we've lost the nice dynamic nature of our error messages. If we change the minlength rule to 3, we have to remember to edit the message for required. It seems we have gone from "feels hacky" to "feels hackier".

What I really want is for required to behave differently when I have the minlength rule applied to a select element. When using minlength on a select element, I want required to default to true but my error message to come from the minlength message format. This would feel far more intuitive for this use case than the current behaviour does. So, given that the plug-in does not do this, what can we do to fix it?

Implementing an intuitive minlength

The jQuery Validation plug-in provides the ability to add custom validation methods. Not only does this allow the addition of new methods, but it also allows for the replacement of existing methods. This gives us two options:

  1. Create a new minlengthofselection or similar that follows the rules we want.
  2. Override the existing minlength that does things the way we want.

While the first option is probably safest for those already familiar with the behaviour as it stands, option two is more fun. Guess which one I did3.

$(function () {
    $.validator.addMethod('minlength', function (value, element, param) {
        var length = $.isArray(value) ? value.length : this.getLength($.trim(value), element);

        if (element.nodeName.toLowerCase() === 'select' && this.settings.rules[$(element).attr('name')].required !== false) {
            // could be an array for select-multiple or a string, both are fine this way
            return length >= param;
        }

        return this.optional(element) || length >= param;
    }, $.format('Please select at least {0} things.'));

    $('#mySuperCoolForm').validate({
        rules: {
            things: {
                minlength: 2
            }
        },
        messages: {
            things: {
                minlength: $.format('Please select at least {0} things.')
            }
        }
    });
});

In this final update to the code, I provided a new implementation to the validator for minlength. This new version is almost identical to the default except that when checking a select element, it looks to see if the field is explicitly not required, thereby assuming that we want something to be selected. Finally, we have a minlength validation check that works intuitively for our scenario as well as the more common scenarios surround string lengths and we don't have to hard code numbers into the error message.

Conclusion

In conclusion, I ran into what is probably a corner case when it comes to using jQuery Validation where things were not as intuitive as I had hoped. In doing so, I was able to learn more about the plug-in and eventually, resolve my specific issue.

  • Have you had a similar problem?
  • Do you have improvements to suggest?
  • Should I submit this minor change for inclusion in the jQuery Validation plug-in?

Please, comment and let me know.

Acknowledgements

I would like to thank, alen, the author of this tutorial which gave me a foundation to work from when investigating and resolving this issue.

  1. Turns out the real reason was my expectations of what should work and the reality of what did work did not match, making the documentation hard to interpret. []
  2. Not in that way! []
  3. Hint: It's not the first one. []