Lakshman Diwaakar Lakshman Diwaakar - 11 days ago 6
React JSX Question

Deploying react-redux app in AWS S3

I have gone through lot of similar questions in stack overflow like this one. Each one have different perspective of deploying the react app.

But this question is for those who use redux, react-router and react-router-redux. It took me lot of time to figure out the proper ways to host Single page application. I am aggregating all the steps here.

I have written the below answer that contains the steps to deploy react-redux app to S3.

Answer

There is a video tutorial for deploying react app to s3. This will be easy for deploying the app as such on s3 but we need to do some changes in our react app. Since it is difficult to follow, I am summarising as steps. Here it goes:

  1. Amazon AWS -> s3 -> create bucket.
  2. Add bucket policy, so that everyone can access the react app. click created bucket -> properties -> permissions. In that click Edit bucket policy. Here is an example for the bucket policy.

    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "Allow Public Access to All Objects",
                "Effect": "Allow",
                "Principal": "*",
                "Action": "s3:GetObject",
                "Resource": "arn:aws:s3:::<YOUR_BUCKET_NAME>/*"
            }
        ]
    }
    
  3. If you want logs, create another bucket and set the bucket name under logs section in properties.

  4. You need to have an index.html(which is the starting point of your app) and error.html and all the public assets(browserified reactJS app and other CSS, JS, img files) in a folder.

  5. Make sure you have relative reference to all these public files in index.html.

  6. Now, navigate to properties -> static website hosting. Enable the website hosting option. Add the index.html and error.html to the fields respectively. On saving, you will receive the Endpoint.

  7. Finally, your react-redux app is deployed. All your react routes will work properly. But when you reload the S3 will redirect to that specific route. As no such routes are already defined, it renders error.html

  8. We need to add some redirection rules under static web hosting. So, when 404 occurs, S3 needs to redirect to index.html, but this can be done only by adding some prefix like #!. Now, when you reload the page with any react route, instead of going to error.html, the same url will be loaded but with a prefix #!. Click Edit redirection rules and add the following code:

    <RoutingRules>
        <RoutingRule>
            <Condition>
                <HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
            </Condition>
            <Redirect>
                <HostName> [YOUR_ENDPOINT] </HostName>      
                <ReplaceKeyPrefixWith>#!/</ReplaceKeyPrefixWith>
            </Redirect>
        </RoutingRule>
    </RoutingRules>
    
  9. In react, we need to remove that prefix and push the route to original route. Here is the change you need to do in react app. I have my main.js file, which is the starting point of react app. Add a listener to browserHistory like this:

    browserHistory.listen(location => {
      const path = (/#!(\/.*)$/.exec(location.hash) || [])[1];
      if (path) {
          history.replace(path);
       }
     });
    
  10. The above code will remove the prefix and push the history to the correct route (HTML 5 browser history). For eg: something like this http://s3.hosting/#!/event will change to http://s3.hosting/event. Doing that makes react-router to understand and change the route accordingly. Here is my main.js file for better understanding:

    import React from 'react';
    import ReactDOM from 'react-dom';
    import {createStore, compose, applyMiddleware} from 'redux';
    import {Provider} from 'react-redux'
    import {Router, Route, IndexRoute, browserHistory, useRouterHistory} from 'react-router';
    import {syncHistoryWithStore} from 'react-router-redux';
    import createLogger from 'redux-logger';
    import thunk from 'redux-thunk';
    
    
    const logger = createLogger();
    const createStoreWithMiddleware = applyMiddleware(thunk, logger)(createStore);
    const store = createStoreWithMiddleware(reducers);
    const history = syncHistoryWithStore(browserHistory, store);
    
    
    browserHistory.listen(location => {
      const path = (/#!(\/.*)$/.exec(location.hash) || [])[1];
      if (path) {
          history.replace(path);
       }
     });
    

So uploading the browserified or webpacked react app(app.js file) will do the required need. Since I found difficult to gather all the stuff, I have aggregated all these as steps.