Contact Us
Thought Leadership

A Serverless React Application Served Via AWS S3

November 26, 2020

Brief:

Build and deploy a serverless multi-page React application on AWS S3.

Summary/Introduction:

Using S3 to host a static website can be a very powerful solution due to his automatic scalability and high availability. This is thanks to the Amazon robust infrastructure. Although, building websites with modern frameworks like React that can be deployed on S3 can be a little complicated due to some intrinsic S3 limitations like dynamic routing.

In this article you will learn how to create and deploy a React application with multiple pages following a serverless approach with AWS S3 service.

Content:

Amazon Simple Storage Service (S3) allows you to host a static website with a serverless strategy. This means you don't have to provision servers. S3 provides high availability thanks to the Amazon robust infrastructure, it scales automatically to handle millions of requests at a very low cost.

One of the most popular frameworks to build front-end web applications (websites) is React, a component-based Javascript library that makes it easier to build complex user interfaces by allowing you to split it into small encapsulated components that manage their own state but that combined makes your complex UI work.

React applications are commonly built into a single Javascript file that is then referenced in a HTML page, from there your whole applications loads when the user first accesses it. Commonly, complex UI contains several pages, React handles this by rendering different components based on the URL path that you are trying to view.

This approach works fine when you are using a web server, you can simple forward all the routes into the same root file with your React application and React will take care of rendering the correct component based on that path. Unfortunately, this won't work when using S3 to serve an application. S3 will try to find the html file for whatever path we try to reach, this means we need to split the React application into multiple files.

In order to accomplish this, we are going to bundle our React application using Webpack. Webpack takes care of bundling the code of the whole application, along with the dependencies referenced in it, into a single Javascript file. We can configure it to create a bundle for each root component of every page we want. Application structure looks like this:

  • dist
  • src
    • Components
    • Pages
      • index-page.jsx
      • video-page.jsx
  • templates
  • .babelrc
  • .eslintrc.js
  • package.json
  • Webpack.config.js

 

dist:this is where webpack will output the bundled application.

src:the source code of the React application. This looks like any other React application but instead of calling ReactDOM.render() in a single root component, we will do so for each page component.

templates: these are HTML files used by Webpack to include the bundled JS files. It is also a good place to import external assets (fonts, styles, etc.). So, if we need to import an asset that is only going to be used in one of the pages of our application, we can create a specific template for that page. Whit this method, we can be certain that other pages won’t load that asset.

.babelrc, .eslintrc.js, package.json: these files will have a similar configuration as any other React application.

webpack.config.js: and finally, here is where we configure multiple bundled files as outputs.

This is the configuration of the Webpack file:

// First import the required dependencies.
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin')

// We define a global configuration for all the pages in our application.
const config = {
    module: {
        // Here we define which loaders to use, in this case with only need babel-loader,
        // test defines which files apply to that loader and exclude is the opposite.
        rules: [
            {
                test: /.(js|jsx)$/,
                exclude: /node_modules/,
                use: ['babel-loader']
            }
        ]
    },
    resolve: {
        // This are the extensions of the files to resolve
        // when using things like 'import ... from'.
        extensions: ['*', '.js', '.jsx']
    },
    // The configuration for the webpack devServer,
    // contentBase is the path where the dev server is going to run,
    // hot means is going to re-build the app every time it detect changes,
    // this only works if we are using the hot module plugin.
    devServer: {
        contentBase: './dist',
        hot: true
    },
    // Finally we define which plugins to use along with webpack,
    // For our global configuration we define the hot module plugin.
    plugins: [
        new webpack.HotModuleReplacementPlugin()
    ]
};

// In order for webpack to create a bundle for each of our pages we need
// to define a configuration for each page, extending from our global configuration.
const indexConfig = Object.assign({}, config, {
    // Name for the page.
    name: 'index',
    // Path to the root component of this page (react).
    entry: './src/pages/index/index.jsx',
    // The output is where our bundled code is going to be deployed at.
    output: {
        // The absolute path to where the app is going to be build.
        path: __dirname + '/dist',
        // This is the path where resources referenced within our app are
        // going to be looked at, in production you can use this to be your cdn.
        publicPath: '/',
        // The name of the bundled javascript file.
        filename: 'index-bundle.js'
    },
    // Finally we need to define plugins, again, so first do a destructure of the global
    // configuration and then we can add any other plugin, in this case we only need,
    // Html webpack plugin, which will generate an html file based on a template with
    // the bundled javascript imported to it.
    plugins: [
        ...config.plugins,
        new HtmlWebpackPlugin({
            title: 'Home Page',
            template: 'templates/basic.html',
            filename: 'index.html',
        }),
    ],
});

// Similar configuration for this page as well.
const videoConfig = Object.assign({}, config, {
    name: 'video',
    entry: './src/pages/video/video.jsx',
    output: {
        path: __dirname + '/dist',
        publicPath: '/',
        filename: 'video-bundle.js',
    },
    plugins: [
        ...config.plugins,
        new HtmlWebpackPlugin({
            title: 'Video Page',
            template: 'templates/video.html',
            filename: 'video.html',
        }),
    ],
});

// That's it, you can export the configuration now. For each new page in the app,
// you need to add the configuration to this array.
module.exports = [
    indexConfig,
    videoConfig,
];

 

This is an example of a configuration for an application with two pages, In case you want to include more, just follow the same pattern.

To build the application just run: "webpack –p config ./webpack.config.js" in the root folder of the application, this will output the result into the "dist" folder.

Now you can put all the content in the "dist" folder into a S3 bucket and configure it as a static website.

Conclusion

Using S3 as a serverless static website provider give us some great advantages over traditional solutions. It is a very cheap service and provides automatic scaling and a very high availability infrastructure.

Further, we can enhance the dynamic contact of the website with extre serverless solution in AWS like combining API Gateway + Lambda + Dynamo to expose a restful service that can by consumed by the website. This allows us to save and display dynamic data. Also, we could use Cognito to handle user authentication for example.

Related Insights

We are results-driven with a focus on providing customer service excellence

IO Connect Services is here to help you by offering high-quality cloud technology solutions.
Connect with us
®2024 IO Connect Services
Privacy PolicyCookie Policy
magnifiercrossmenuchevron-down
linkedin facebook pinterest youtube rss twitter instagram facebook-blank rss-blank linkedin-blank pinterest youtube twitter instagram