SSR with Create React App v2

If you haven’t seen, I wrote one of these a month or two ago, link: https://medium.com/@benlu/server-side-rendering-with-create-react-app-1faf5a9d1eff

I’d rushed the article so wasn’t particularly well written, code was kinda scrappy (though still not too bad), and didn’t really touch on many of the core concepts. Also, it was written for React Router v3 so when v4 dropped, all my code got thrown out the window.

I’ve touched it up, it’s a bit nicer now and code is at: https://github.com/ayroblu/ssr-create-react-app-v2

I’ve written a new article, and my old demo doesn’t quite work :/ Checkout the new article at: https://medium.com/@benlu/ssr-with-create-react-app-v2-1-ee83fb767327


Core concepts

When you’re adding Server Side Rendering (SSR) to your app, there’s a few things you can do easily and a couple that you can’t. This is my opinionated view on them, so take it how you will, but for smaller/simpler projects, these are the things I’m looking for.

Browser v Server

The browser has nice things like window which the server does not. If you want to use browser APIs, make sure they’re not in your critical path. JavaScript is a wonderful scripting language. So if you don’t have something, but it’s in an if statement or similarly sectioned part of your code, it won’t be checked. Some people don’t like this, but you don’t have much of a choice in the matter.

Also, you might want to import css, however, that’s not a thing on the server, so you’ll need to run a plugin to stop css and whatever other weird things you’re putting in to your import statements.

Node

You pretty much have to use Node as your web server. That’s the whole point of this article, simplicity, React and code sharing. Not a fan? Check out the next point.

Pre-render

This is pretty much the point of SSR, to go from old server based web apps to newer SPAs back to server rendering. In your create-react-app html, you’ll find a piece of code that looks like: <div id="root"></div>. Put whatever you want in here, that’s your frontend until your JavaScript bundle has loaded. Once React’s loaded, it will diff this (so some cost) and if it’s different, it just throws it out. This means you can serve all your endpoints statically, or with a rendered template that gets rendered to html. So for those who want to use Django or ASP.NET Razor or something else for their backend, this is your possible solution. Sadly this isn’t what we’re gonna talk about so lets gloss over this.

Code Splitting

Async JavaScript loading is very similar to traditional webpages that would load their pages and their data page by page. React apps are still doing this, just showing loading screens and then populating page data. If you’ve ever used next.js, they have an excellent SSR system with pages defined in seperate JavaScript files. Create React App doesn’t really do code splitting until you eject and make changes to the webpack configuration and probably systematic changes, so we’ll gloss over this too.

Route Management

This is the major thing that’s difficult on the server end, translating the routes on the frontend to the backend, and keeping them consistent. You’ll probably want some tests around this too! We use React Router for this, and as we hinted at, at the beginning of this article, we’re using the v4 version, which is completely different from before, so it’s a primer for React Router v4 too for those unfamiliar with it (well my like 4 hours of use of it anyways).

State

This is something really difficult for user data, it’s similar to caching. For the user agnostic pages, SSR is easy, cause you’re pretty much sending the same data, you can cache it and all that jazz. Traditionally, you don’t cache user data, and personally, I don’t worry about user state. However this is a real issue as you’re potentially losing the benefits of SSR as you wait for your mobile device to download the 200KB JS bundle then make its API call to get the current user state.

In this article and code, I’m using Redux, which has defaults specified on its data. This means the page is rendered to a default and sent over, if you wanted, you could add a caching layer to this, save yourself the render time. If you do care about user data, then I’d recommend sending back a loading state (no user data) or a partial state (no interactions (harder)), then either with embedded data in a script tag or HTTP/2 pushed data so that once your bundle’s loaded, it renders immediately. For the purpose of the article, I’ve glossed over this too.


Structure

I’m building the structure off what I had before, so there’s an express (Node) app on the server, in a server folder, with Create React App doing most of the rest of the structure.

Babel

Your frontend JavaScript is babelified, if you want to use it on the backend, you’re gonna have to babelify it. I haven’t done it the production ready way, if you go through my git commits, you’ll see a reversion commit, just cause I had initially written it in esnext and had to revert it back to Node code. If someone wants to point out a quick tutorial on setting up webpack for Node so that it build’s the whole repo, let me know. In the mean time, magic line of code:

