{"id":1583,"date":"2015-06-29T00:43:14","date_gmt":"2015-06-29T05:43:14","guid":{"rendered":"http:\/\/cbateman.com\/blog\/?p=1583"},"modified":"2016-12-10T12:29:14","modified_gmt":"2016-12-10T17:29:14","slug":"introduction-to-isomorphic-rendering-with-react","status":"publish","type":"post","link":"https:\/\/cbateman.com\/blog\/introduction-to-isomorphic-rendering-with-react\/","title":{"rendered":"Introduction to Isomorphic Rendering with React"},"content":{"rendered":"<p>First off: yeah, isomorphic is a silly word, but there isn&#8217;t really a better alternative. If you&#8217;re not familiar, it just means the ability to render an app on both the client and the server.<\/p>\r\n\r\n<p>Why would you want to render a JavaScript app on the server? Mainly, for performance. Not everyone (especially mobile users) is on the instant connection you use while you&#8217;re developing, and they end up staring at a blank page while the JS downloads and executes. With server-side rendering, we can give them the full HTML immediately so that they can start looking at the page while the JS loads. It also provide a measure of progressive enhancement &mdash; if something in the JS fails, at least the user still gets something. And it also allows search engines to crawl your apps, for better SEO.<\/p>\r\n\r\n<p>Currently, with the major JS frameworks, server-side rendering of JavaScript apps is really only possible with React, though Angular and Ember have plans to support it in the future.<\/p>\r\n\r\n<p>This post is a basic introduction for those who are new to the topic and are curious about how it might work. This won&#8217;t be a production-ready implementation (there are plenty of starter-kits out there if that&#8217;s what you&#8217;re looking for), and we won&#8217;t deal with loading the JS app in the server-rendered version.<\/p>\r\n\r\n<p>To start with, let&#8217;s check out a very simple React app with a couple routes. We&#8217;ll use ES6 with <a href=\"http:\/\/babeljs.io\">Babel<\/a> and <a href=\"http:\/\/rackt.github.io\/react-router\/\">react-router<\/a> for routing, of course.<\/p>\r\n\r\n<p><code>browser.jsx<\/code> will be the starting point for our code when it runs in the browser. It imports our routes, starts up the router, and renders the resulting app into <code>document.body<\/code>.\r\n\r\n<pre class=\"pre\"><code class=\"language-javascript\">\/\/ browser.jsx\r\nimport React from 'react';\r\nimport Router from 'react-router';\r\nimport routes from '.\/routes';\r\n\r\nRouter.run(routes, Router.HashLocation, (Root) => {\r\n    React.render(&lt;Root\/&gt;, document.body);\r\n});\r\n<\/code><\/pre>\r\n\r\n<p>Here&#8217;s our route config, showing our app&#8217;s default &#8220;Index&#8221; route as well as an &#8220;About&#8221; route.<\/p>\r\n\r\n<pre class=\"pre\"><code class=\"language-javascript\">\/\/ routes.jsx\r\nimport React from 'react';\r\nimport {Route, DefaultRoute} from 'react-router';\r\nimport App from '.\/app'\r\nimport Index from '.\/routes\/index';\r\nimport About from '.\/routes\/about';\r\n\r\nexport default (\r\n    &lt;Route handler={App}&gt;\r\n        &lt;DefaultRoute name=\"home\" handler={Index} \/&gt;\r\n        &lt;Route name=\"about\" handler={About} \/&gt;\r\n    &lt;\/Route&gt;\r\n);\r\n<\/code><\/pre>\r\n\r\n<p>The app component contains the base HTML for the page, which includes links to each route as well as the <code>RouteHandler<\/code>, which is where our route components will be displayed.<\/p>\r\n\r\n<pre class=\"pre\"><code class=\"language-javascript\">\/\/ app.jsx\r\nimport React from 'react';\r\nimport {RouteHandler, Link} from 'react-router';\r\n\r\nexport default class extends React.Component {\r\n    render() {\r\n        return (\r\n            &lt;div&gt;\r\n                &lt;h1&gt;React Isomorphic Demo&lt;\/h1&gt;\r\n                &lt;nav&gt;\r\n                    Route navigation:\r\n                    &lt;Link to=\"home\"&gt;Home&lt;\/Link&gt; | &lt;Link to=\"about\"&gt;About&lt;\/Link&gt;\r\n                &lt;\/nav&gt;\r\n                &lt;RouteHandler\/&gt;\r\n            &lt;\/div&gt;\r\n        );\r\n    }\r\n}\r\n<\/code><\/pre>\r\n\r\n<p>So far, this is pretty standard for a routed React app. Here&#8217;s the result (or open it in a <a href=\"https:\/\/chrisbateman.github.io\/react-isomorphic-demo\/index.htm\">new window<\/a>). The HTML file itself contains nothing but a &lt;title&gt; and a &lt;script&gt; tag for our built JS.<\/p>\r\n\r\n<iframe src=\"https:\/\/chrisbateman.github.io\/react-isomorphic-demo\/index.htm\" class=\"demo-frame\" height=\"225\"><\/iframe>\r\n\r\n<p>Alright, so now let&#8217;s render our app on the server. I&#8217;m using node.js, but you really could do it with any JavaScript runtime, thanks to React&#8217;s virtual DOM. There&#8217;s no need to simulate a DOM with <a href=\"https:\/\/github.com\/tmpvar\/jsdom\">jsdom<\/a> or <a href=\"http:\/\/phantomjs.org\/\">PhantomJS<\/a> or anything like that.<\/p>\r\n\r\n<p>Below we have a function that will be our server-side equivalent to <code>browser.jsx<\/code>. The difference is that here we specify the exact route we want to render, render the result to a string with <code>React.renderToStaticMarkup()<\/code> and save it, rather than inserting it into <code>document.body<\/code>.<\/p>\r\n\r\n<pre class=\"pre\"><code class=\"language-javascript\">\/\/ serverrender.js\r\nfunction renderRoute(routePath, filePath) {\r\n    Router.run(routes, routePath, function(Root, state) {\r\n        var appHtml = React.renderToStaticMarkup(React.createElement(Root));\r\n        fs.writeFile(filePath, fileHeader + appHtml);\r\n    });\r\n}\r\n\r\nrenderRoute('\/', 'dist\/server-index.htm');\r\nrenderRoute('\/about', 'dist\/server-about.htm');\r\n<\/code><\/pre>\r\n\r\n<p>And here are the resulting HTML pages (<a href=\"https:\/\/chrisbateman.github.io\/react-isomorphic-demo\/server-index.htm\">new<\/a> <a href=\"https:\/\/chrisbateman.github.io\/react-isomorphic-demo\/server-about.htm\">window<\/a>):<\/p>\r\n\r\n<iframe src=\"https:\/\/chrisbateman.github.io\/react-isomorphic-demo\/server-index.htm\" class=\"demo-frame\" height=\"225\"><\/iframe>\r\n<iframe src=\"https:\/\/chrisbateman.github.io\/react-isomorphic-demo\/server-about.htm\" class=\"demo-frame\" height=\"225\"><\/iframe>\r\n\r\n<p>As you can see, they look exactly like the pages in the JS version. Except here, there&#8217;s absolutely no JavaScript &mdash; it&#8217;s just plain HTML (the route links won&#8217;t work, by the way, since I haven&#8217;t set up those locations).<\/p>\r\n\r\n<p>This is an incredibly simplistic app, but I&#8217;m still curious: what&#8217;s the difference for performance? Using an iPhone 6 and a stopwatch (so take it with a grain of salt), load times went about like this for me:<\/p>\r\n\r\n<ul>\r\n  <li>3G JS: 2.0-2.5s<\/li>\r\n  <li>3G server-rendered: 1.0s<\/li>\r\n  <li>LTE JS: 1.0s<\/li>\r\n  <li>LTE server-rendered: 0.3s<\/li>\r\n<\/ul>\r\n\r\n<p>Using <a href=\"http:\/\/webpagetest.org\">WebPagetest.org<\/a>, with a Motorola G on 3G, I got 3.5s for the JS page and 1.5s for server-rendered.<\/p>\r\n\r\n<p>That&#8217;s pretty awesome. When <a href=\"http:\/\/blog.codinghorror.com\/performance-is-a-feature\/\">fractions of a second<\/a> can make a difference in your site&#8217;s success, shaving whole seconds off is a big deal. And the difference will only get more pronounced as an app gets bigger.<\/p>\r\n\r\n<p>The full <a href=\"https:\/\/github.com\/chrisbateman\/react-isomorphic-demo\">source is on GitHub<\/a>, so feel free to clone it if you want to play around with it.<\/p>\r\n","protected":false},"excerpt":{"rendered":"<p>First off: yeah, isomorphic is a silly word, but there isn&#8217;t really a better alternative. If you&#8217;re not familiar, it just means the ability to render an app on both the client and the server. Why would you want to render a JavaScript app on the server? Mainly, for performance. Not everyone (especially mobile users) is on the instant connection&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1583","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/cbateman.com\/blog\/wp-json\/wp\/v2\/posts\/1583"}],"collection":[{"href":"https:\/\/cbateman.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cbateman.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cbateman.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cbateman.com\/blog\/wp-json\/wp\/v2\/comments?post=1583"}],"version-history":[{"count":44,"href":"https:\/\/cbateman.com\/blog\/wp-json\/wp\/v2\/posts\/1583\/revisions"}],"predecessor-version":[{"id":1679,"href":"https:\/\/cbateman.com\/blog\/wp-json\/wp\/v2\/posts\/1583\/revisions\/1679"}],"wp:attachment":[{"href":"https:\/\/cbateman.com\/blog\/wp-json\/wp\/v2\/media?parent=1583"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cbateman.com\/blog\/wp-json\/wp\/v2\/categories?post=1583"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cbateman.com\/blog\/wp-json\/wp\/v2\/tags?post=1583"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}