Building a Web App in React and ASP.NET Core - Part 2

In part one of this React series we built a client-side tube status mobile web app. In this part we'll examine server-side rendering to generate some HTML.

Motivations

Our previous app was entirely written in JavaScript and this meant that nothing was displayed in the browser until the app had loaded and executed. It would be a better user experience (UX) if we served some HTML that could be rendered before the JavaScript has run.

This also allows user agents that don't support JavaScript to view the site and provides a form of progressive enhancement. For example, search engine crawlers can easily get an idea of the content and this will help with SEO.

Our React app is run on the server so there is no code duplication. We don't have to maintain a client and server codebase in parallel.

Implications

It's worth considering the future implications of any design decisions upfront. As with any choice, there are trade-offs. There is a fork in the road.

We can choose to either serve generic placeholder HTML that doesn't change or we can render the current live data. If we call the API from the server and use that data then the user will immediately get the most up-to-date information, but this limits our agility.

By rendering dynamic data we will make it more difficult to make an offline version of our app later. It is a good idea to separate the application logic from the state. You would typically cache the app much longer than the dynamic data, perhaps even indefinitely.

If sending the latest state as HTML then we will also need a full application server on the back-end rather than a simple static site. This is less of a concern but can have scaling implications down the line.

For now, we will start with a static server-side model and will explore dynamic data later. We'll add a generic placeholder board to reactube.com that shows the line names but no statuses until the API has been called from the client.

Build

We don't need to make many changes in our existing app to use server-side rendering. You can see all of the differences together or view the source of reactube.com to see the final result of this part.

Step 1 - Remove Code from JSX

We need to remove a couple of things from the JSX file. First we'll remove the hard-coded statusData and replace this with the injected initialData from props.

getInitialState: function () {  
    return {
        statusData: this.props.initialData,
        statusTimestamp: moment()
    };
},

We also want to remove the call to ReactDOM.render() as this will be generated by ReactJS.NET.

Step 2 - Modify the HTML View

In our Index.cshtml Razor view we should remove our <div> and add a React helper @Html.React("TubeStatus", new { initialData = Model }). This will generate a <div> containing our initial data (with a custom random id that matches the generated JavaScript).

We also need to add @Html.ReactInitJavaScript() after we load our libraries. I've also changed the source of moment.js from a CDN to local, but this isn't strictly necessary.

Step 3 - Add Local Dependencies

As our app is now run on both the client and server, we need to add a local copy of any additional libraries that we are using. In our case just moment.js. Download the same (min) file from the CDN and add it to the js folder.

In Startup.cs we need to define our dependencies. So we need to add the following to the UseReact config.

config  
    .AddScriptWithoutTransform("~/js/moment.min.js")
    .AddScript("~/js/reactube.jsx");

The moment.min.js file can be used as-is but our JSX file needs converting to JavaScript.

Step 4 - Add a Model to the Controller

Finally, we need to create a static model and pass this to our view in the Index action of the HomeController.

var model = new[] {  
   new { id = "bakerloo",          name = "Bakerloo",           modeName = "tube" },
   new { id = "central",           name = "Central",            modeName = "tube" },
   new { id = "circle",            name = "Circle",             modeName = "tube" },
   new { id = "district",          name = "District",           modeName = "tube" },
   new { id = "hammersmith-city",  name = "Hammersmith & City", modeName = "tube" },
   new { id = "jubilee",           name = "Jubilee",            modeName = "tube" },
   new { id = "metropolitan",      name = "Metropolitan",       modeName = "tube" },
   new { id = "northern",          name = "Northern",           modeName = "tube" },
   new { id = "piccadilly",        name = "Piccadilly",         modeName = "tube" },
   new { id = "victoria",          name = "Victoria",           modeName = "tube" },
   new { id = "waterloo-city",     name = "Waterloo & City",    modeName = "tube" },
   new { id = "london-overground", name = "London Overground",  modeName = "overground" },
   new { id = "tfl-rail",          name = "TfL Rail",           modeName = "tflrail" },
   new { id = "dlr",               name = "DLR",                modeName = "dlr" },
   new { id = "tram",              name = "Tram",               modeName = "tram" }
};
return View(model);  

Here we have simply hard-coded an array of anonymous objects. This will be serialized to JSON for the initial data and also used to generate the HTML for the page.

Done

That's it. You can try the output of this part at reactube.com. Even with no JavaScript you get a table of transport lines with placeholder statuses.

Next Steps

The next step would be to call the API from the server and use that to generate the initial HTML. However, beware of the implications mentioned above.

TfL even provide their DTOs as a DLL, although it is missing a common library that it references. It's also less than trivial to use it with ASP.NET Core, even if using .NET Framework 4 as the foundation.

Other improvements include modifying the JSX to use ES6 features such as classes, rather than the React.createClass() helper method.

If you liked this post then you may also like my book or my services.

Submit / Comment on Hacker News