react-server-routing-example
A simple (no compile) example of how to do universal server/browser rendering, routing and data fetching with React and AWS DynamoDB for fast page loads, and search-engine-friendly progressively-enhanced pages.
Also known as isomorphic, this approach shares as much browser and server code
as possible and allows single-page apps to also render on the server. All
React components, as well as router.js
and db.js
are shared (using
browserify) and data fetching needs are declared
statically on each component.
This example shows a very basic blog post viewer, Grumblr, with the posts stored in and fetched from DynamoDB whenever the route changes.
An even simpler example of server-side rendering with React, with no routing or data fetching, can be found at react-server-example.
Example
$ npm install$ node server.js
Then navigate to http://localhost:3000 and click some links, press the back button, etc.
Try viewing the page source to ensure the HTML being sent from the server is already rendered (with checksums to determine whether client-side rendering is necessary).
Also note that when JavaScript is enabled, the single-page app will fetch the data via AJAX POSTs to DynamoDB directly, but when it's disabled the links will follow the hrefs and fetch the full page from the server each request.
Here are the files involved:
router.js
:
// This is a very basic router, shared between the server (in server.js) and// browser (in App.js), with each route defining the URL to be matched and the// main component to be rendered exportsroutes = list: url: '/' component: view: url: /^\/posts\/$/ component: // A basic routing resolution function to go through each route and see if the// given URL matches. If so we return the route key and data-fetching function// the route's component has declared (if any)exports { for var key in exportsroutes var route = exportsrouteskey var match = typeof routeurl === 'string' ? url === routeurl : url if match var params = Array ? match : return key: key { if !routecomponentfetchData return return routecomponentfetchData } }
PostList.js
:
var createReactClass = var DOM = var db = var div = DOMdiv h1 = DOMh1 ul = DOMul li = DOMli a = DOMa // This is the component we use for listing the posts on the homepage moduleexports =
PostView.js
:
var createReactClass = var DOM = var db = var div = DOMdiv h1 = DOMh1 p = DOMp a = DOMa // This is the component we use for viewing an individual post moduleexports =
App.js
:
var React = var createReactClass = var router = // This is the top-level component responsible for rendering the correct// component (PostList/PostView) for the given route as well as handling any// client-side routing needs (via window.history and window.onpopstate) moduleexports =
browser.js
:
var React = var ReactDOM = var App = React // This script will run in the browser and will render our component using the// value from APP_PROPS that we generate inline in the page's html on the server.// If these props match what is used in the server render, React will see that// it doesn't need to generate any DOM and the page will load faster ReactDOM
server.js
:
var http = var browserify = var literalify = var React = var ReactDOMServer = var DOM = var AWS = // Our router, DB and React components are all shared by server and browser// thanks to browserifyvar router = var db = var body = DOMbody div = DOMdiv script = DOMscriptvar App = React // A variable to store our JS, which we create when /bundle.js is first requestedvar BUNDLE = null // Just create a plain old HTTP server that responds to our route endpoints// (and '/bundle.js')var server = http // We start the http server after we check if the DB has been setup correctly // A utility function to safely escape JSON for embedding in a <script> tag { return JSON // Only necessary if interpreting as JS, which we do // Ditto} // A bootstrapping function to create and populate our DB table if it doesn't// exist (and start the mock DB if running locally) { // Excluded for brevity...}
db.js
:
var AWS = // Because the AWS SDK works in the browser as well, we can share this file and all its// functions and reuse them on both the server and the browser var db = moduleexports = // This endpoint will try to connect to a DynamoDB running locally // Comment this out if you want to connect to a live/production AWS DynamoDB instance endpoint: 'http://localhost:4567' region: 'us-east-1' // These credentials are only necessary if connecting to AWS, // but including credentials in your client-side code is obviously // problematic if your project is public facing. // See http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/browser-configuring.html#Loading_Credentials_in_the_Client_s_Browser // for other (safer) methods to authenticate users for the AWS SDK credentials: accessKeyId: 'akid' secretAccessKey: 'secret' // This function will fetch the id, date and title of all posts in our// "grumblr" tabledb { db} // This function will fetch the detail of a particular posts from our "grumblr"// tabledb { db} // A simple utility function to flatten a DynamoDB object { return Object}