Step-by-Step Guide: Deploy React App on AWS with NGINX

kietHTkietHT
7 min read

Here's a step-by-step process that outlines how to do it. The process involves building your React app, setting up an EC2 instance on AWS, installing NGINX, and configuring it to serve your React app.

Steps for Deploying a React Application on AWS using NGINX

1. Build Your React Application

Before deploying, you need to create a production-ready version of your React app.

  1. Navigate to your project folder in the terminal.

  2. Run the following command to create a production build of your React app:

     npm run build
    

    This will generate a build/ directory containing the static files that can be served by NGINX.

2. Set Up an EC2 Instance on AWS

  1. Log in to your AWS Management Console and go to the EC2 Dashboard.

  2. Launch a new EC2 instance:

    • Choose an Amazon Machine Image (AMI), such as Amazon Linux 2 or Ubuntu.

    • Choose an instance type (e.g., t2.micro for free tier).

    • Set up a Security Group with HTTP (80) and SSH (22) ports open.

    • Choose or create a key pair to access the EC2 instance.

  3. After the instance is up and running, connect to it using SSH:

     ssh -i /path/to/your-key.pem ec2-user@your-ec2-public-ip
    

3. Install NGINX on the EC2 Instance

  1. Update the package manager:

     sudo yum update -y    # For Amazon Linux 2
    
  2. Install NGINX:

     sudo yum install nginx -y    # For Amazon Linux 2
    
  3. Start NGINX:

     sudo systemctl start nginx
     sudo systemctl enable nginx   # Ensures NGINX starts on boot
    
  4. Open a browser and visit the EC2 public IP to confirm that NGINX is running. You should see the default NGINX welcome page.

4. Upload Your React Build to the EC2 Instance

Now, you need to upload the production build files from your local machine to your EC2 instance.

  1. Use SCP to copy the build files:

     scp -i /path/to/your-key.pem -r build/ ec2-user@your-ec2-public-ip:/home/ec2-user/
    

    Alternatively, you can use AWS S3 to upload your build files and then transfer them to your EC2 instance.

