├── .gitignore ├── assets └── style.css ├── package.json ├── server.js ├── README.md └── client.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | assets/bundle.js 3 | -------------------------------------------------------------------------------- /assets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: Helvetica, sans-serif; 5 | background: #fefefe; 6 | color: #444444; 7 | } 8 | 9 | .App { 10 | padding: 25px; 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-quickstart", 3 | "version": "0.0.0", 4 | "description": "React project", 5 | "main": "./server.js", 6 | "browser": "./client.js", 7 | "browserify": { 8 | "transform": [ 9 | ["reactify", {"harmony": true}] 10 | ] 11 | }, 12 | "dependencies": { 13 | "react": "~0.10.0", 14 | "react-async": "~0.9.1", 15 | "react-router-component": "~0.18.2", 16 | "express": "~4.1.1", 17 | "node-jsx": "~0.10.0", 18 | "superagent": "~0.18.0", 19 | "fibers": "~1.0.1" 20 | }, 21 | "devDependencies": { 22 | "reactify": "~0.13.1", 23 | "envify": "~1.2.0", 24 | "browserify": "~3.44.2", 25 | "connect-browserify": "~2.0.1", 26 | "uglify-js": "~2.4.13", 27 | "supervisor": "~0.6.0" 28 | }, 29 | "scripts": { 30 | "test": "echo \"Error: no test specified\" && exit 1", 31 | "start": "supervisor -i node_modules -e js,jsx server.js", 32 | "build": "NODE_ENV=production browserify ./ | uglifyjs -cm 2>/dev/null > ./assets/bundle.js", 33 | "start-prod": "NODE_ENV=production node server.js", 34 | "clean": "rm -f ./assets/bundle.js" 35 | }, 36 | "author": "", 37 | "license": "MIT" 38 | } 39 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var url = require('url'); 5 | var express = require('express'); 6 | var browserify = require('connect-browserify'); 7 | var ReactAsync = require('react-async'); 8 | var nodejsx = require('node-jsx').install(); 9 | var App = require('./client'); 10 | 11 | var development = process.env.NODE_ENV !== 'production'; 12 | 13 | function renderApp(req, res, next) { 14 | var path = url.parse(req.url).pathname; 15 | var app = App({path: path}); 16 | ReactAsync.renderComponentToStringWithAsyncState(app, function(err, markup) { 17 | if (err) { 18 | return next(err); 19 | } 20 | res.send('\n' + markup); 21 | }); 22 | } 23 | 24 | var api = express() 25 | .get('/users/:username', function(req, res) { 26 | var username = req.params.username; 27 | res.send({ 28 | username: username, 29 | name: username.charAt(0).toUpperCase() + username.slice(1) 30 | }); 31 | }); 32 | 33 | var app = express(); 34 | 35 | if (development) { 36 | app.get('/assets/bundle.js', 37 | browserify('./client', { 38 | debug: true, 39 | watch: true 40 | })); 41 | } 42 | 43 | app 44 | .use('/assets', express.static(path.join(__dirname, 'assets'))) 45 | .use('/api', api) 46 | .use(renderApp) 47 | .listen(3000, function() { 48 | console.log('Point your browser at http://localhost:3000'); 49 | }); 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-quickstart 2 | 3 | A minimal React project template which combines: 4 | 5 | * [react-router-component][] to provide HTML5 History routing and navigation 6 | 7 | * [react-async][] to create "asynchronous" React components 8 | 9 | * [express][] to serve pre-rendered React components, assets and provide API 10 | 11 | * [browserify][] to provide module system for a browser 12 | 13 | * [npm][] to install and manage server-side and client-side dependencies 14 | 15 | Every "page" in the application is **pre-rendered on server** so the user can 16 | see the UI before the client code is shipped to a browser. After that 17 | application starts functioning like a **single page application**, navigating 18 | between "pages" without reloads. 19 | 20 | ## Project structure 21 | 22 | Project structure is really minimal, you'd probably like to customize it for 23 | your specific needs and taste: 24 | 25 | . 26 | ├── assets 27 | ├── client.js 28 | ├── package.json 29 | └── server.js 30 | 31 | Directory `assets` is served under `/assets` URL, `client.js` module contains UI 32 | code while `server.js` — HTTP server which serves pre-rendered React components, 33 | assets and provide a stub for a REST API. 34 | 35 | ## Development workflow 36 | 37 | After cloning a git repo, run: 38 | 39 | % npm install 40 | 41 | to install all needed dependencies and then: 42 | 43 | % npm run start 44 | 45 | to start a development server. 46 | 47 | Now you can start edit the source code — on changes, server will be reloaded and 48 | client code bundle will be rebuilt. 49 | 50 | ## Going "production" 51 | 52 | To build an optimized bundle of client code run: 53 | 54 | % npm run build 55 | 56 | which will produce `assets/bundle.js` build, then: 57 | 58 | % npm run start-prod 59 | 60 | to start server in "production" mode (no source code watching and serving 61 | optimized bundle to browser). 62 | 63 | [react-router-component]: http://andreypopp.viewdocs.io/react-router-component 64 | [react-async]: http://andreypopp.viewdocs.io/react-async 65 | [express]: expressjs.com 66 | [npm]: https://www.npmjs.org/ 67 | [browserify]: http://browserify.org/ 68 | -------------------------------------------------------------------------------- /client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jsx React.DOM 3 | */ 4 | 'use strict'; 5 | 6 | 7 | var React = require('react'); 8 | var ReactAsync = require('react-async'); 9 | var ReactRouter = require('react-router-component'); 10 | var superagent = require('superagent'); 11 | 12 | var Pages = ReactRouter.Pages; 13 | var Page = ReactRouter.Page; 14 | var NotFound = ReactRouter.NotFound; 15 | var Link = ReactRouter.Link; 16 | 17 | var MainPage = React.createClass({ 18 | 19 | render: function() { 20 | return ( 21 |
22 |

