Lightweight boilerplate project to set up a React 17 web application on AWS Lambda using the Serverless Framework.
npx sls deploy
.redux
, react-router
, sass
, less
or any other 3rd party dependency for full flexibility.Looking for the plain JavaScript version of this boilerplate?
The idea is that we use AWS Lambda to serve the dynamic part of our app, the server-side logic, and perform the server-side rendering. For all static data like images, stylesheets, and even the app's index.tsx
that is loaded in the browser, we use an S3 bucket for public hosting.
This combination makes our app fast and incredibly scalable. AWS will spin up new Lambda instances once your number of users increases, handling even the largest spikes fully automatically while incurring virtually no costs when your app isn't used. At the same time, S3 provides a robust and fast platform for your static content so you don't have to waste your own computing resources.
All resources, including the S3 bucket for hosting static content, are created and configured automatically when your app is deployed the first time. You can make changes to the default setup by updating your serverless.yml
to your linking.
serverless-react-boilerplate/
│
├── public/ - Public assets which will retain their original file names and folder structure
│ ├── favicon.ico - Favicon
│ └── manifest.json - Web page manifest
│
├── src/
│ ├── browser/
│ │ └── ... - Client-side code running in the browser as well as during server-side rendering
│ ├── components/
│ │ └── ... - React components
│ ├── server/
│ │ └── ... - Server-side code running on AWS Lambda
│ ├── App.tsx - The web application's root component.
│ └── ... - Other files used by the application
│
├── handler.ts - AWS Lambda function handler
├── serverless.yml - Project configuration
├── babel.config.js - Babel configuration
├── jest.config.js - Jest configuration
├── webpack.browser.config.js - Webpack configuration for client-side code
├── webpack.server.config.js - Webpack configuration for the Lambda backend
└── ...
The project is based on the Serverless Framework and makes use of several plugins:
main.js
.Though we use the same source code for both the server-side and browser rendering, the project will be packaged into two distinct bundles:
./src/server/render.tsx
. It contains the handler function that is invoked by AWS Lambda. The packaging is controlled by webpack.server.config.js
and optimized for Node.js 12../src/browser/index.tsx
. It's packaged using the webpack.browser.config.js
, optimized for web browsers. The output files will have their content hash added to their names to enable long-term caching in the browser.webpack.browser.config.js
defines some default code-splitting settings that optimize browser loading times and should make sense for most projects:
src/components
folder) are loaded in a separate components.js
chunk.node_modules/
folder) are loaded in the vendor.js
chunk. External modules usually don't change as often as the rest of your application and this split will improve browser caching for your users.main.js
chunk.Update the serverless.yml
with your project name and additional resources you might need. For example, you might want to create a custom domain name for your app.
The frontend, as well as the server-side code running on AWS Lambda, share a common application configuration. Currently, it is used for injecting the application name from the public/manifest.json
as well as setting the public hostnames. You can extend the configuration by adding your own variables to src/server/config.tsx
. They will become available in both your backend and frontend code via the useConfig
hook:
import useConfig from "../components/useConfig";
export default function MyComponent() {
const config = useConfig();
return (
// ...
)
}
The boilerplate comes with a preferred folder structure for your project. However, you can change it to your liking. If you decide to do so, make sure to update the respective Webpack and Serverless configurations to point to the new locations.
Generally, you shouldn't need to touch the contents of the src/browser/
and src/server/
folders, with exception of updating the configuration. Common components shared across your React site should go into the src/components/
folder. It currently contains only the ConfigContext
provider and the useConfig
hook implementation. Code splitting has already been configured to package these shared components separately from the rest of your application. You might want to place individual web pages or screens of your application into subfolders directly underneath src/
or next to App.tsx
.
Images can be loaded directly from the src/
folder as demonstrated in App.tsx
. Webpack will automatically manage your images, ensure they use a unique file name and are loaded either from S3 or get embedded directly into the generated HTML if they are small enough. The public/
folder on the other hand is used for static assets that should retain their original file names and folder structure. All content of this folder will be uploaded to S3 exactly 1:1 and served from there. It is the perfect place to put your favicon.ico
, robots.txt
, and similar static assets that you need to reference by a fixed unchanging URL.
I recommend creating a separate Serverless service that provides the frontend with an API and protecting it with Amazon Cognito, a custom Authorizer, or even just an API Key. Mixing React with pure backend API functions is possible and technically fine, but in my experience, it quickly becomes a hassle and you need to take special care not to leak anything to the browser that's not supposed to be known there.
The goal of this boilerplate is to offer a minimal setup that can be used as a basis for pretty much all your React needs. A lot of people love Redux, rely on React Router or need other external modules. I have intentionally left these out of the boilerplate code but it should be trivial to add them, following the standard documentation steps.
If you are interested in integrating with React Router, check out out the Added React Router example configuration Pull Request.
Similar to the statement above, I have decided against integrating with a specific framework. The boilerplate uses plain and simple CSS and integrating another system should be easy enough.
To keep this repository lightweight no ESLint rules are included. There are many different plugins and people tend to prefer different coding styles. The existing code should be easily adaptable to any style you personally prefer. I recommend using Prettier to format your code automatically and a default configuration is already part of this repository, defined in package.json
. In addition, I recommend adding ESLint and Husky to your project to ensure your coding guidelines are followed.
You can test the setup locally. No direct access to AWS is needed. This allows developers to write and test code even if not everyone has full deployment access.
For local testing run the following command and open your web browser at http://localhost:3000/. Static content such as images will be served via the Webpack DevServer running on http://localhost:8080. Note that the app has to be deployed first before you will be able to run locally.
npm start
Testing is set up as well, using Jest and will execute all *.test.ts
and *.test.tsx
files in the src/
directory:
npm test
The whole application can be deployed with a single command:
npx sls deploy
And finally to remove all AWS resources again run:
npx sls remove
This will delete all resources but the distribution S3 bucket. As it still contains the bundles you will have to delete the bucket manually for now.
App.tsx
and related files have been moved out for the browser
folder directly into src
as it is used by both the server and the browser rendering. You should rarely need to touch the contents of the browser
or the server
folder.handler.ts
to add your own custom error handling code such as Youch.