require('babel-register')({ ignore: /\/(build|node_modules)\//, presets: ['react-app'] })

You’ll also need to ignore styles:

require('ignore-styles')

Running

With this setup, you’ve got the standard Create React App setup, if you want to test out your SSR, you need to run a build first. This isn’t ideal, but it’s pretty straight forward, and for the most part, you don’t need to change your SSR once it’s working. Dev steps might be as follows:

  1. yarn start: Mess around with the frontend
  2. yarn run build: Build the frontend
  3. yarn run start:server: Run the server, create API endpoints and so on

Setting up Routing

React Router v4 totally revamped app structure, so it could lead to a pretty heavy change on established projects. We’ll give a quick overview of its new API, especially for those unfamiliar with it. I’m also unfamiliar with it so I might have made a few mistakes or sub optimal decisions, so bear with me.

API Changes from v3->v4

Use react-router-dom. Yup.

No more sub routes and children components. You no longer define your routes separately, but as part of your application. You can define a routes prop, but at that point, you’re kinda fighting the system. A really cool benefit is recursive routes, though I’ve never really found that particularly useful.

Instead of matching a single route, you match all routes. Not what you’re looking for? Use a switch to match a single route, which I need to display my 404 page, so not really negotiable. Don’t want to match a sub-route? Use exact (prop).

You use redirects with browserHistory? Render a redirect to do your redirects, or expose the history prop with the withRouter higher order component, that you can push and replace if you’re not already in a route. You can also specify a from prop when rendered inside a switch.

Router

You don’t have a single router anymore but rather, a BrowserRouter and a StaticRouter. Put the BrowserRouter in the browser side (src/index.js) and the StaticRouter in the server side (server/universal.js). This is actually far better than before, and really simplifies the server side code.

Notable props are basename for the BrowserRouter, stick the process.env.PUBLIC_URL in there and stop worrying about it. location and context on the StaticRouter where location gets the route or req.url from express and context gets an empty object and is filled when you do a render (I could do with a little more explanation on quite a lot of this API so currently I don’t have a reason for you). For redirects, you’ll need to check this context for a url so that you can do a redirect on the server side.

ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
, document.getElementById('root')
)

Server side:

const context = {}
const store = configureStore()
const markup = renderToString(
<Provider store={store}>
<StaticRouter
location={req.url}
context={context}
>
<App/>
</StaticRouter>
</Provider>
)
if (context.url) {
// Somewhere a `<Redirect>` was rendered
redirect(301, context.url)
} else {
// we're good, send the response
const RenderedApp = htmlData.replace('{{SSR}}', markup)
res.send(RenderedApp)
}

What Props do I get?

You get 4 props when you’re inside a route, match, history, location and staticContext . history is nice for change routes, so push to that when you can’t be bothered rendering a redirect. When you have sub routes, use the match attribute like: <Link to={`${match.url}/subpage`}> .

Use the staticContext Prop

Just checking for the window variable will cause your app to break, so make sure you’re checking the staticContext, it’ll be undefined if you’re on the browser and an object (so basic truthy check is sufficient) when you’re on the server. Use this everywhere you might call browser specific code!

Render Differently on the Server

You don’t need to, and in some cases, don’t want to render your whole app on the server. Remember the user can’t really interact with your app until the JavaScript is loaded, so you might just want to render a loading screen or partial screen until you’ve downloaded your JS bundle. Use that staticContext prop!

Express Routes

You’ve got your main route setup to send back from the universal renderer, plus you want a catch all for when a route is completely missed to also send back the universal renderer for all the sub-routes.

Testing

Okay, I wish this was a proper testing section where I setup some PhantomCSS screenshot testing and jest snapshot testing and so on, but I haven’t yet gotten around to learning that, so I guess I’ll have to do a v3?

In Chrome you can disable JavaScript, going in to dev tools, settings, disable JavaScript, or get a Chrome extension. Disabling JavaScript, you should be able to click around without issue. Also use network throttling, stick it on some thing slow, watch the main page get rendered almost immediately then wait for the JavaScript to slowly feed in. In the example I’ve provided, you’ll see that there’s some code that will render differently when viewed straight from the server and once React has diffed the dom.

Conclusion

We added an express server, put in a universal render method,

If you’re like me and love Create React App and aren’t particularly fussed with the SSR performance and don’t need all the code splitting and other features of next.js, while maintaining the flexibility of all of React’s features, this is a great way to get something setup, and it’s easier than ever.