Hello, anonymous!

23 |

Login

24 |
25 | ); 26 | } 27 | }); 28 | 29 | var UserPage = React.createClass({ 30 | mixins: [ReactAsync.Mixin], 31 | 32 | statics: { 33 | getUserInfo: function(username, cb) { 34 | /* 35 | * The use of localhost URLs work as long as the browser is running on the same machine as the server, 36 | * a typical development setup. 37 | * As soon as you want to run this code on public facing machines, each server will need to know it's 38 | * own hostname and port (which is ugly). 39 | * Relative paths cannot work for serverside rendering, as that has no page context. 40 | * More discussion of this issue, and solutions, can be found at: 41 | * https://github.com/andreypopp/react-async/issues/34 42 | * http://stackoverflow.com/questions/26463924/getting-rid-of-localhost3000-urls-for-reactasync 43 | */ 44 | superagent.get( 45 | 'http://localhost:3000/api/users/' + username, 46 | function(err, res) { 47 | cb(err, res ? res.body : null); 48 | }); 49 | } 50 | }, 51 | 52 | getInitialStateAsync: function(cb) { 53 | this.type.getUserInfo(this.props.username, cb); 54 | }, 55 | 56 | componentWillReceiveProps: function(nextProps) { 57 | if (this.props.username !== nextProps.username) { 58 | this.type.getUserInfo(nextProps.username, function(err, info) { 59 | if (err) { 60 | throw err; 61 | } 62 | this.setState(info); 63 | }.bind(this)); 64 | } 65 | }, 66 | 67 | render: function() { 68 | var otherUser = this.props.username === 'doe' ? 'ivan' : 'doe'; 69 | return ( 70 |
71 |

Hello, {this.state.name}!

72 |

73 | Go to /users/{otherUser} 74 |

75 |

Logout

76 |
77 | ); 78 | } 79 | }); 80 | 81 | var NotFoundHandler = React.createClass({ 82 | 83 | render: function() { 84 | return ( 85 |

Page not found

86 | ); 87 | } 88 | }); 89 | 90 | var App = React.createClass({ 91 | 92 | render: function() { 93 | return ( 94 | 95 | 96 | 97 |