├── .babelrc ├── .gitignore ├── README.md ├── client-render.js ├── components ├── about.js ├── app.js └── index.js ├── package.json ├── routes.js ├── server.js ├── views └── index.ejs └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | public/build.js 2 | node_modules 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # universal-react-example 2 | An example Universal ReactJS application 3 | 4 | Written for 24ways. [Go read the blog post](https://24ways.org/2015/universal-react/) :) 5 | 6 | ## Install 7 | ```sh 8 | $ git clone https://github.com/jackfranklin/universal-react-example.git 9 | $ cd universal-react-example 10 | $ npm install 11 | $ npm run build 12 | $ npm start 13 | $ open http://localhost:3003 14 | ``` 15 | -------------------------------------------------------------------------------- /client-render.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Router, browserHistory } from 'react-router'; 4 | 5 | import { routes } from './routes'; 6 | 7 | // import createBrowserHistory from 'history/lib/createBrowserHistory'; 8 | 9 | ReactDOM.render( 10 | , 11 | document.getElementById('app') 12 | ) 13 | -------------------------------------------------------------------------------- /components/about.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class AboutComponent extends React.Component { 4 | render() { 5 | return ( 6 |
7 |

A little bit about me.

8 |
9 | ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /components/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | export default class AppComponent extends React.Component { 5 | render() { 6 | return ( 7 |
8 |

Welcome to my App

9 | 13 | { this.props.children } 14 |
15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /components/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class IndexComponent extends React.Component { 4 | render() { 5 | return ( 6 |
7 |

This is the index page

8 |
9 | ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "universal-react-example", 3 | "version": "1.0.0", 4 | "description": "Universal react example", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "webpack -p", 9 | "start": "babel-node server.js" 10 | }, 11 | "keywords": [ 12 | "react", 13 | "react-router", 14 | "ejs", 15 | "express", 16 | "history", 17 | "react-dom", 18 | "babel", 19 | "webpack" 20 | ], 21 | "author": "", 22 | "license": "ISC", 23 | "dependencies": { 24 | "ejs": "2.4.2", 25 | "express": "4.14.0", 26 | "history": "3.0.0", 27 | "react": "15.2.1", 28 | "react-dom": "15.2.1", 29 | "react-router": "2.5.2" 30 | }, 31 | "devDependencies": { 32 | "babel-cli": "6.10.1", 33 | "babel-core": "6.10.4", 34 | "babel-loader": "6.2.4", 35 | "babel-preset-es2015": "6.9.0", 36 | "babel-preset-react": "6.11.1", 37 | "webpack": "1.13.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /routes.js: -------------------------------------------------------------------------------- 1 | import AppComponent from './components/app'; 2 | import IndexComponent from './components/index'; 3 | import AboutComponent from './components/about'; 4 | 5 | const routes = { 6 | path: '', 7 | component: AppComponent, 8 | childRoutes: [ 9 | { 10 | path: '/', 11 | component: IndexComponent 12 | }, 13 | { 14 | path: '/about', 15 | component: AboutComponent 16 | } 17 | ] 18 | } 19 | 20 | export { routes }; 21 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | 3 | import React from 'react'; 4 | import { renderToString } from 'react-dom/server'; 5 | import { match, RouterContext } from 'react-router'; 6 | 7 | import { routes } from './routes'; 8 | 9 | const app = express(); 10 | 11 | app.use(express.static('public')); 12 | 13 | app.set('view engine', 'ejs'); 14 | 15 | app.get('*', (req, res) => { 16 | // routes is our object of React routes defined above 17 | match({ routes, location: req.url }, (err, redirectLocation, props) => { 18 | if (err) { 19 | // something went badly wrong, so 500 with a message 20 | res.status(500).send(err.message); 21 | } else if (redirectLocation) { 22 | // we matched a ReactRouter redirect, so redirect from the server 23 | res.redirect(302, redirectLocation.pathname + redirectLocation.search); 24 | } else if (props) { 25 | // if we got props, that means we found a valid component to render 26 | // for the given route 27 | const markup = renderToString(); 28 | 29 | // render `index.ejs`, but pass in the markup we want it to display 30 | res.render('index', { markup }) 31 | 32 | } else { 33 | // no route match, so 404. In a real app you might render a custom 34 | // 404 view here 35 | res.sendStatus(404); 36 | } 37 | }); 38 | }); 39 | app.listen(3003, 'localhost', function(err) { 40 | if (err) { 41 | console.log(err); 42 | return; 43 | } 44 | console.log('Listening at http://localhost:3003'); 45 | }); 46 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | My App 5 | 6 | 7 | 8 |
<%- markup %>
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | module.exports = { 3 | entry: path.join(process.cwd(), 'client-render.js'), 4 | output: { 5 | path: './public/', 6 | filename: 'build.js' 7 | }, 8 | module: { 9 | loaders: [ 10 | { 11 | test: /\.js$/, 12 | loader: 'babel' 13 | } 14 | ] 15 | } 16 | } 17 | --------------------------------------------------------------------------------