• Stars
    star
    1,792
  • Rank 25,925 (Top 0.6 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created almost 11 years ago
  • Updated about 4 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A simple example of how to do server-side rendering with React (no compilation needed)

react-server-example

A simple (no compile) example of how to do server-side rendering with the React library so that component code can be shared between server and browser, as well as getting fast initial page loads and search-engine-friendly pages.

A more complex example with shared routing and data fetching can be found at react-server-routing-example.

Example

$ npm install
$ node server.js

Then navigate to http://localhost:3000 and click on the button to see some reactive events in action.

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)

Here are the files involved:

App.js:

var createReactClass = require('create-react-class')
var DOM = require('react-dom-factories')
var div = DOM.div, button = DOM.button, ul = DOM.ul, li = DOM.li

// This is just a simple example of a component that can be rendered on both
// the server and browser

module.exports = createReactClass({

  // We initialise its state by using the `props` that were passed in when it
  // was first rendered. We also want the button to be disabled until the
  // component has fully mounted on the DOM
  getInitialState: function() {
    return {items: this.props.items, disabled: true}
  },

  // Once the component has been mounted, we can enable the button
  componentDidMount: function() {
    this.setState({disabled: false})
  },

  // Then we just update the state whenever its clicked by adding a new item to
  // the list - but you could imagine this being updated with the results of
  // AJAX calls, etc
  handleClick: function() {
    this.setState({
      items: this.state.items.concat('Item ' + this.state.items.length),
    })
  },

  // For ease of illustration, we just use the React JS methods directly
  // (no JSX compilation needed)
  // Note that we allow the button to be disabled initially, and then enable it
  // when everything has loaded
  render: function() {

    return div(null,

      button({onClick: this.handleClick, disabled: this.state.disabled}, 'Add Item'),

      ul({children: this.state.items.map(function(item) {
        return li(null, item)
      })})

    )
  },
})

browser.js:

var React = require('react')
var ReactDOM = require('react-dom')
// This is our React component, shared by server and browser thanks to browserify
var App = React.createFactory(require('./App'))

// 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.render(App(window.APP_PROPS), document.getElementById('content'))

server.js:

var http = require('http')
var browserify = require('browserify')
var literalify = require('literalify')
var React = require('react')
var ReactDOMServer = require('react-dom/server')
var DOM = require('react-dom-factories')
var body = DOM.body, div = DOM.div, script = DOM.script
// This is our React component, shared by server and browser thanks to browserify
var App = React.createFactory(require('./App'))

// A variable to store our JS, which we create when /bundle.js is first requested
var BUNDLE = null

