├── .babelrc ├── .eslintrc ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── gulpfile.babel.js ├── package.json ├── src ├── ReactServer.js ├── Response.js ├── Route.js ├── handlers │ └── simpleResponse.js ├── index.js ├── middleware │ ├── Favicon.js │ ├── Middleware.js │ ├── Static.js │ └── index.js ├── server │ ├── createServer.js │ ├── index.js │ ├── runServer.js │ └── serve.js └── utils │ ├── Html.js │ ├── ReactResponseGreeter.js │ └── createTemplateString.js └── test ├── Middleware.spec.js ├── ReactServer.spec.js ├── Response.spec.js ├── consumer ├── consumerenv.js ├── helpers │ └── favicon.ico ├── routes.js └── test.js ├── createServer.spec.js ├── helpers ├── Null.js └── testenv.js └── runServer.spec.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["add-module-exports", "babel-root-import"], 3 | "presets": ["react", "es2015", "stage-0"] 4 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "plugins": [ 5 | "react" 6 | ], 7 | "env": { 8 | "browser": false, 9 | "es6": true, 10 | "node": true 11 | }, 12 | "ecmaFeatures": { 13 | "jsx": true, 14 | "classes": true, 15 | "modules": true 16 | }, 17 | "rules": { 18 | "no-debugger": 0, 19 | "no-console": 0, 20 | "eqeqeq": 1, 21 | "space-after-keywords": 0, 22 | "max-len": 0, 23 | "indent": [2, 4], 24 | "padded-blocks": 0, 25 | "comma-dangle": 0, 26 | "key-spacing": 0, 27 | "new-cap": 0, 28 | "strict": [2, "global"], 29 | "no-underscore-dangle": 0, 30 | "no-use-before-define": 0, 31 | "eol-last": 0, 32 | "quotes": 0, 33 | "semi": [2, "never"], 34 | "space-infix-ops": 0, 35 | "space-in-parens": 0, 36 | "no-trailing-spaces": 0, 37 | "no-multi-spaces": 0, 38 | "no-alert": 0, 39 | "no-undef": 2, 40 | "func-names": 0, 41 | "space-before-function-paren": [2, "never"], 42 | "no-unused-vars": 0, 43 | "react/jsx-boolean-value": 1, 44 | "react/jsx-curly-spacing": 0, 45 | "react/jsx-indent-props": [2, 4], 46 | "react/prefer-es6-class": [1, "always"], 47 | "react/jsx-uses-react": 1, 48 | "react/jsx-uses-vars": 1, 49 | "react/jsx-no-bind": 0, 50 | "react/jsx-closing-bracket-location": 0 51 | }, 52 | "globals" : { 53 | "document": false, 54 | "escape": false, 55 | "navigator": false, 56 | "unescape": false, 57 | "window": false, 58 | "describe": true, 59 | "before": true, 60 | "it": true, 61 | "expect": true, 62 | "sinon": true 63 | } 64 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | *.tgz 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Daniel Dunderfelt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | React-response 2 | === 3 | 4 | React-response provides an easy-to-use server-side rendering server for React. 5 | The goal of this project is to reduce the boilerplate you need for a universal React project. Instead of copy-pasting a server from somewhere, React-response makes it possible to quickly write a production-ready server-side React renderer yourself. This will enable you to focus on your app without worrying about how to implement server-side rendering. 6 | 7 | The configuration of your React-response is done with familiar React components, kind of like React Router's route configuration. Almost all of the props have sensible defaults, so for the simplest apps you really don't have to do a lot to get a server running. 8 | 9 | > Production-ready stability is one of my end goals but we're still in the early days. Use in production at your own risk! If you do, do not hesitate to contact me with your experience. 10 | 11 | What's it look like? 12 | --- 13 | 14 | Glad you asked. The simplest hello World with React-response looks like this: 15 | 16 | ```javascript 17 | import { ReactServer, Response, serve, createServer } from 'react-response' 18 | 19 | const server = createServer( 20 | 21 | 22 | 23 | ) 24 | 25 | serve(server) 26 | 27 | ``` 28 | 29 | Running that will display a built-in Hello World page in your browser at `localhost:3000`. 30 | 31 | The `ReactServer` component instantiates a new Express server. The `Response` component is responsible for rendering a React application at the specified Express route. The simplest example above demonstrates the built-in defaults, but most of React-response's behaviour is customizable. The full example illustrates all the ways you can customize React-response. 32 | 33 | ### The full example 34 | 35 | ```javascript 36 | import { RouterContext } from 'react-router' // RouterContext is your root component if you're using React-router. 37 | import routes from './routes' // React-router routes 38 | import Html from './helpers/Html' // Your template component 39 | import ReactResponse from 'react-response' 40 | 41 | // Install the React-router-response if you use React-router 42 | import createReactRouterResponse from 'react-response-router' 43 | 44 | // Import all the things 45 | const { 46 | ReactServer, 47 | Route, 48 | Response, 49 | serve, 50 | createServer, 51 | Middleware, 52 | Static, 53 | Favicon, 54 | } = ReactResponse 55 | 56 | /* Note that you need to install 'serve-favicon' and other middleware if you want to use them. */ 57 | 58 | const server = createServer( 59 | // Set basic server config on the ReactServer component. 60 | 61 | // This is an Express route with the default props. Middlewares need to be 62 | // mounted inside a Route component. 63 | 64 | // React-response ships with wrappers for some commonly used middleware. 65 | 66 | 67 | 68 | 69 | // Set your template and handler. 70 | // React-response uses simple built-in templates and handlers by default. 71 | 72 | // Pass the React component you want to render OR 73 | // a custom render function as a child to Response. 74 | 75 | {(renderProps, req, res) => { 76 | 77 | // Return a map of props for the template component. The Html component 78 | // takes one prop: `component` which should be a rendered React component. 79 | return { component: ReactDOM.renderToString( 80 | 81 | ) } 82 | }} 83 | 84 | 85 | // Many routes 86 | 87 | { /* Implement your APi proxy */ }} /> 88 | 89 | 90 | ) 91 | 92 | serve(server) 93 | 94 | ``` 95 | 96 | Alright, this is more like it! As you can see, with React-response we attach middleware and app renderers to Routes, denoted by the `` component. This is, as we saw in the simple example, completely optional. 97 | 98 | Express middleware is painless to use through the `` component. The middleware will be mounted on the route which the middleware component is a child of. Simply pass in a middleware function as the `use` prop. `Favicon` and `Static` middleware components ship with React-response. They are simple wrappers for the generic middleware component. 99 | 100 | The `` component is where all the action happens. It receives your template component as a prop and the thing you want to render as a child. If you simply pass your app root component as a child to Response, Response will automatically render it to a string with ReactDOM. If you pass a function instead, it will be called with some props from the handler, as well as the request and response data from Express. This is called a custom render function. 101 | 102 | The return value from your custom render function should be a map of props that will be applied to your template component. This is important! 103 | 104 | React-response ships one handler, `simpleResponse`. SimpleResponse will be used by default. Both modules export a factory function which should be called to produce the handler itself. This is your chance to supply additional props to the component that will be rendered! The reactRouterResponse factory expects your router config as its argument which will be used to serve your app. The simpleResponse factory simply splats any object you supply onto the rendered component. 105 | 106 | To illustrate this, an example of the simpleResponse: 107 | 108 | ```javascript 109 | 110 | 111 | 112 | /* EQUALS */ 113 | 114 | { renderProps => ({ 115 | component: ReactDOM.renderToString() 116 | }) } 117 | 118 | ``` 119 | 120 | The custom render function in the above example will receive `{ foo: "bar" }` as the `renderProps` argument. If you simply pass your root component as Response's child, the renderProps will be applied to it. 121 | 122 | This is not very useful in the case of the `simpleResponse`. If you use `reactRouterResponse` (from the `react-response-router` package), you give your route config to the factory and the handler outputs `renderProps` from React-router. An example: 123 | 124 | ```javascript 125 | 126 | 127 | 128 | /* EQUALS */ 129 | 130 | { renderProps => ({ 131 | component: ReactDOM.renderToString() 132 | }) } 133 | 134 | ``` 135 | Note that `` will initially complain about missing props as you start the server if you do not give it the renderProps right away. This is OK and won't hinder the functionality of your app. 136 | 137 | Again, remember to return *a map of props for the template component*. The simple Html skeleton that ships with React-response expects your stringified app as the `component` prop, as illustrated in the examples. 138 | 139 | The whole React-response setup is fed into the `createServer` function. It compiles a fully-featured Express server from the components which you can feed to the `serve` function. 140 | 141 | ### A note on JSX 142 | 143 | I know that some developers are not fond of JSX and prefer vanilla Javascript. My decision to use JSX and even React components for server configuration is bound to raise eyebrows. For me it comes down to preference, usability and how it looks. React-response is all about eliminating React boilerplate and easing the cognitive load of writing a server-side rendering server, so the last thing I wanted was a declarative and messy configuration. 144 | 145 | The very first thing I did with React-response was to design the user interface of the configuration. While the data is not naturally nested like React Router's routes, I feel that using JSX and React components to build the server configuration gives React-response a distinct "React identity". Rendering React components on the server should just be a matter of composing them into the server configuration. It is also very easy to see what is going on from a quick glance at the configuration tree, and in my opinion it is much better than plain Javascript objects. 146 | 147 | However, if you do not wish to use JSX, inspect the output of `createServer`. It should be rather simple to re-create that object structure without JSX. Note that the components, like `Middleware` and `Response`, directly apply middleware and handlers to the Express instance. 148 | 149 | Rest assured that I plan to fully support usage of React-response without React components, à la React Router. It just isn't a priority for the first few releases. 150 | 151 | # Getting started 152 | 153 | First, install React-response: 154 | `npm install --save react-response` 155 | 156 | Make sure you have React-response's peerDependencies (react react-dom express) installed. Also install any middleware you want to use through the components. 157 | 158 | If you are using React-router, install the [React-router response handler](https://github.com/danieldunderfelt/react-response-router) for React-response. This can be achieved with: 159 | 160 | `npm i react-response-router --save` 161 | 162 | The full example above uses it. 163 | 164 | Then, follow the examples above to set up your server config. When done, feed the config to `createServer` and the output of `createServer` into `serve`. 165 | 166 | Before unleashing `node` on your server file, keep in mind that you need Babel to transpile the JSX. I suggest using `babel-core/register` to accomplish this if you do not have transpiling enabled for your server-side code. Like this: 167 | 168 | ```javascript 169 | // server.babel.js 170 | require('babel-core/register') 171 | require('server.js') // Your server file 172 | ``` 173 | 174 | Then run that file with Node. 175 | 176 | When run, React-response will output the URL where you can see your app. By default that is `http://localhost:3000`. 177 | 178 | # How do I... 179 | 180 | React-response was never meant to cover 100% of all use cases. I made it according to how I write universal React apps, but I have seen some projects that are simply out of React-response's scope. I am aiming for 80-90% of use cases. That said, React-response is quite accommodating as you can make your own response handlers and rendering functions. Also, many things seen in the `server.js` file of various projects can be moved elsewhere, for example into the root component. The server should only render your app into a template and send the response on its way. The rest can be accomplished elsewhere. 181 | 182 | ### ... use Redux: 183 | 184 | Easily! You need a custom render function where you configure your store, set up the `` and do data fetching. An example: 185 | 186 | ```javascript 187 | // server.js 188 | 189 | { (renderProps, req, res) => { 190 | // If you use react-router-redux, create history for its router state tracking. 191 | const history = createMemoryHistory(req.url) 192 | 193 | // The function that returns your store 194 | const store = configureStore({ initialState: {}, history }) 195 | 196 | // You can also use a Root component that composes the Provider and includes your DevTools. 197 | const component = ( 198 | 199 | 200 | 201 | ) 202 | 203 | // Return props for the template 204 | return { 205 | component, 206 | store 207 | } 208 | }} 209 | 210 | 211 | // YourTemplate.js 212 | 213 | class Html extends Component { 214 | 215 | render() { 216 | const {store, component} = this.props 217 | // First, render your app to a string. See, no need to do even this in server.js! 218 | const content = component ? ReactDOM.renderToString(component) : '' 219 | 220 | return ( 221 | 222 | 223 | 224 | 225 | 226 | // Plop in your app 227 |
228 | // Get the state from your store and put it into the template serialized: 229 |