5. Configure NGINX to Serve the React Application

  1. Move the build files to NGINX’s default directory:

     sudo mv /home/ec2-user/build/* /usr/share/nginx/html/
    
  2. Update NGINX configuration (optional):

    • Open the NGINX configuration file:

        sudo vi /etc/nginx/nginx.conf
      
    • Make any necessary adjustments, such as updating the root directory or adding custom server block configurations.

  3. Restart NGINX to apply the changes:

     sudo systemctl restart nginx
    

6. Verify Deployment

Now, when you visit the public IP of your EC2 instance, you should see your React application being served by NGINX.

7. Optional: Set Up a Domain Name (with Route 53)

If you want to serve your application using a custom domain name:

  1. Set up Route 53 in AWS to manage your DNS records.

  2. Add an A record pointing to your EC2 instance’s public IP.

8. Secure Your Application with HTTPS (SSL/TLS)

  1. Install Certbot and get a free SSL certificate from Let's Encrypt:

     sudo yum install certbot -y    # For Amazon Linux 2
     sudo certbot --nginx
    
  2. Configure automatic certificate renewal:

     sudo certbot renew --dry-run   # Test auto-renewal
    

Summary

This process sets up NGINX on an EC2 instance to serve your React application. You can scale this approach, using tools like AWS S3 for hosting static files or Elastic Load Balancer (ELB) for larger-scale applications. If you want more advanced features like CI/CD pipelines or auto-scaling, you can integrate services like AWS CodePipeline or Elastic Beanstalk.

Many things you should pay attention to:

When building a React application for deployment, especially in a production environment like AWS, there are several things to pay attention to in order to ensure optimal performance, security, and scalability. Here are some important considerations:

1. Optimization for Production

  • Minification & Bundling: React automatically minifies and bundles the JavaScript files when you run npm run build. However, it’s good to double-check that you are running the production build to ensure your app is optimized.

    • Run npm run build to create a production-ready build that is minified, optimized, and ready for deployment.
  • Code Splitting: Code splitting helps load only the necessary code for a page, which improves load times. React supports this out of the box, especially with React.lazy and Suspense.

    • Example:

        const MyComponent = React.lazy(() => import('./MyComponent'));
      
  • Tree Shaking: Ensure that unused code is removed from the final build by utilizing tree shaking, which is enabled by default in tools like Webpack.

  • Service Workers: You can enable service workers for offline capabilities (if required), but be mindful of how they work and make sure to update them properly to avoid issues with caching old versions of your app.

2. Environment Variables

  • Sensitive Data: Ensure you don’t expose sensitive information like API keys or credentials in your build.

    • Use environment variables (.env) to store values that can be different between development and production.

    • Use .env.production for production-specific variables.

Example:

    REACT_APP_API_URL=https://your-api.com
  • Environment-Specific Settings: React’s build process uses .env files. Ensure that production values are set correctly, such as enabling caching, analytics, and production API endpoints.

3. Caching and Versioning

  • Cache-Control Headers: Set up proper cache headers on your server (NGINX) to ensure assets like JavaScript, CSS, and images are cached properly. This will improve load times and reduce unnecessary network requests.

    • Example of setting cache headers in NGINX:

        location /static/ {
            expires 30d;
            add_header Cache-Control "public, max-age=2592000, immutable";
        }
      
  • Versioning Your Files: React’s build process automatically appends a unique hash to your filenames (like main.a1b2c3.js). This helps ensure the browser fetches the latest version of your files after deployment.

4. Security Best Practices

  • Content Security Policy (CSP): Set a CSP header to protect against XSS (Cross-Site Scripting) attacks by specifying which domains are allowed to execute scripts, load images, etc.

    • Example:

        add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://trusted-scripts.com;" always;
      
  • HTTP Headers: Use additional HTTP headers for added security, such as:

    • Strict-Transport-Security (HSTS): Enforces HTTPS.

    • X-Content-Type-Options: Prevents browsers from interpreting files as a different MIME type.

    • X-Frame-Options: Prevents your app from being embedded in an iframe (Clickjacking protection).

Example of configuring NGINX headers:

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;

5. Handling Errors Gracefully

  • Error Boundaries: In React, use Error Boundaries to catch JavaScript errors in the component tree and display a fallback UI.

      class ErrorBoundary extends React.Component {
          constructor(props) {
              super(props);
              this.state = { hasError: false };
          }
    
          static getDerivedStateFromError(error) {
              return { hasError: true };
          }
    
          componentDidCatch(error, info) {
              logErrorToMyService(error, info);
          }
    
          render() {
              if (this.state.hasError) {
                  return <h1>Something went wrong.</h1>;
              }
              return this.props.children;
          }
      }
    
  • Logging & Monitoring: Set up logging for both front-end and back-end (if applicable). For React, you can use services like Sentry for error monitoring in production.

6. Optimizing Images and Assets

  • Image Optimization: Optimize images before deploying. Tools like ImageOptim, TinyPNG, or responsive images (using <picture> tags) can help reduce load times.

  • Lazy Load Images: For non-critical images, consider lazy loading them to improve initial load times.

      <img src="image.jpg" loading="lazy" alt="Description" />
    

7. CDN (Content Delivery Network)

  • Use a CDN for Static Assets: AWS CloudFront or other CDN services can improve the delivery speed of your static assets globally by caching them at edge locations.

8. Access Control and Permissions

  • Restrict Access to API Endpoints: If your app is accessing APIs or sensitive data, ensure that the APIs have proper authentication and authorization in place (JWT, OAuth, etc.).

  • Set Correct Permissions on AWS S3 or any other storage services, ensuring your React app can only access necessary resources and not leak information.

9. Scaling Considerations

  • Auto Scaling: Consider setting up Elastic Load Balancers (ELB) with EC2 instances or Elastic Beanstalk for automatic scaling if your app grows in terms of traffic.

  • Serverless: You could also explore deploying the front-end as a serverless application using services like AWS Amplify, which automates deployment, scaling, and hosting.

10. Continuous Deployment (CD)

  • Set up a CI/CD pipeline to automatically deploy your app whenever changes are made to the codebase. This can be done with AWS CodePipeline, GitHub Actions, or other CI/CD tools.

Example for deploy:

GitHub Actions for Deployment

You can use GitHub Actions to automate the build and deployment process.

Here’s an example of a basic GitHub Actions workflow for deploying a React app to AWS S3 (for static hosting):

name: Deploy to AWS S3
on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v2

    - name: Set up Node.js
      uses: actions/setup-node@v2
      with:
        node-version: '14'

    - name: Install dependencies
      run: npm install

    - name: Build app
      run: npm run build

    - name: Deploy to S3
      run: aws s3 sync ./build/ s3://your-bucket-name --delete
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        AWS_REGION: 'us-east-1'

Conclusion

Before building and deploying, ensuring your app is optimized for performance and secure is crucial. Following the best practices for building React applications, securing your deployment environment, and setting up monitoring and logging will help ensure that your app performs well, is secure, and provides a good user experience.

0
Subscribe to my newsletter

Read articles from kietHT directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

kietHT
kietHT

I am a developer who is highly interested in TypeScript. My tech stack has been full-stack TS such as Angular, React with TypeScript and NodeJS.