// Just create a plain old HTTP server that responds to two endpoints ('/' and
// '/bundle.js') This would obviously work similarly with any higher level
// library (Express, etc)
http.createServer(function(req, res) {

  // If we hit the homepage, then we want to serve up some HTML - including the
  // server-side rendered React component(s), as well as the script tags
  // pointing to the client-side code
  if (req.url === '/') {

    res.setHeader('Content-Type', 'text/html; charset=utf-8')

    // `props` represents the data to be passed in to the React component for
    // rendering - just as you would pass data, or expose variables in
    // templates such as Jade or Handlebars.  We just use some dummy data
    // here (with some potentially dangerous values for testing), but you could
    // imagine this would be objects typically fetched async from a DB,
    // filesystem or API, depending on the logged-in user, etc.
    var props = {
      items: [
        'Item 0',
        'Item 1',
        'Item </scRIpt>\u2028',
        'Item <!--inject!-->\u2029',
      ],
    }

    // Here we're using React to render the outer body, so we just use the
    // simpler renderToStaticMarkup function, but you could use any templating
    // language (or just a string) for the outer page template
    var html = ReactDOMServer.renderToStaticMarkup(body(null,

      // The actual server-side rendering of our component occurs here, and we
      // pass our data in as `props`. This div is the same one that the client
      // will "render" into on the browser from browser.js
      div({
        id: 'content',
        dangerouslySetInnerHTML: {__html: ReactDOMServer.renderToString(App(props))},
      }),

      // The props should match on the client and server, so we stringify them
      // on the page to be available for access by the code run in browser.js
      // You could use any var name here as long as it's unique
      script({
        dangerouslySetInnerHTML: {__html: 'var APP_PROPS = ' + safeStringify(props) + ';'},
      }),

      // We'll load React from a CDN - you don't have to do this,
      // you can bundle it up or serve it locally if you like
      script({src: 'https://cdn.jsdelivr.net/npm/[email protected]/umd/react.production.min.js'}),
      script({src: 'https://cdn.jsdelivr.net/npm/[email protected]/umd/react-dom.production.min.js'}),
      script({src: 'https://cdn.jsdelivr.net/npm/[email protected]/index.min.js'}),
      script({src: 'https://cdn.jsdelivr.net/npm/[email protected]/create-react-class.min.js'}),

      // Then the browser will fetch and run the browserified bundle consisting
      // of browser.js and all its dependencies.
      // We serve this from the endpoint a few lines down.
      script({src: '/bundle.js'})
    ))

    // Return the page to the browser
    res.end(html)

  // This endpoint is hit when the browser is requesting bundle.js from the page above
  } else if (req.url === '/bundle.js') {

    res.setHeader('Content-Type', 'text/javascript')

    // If we've already bundled, send the cached result
    if (BUNDLE != null) {
      return res.end(BUNDLE)
    }

    // Otherwise, invoke browserify to package up browser.js and everything it requires.
    // We also use literalify to transform our `require` statements for React
    // so that it uses the global variable (from the CDN JS file) instead of
    // bundling it up with everything else
    browserify()
      .add('./browser.js')
      .transform(literalify.configure({
        'react': 'window.React',
        'react-dom': 'window.ReactDOM',
        'react-dom-factories': 'window.ReactDOMFactories',
        'create-react-class': 'window.createReactClass',
      }))
      .bundle(function(err, buf) {
        // Now we can cache the result and serve this up each time
        BUNDLE = buf
        res.statusCode = err ? 500 : 200
        res.end(err ? err.message : BUNDLE)
      })

  // Return 404 for all other requests
  } else {
    res.statusCode = 404
    res.end()
  }

// The http server listens on port 3000
}).listen(3000, function(err) {
  if (err) throw err
  console.log('Listening on 3000...')
})


// A utility function to safely escape JSON for embedding in a <script> tag
function safeStringify(obj) {
  return JSON.stringify(obj)
    .replace(/<\/(script)/ig, '<\\/$1')
    .replace(/<!--/g, '<\\!--')
    .replace(/\u2028/g, '\\u2028') // Only necessary if interpreting as JS, which we do
    .replace(/\u2029/g, '\\u2029') // Ditto
}

More Repositories

1

alpine-node

Minimal Node.js Docker Images built on Alpine Linux
Dockerfile
2,452
star
2

kinesalite

An implementation of Amazon's Kinesis built on LevelDB
JavaScript
796
star
3

aws4

Signs and prepares Node.js requests using AWS Signature Version 4
JavaScript
663
star
4

aws4fetch

A compact AWS client for modern JS environments
JavaScript
405
star
5

react-server-routing-example

An example using universal client/server routing and data in React with AWS DynamoDB
JavaScript
299
star
6

simple-relay-starter

A very simple starter for React Relay using Browserify
JavaScript
156
star
7

kinesis

A Node.js stream implementation of Amazon's Kinesis
JavaScript
149
star
8

epipebomb

Ignore EPIPE errors when stdout runs through a truncated pipe (such as `head`) in Node.js
JavaScript
63
star
9

awscred

Node.js module to resolve AWS credentials/region using env, ini files and IAM roles
JavaScript
45
star
10

gelf-stream

A node.js stream to send JS objects to a Graylog2 server (in GELF format)
JavaScript
28
star
11

StringStream

Encode and decode streams into string streams in node.js
JavaScript
27
star
12

dynamo-table

A lightweight module to map JS objects and queries to DynamoDB tables
JavaScript
24
star
13

gelfling

Create and send GELF (Graylog2) messages in node.js, including chunking
JavaScript
8
star
14

awslogger

A CLI tool to send lines from stdin to AWS CloudWatch Logs
JavaScript
7
star
15

aws2

Signs and prepares node.js http(s) requests using AWS Signature Version 2
JavaScript
6
star
16

deep_learning_glossary

Simple, opinionated explanations of various things encountered in Deep Learning
6
star
17

pdf2png-demo

A demo of Container Image Support in AWS Lambda
Go
6
star
18

test-ci-project

Shell
2
star
19

aws3

Signs and prepares node.js http(s) requests using AWS Signature Version 3
JavaScript
1
star