Skip to content

Instantly share code, notes, and snippets.

@swport
Last active March 13, 2025 15:13
Show Gist options
  • Save swport/dea1a666ced323b6c5c2faf94eeef90e to your computer and use it in GitHub Desktop.
Save swport/dea1a666ced323b6c5c2faf94eeef90e to your computer and use it in GitHub Desktop.
Deployment steps to deploy simple react app to s3 bucket on aws

To create a React-TypeScript application, deploy it to an S3 bucket, and make it publicly accessible using the AWS SDK, follow these steps:


1. Set Up a React-TypeScript Application

  1. Create a React-TypeScript app using create-react-app:

    npx create-react-app my-react-ts-app --template typescript
    cd my-react-ts-app
  2. Run the app locally to ensure it works:

    npm start
  3. Build the app for production:

    npm run build

    This generates a build folder containing the production-ready files.


2. Set Up AWS SDK

  1. Install the AWS SDK for JavaScript:

    npm install aws-sdk
  2. Configure AWS credentials:

    • Create an IAM user with the following permissions:
      • AmazonS3FullAccess
    • Generate access keys for the IAM user.
    • Configure the AWS CLI or SDK with the credentials:
      aws configure
      Provide the access key, secret key, region (e.g., us-east-1), and default output format (e.g., json).

3. Create an S3 Bucket

  1. Create an S3 bucket using the AWS SDK: Create a script (e.g., deploy.js) to create the bucket and upload files:

    const AWS = require('aws-sdk');
    const fs = require('fs');
    const path = require('path');
    
    // Configure AWS SDK
    AWS.config.update({ region: 'us-east-1' });
    const s3 = new AWS.S3();
    
    // Bucket name (must be unique globally)
    const bucketName = 'my-react-ts-app-bucket';
    
    // Function to create S3 bucket
    const createBucket = async () => {
      try {
        await s3.createBucket({ Bucket: bucketName }).promise();
        console.log(`Bucket created: ${bucketName}`);
      } catch (err) {
        console.error('Error creating bucket:', err);
      }
    };
    
    // Function to upload files to S3
    const uploadFiles = async () => {
      const buildFolder = path.join(__dirname, 'build');
      const files = fs.readdirSync(buildFolder);
    
      for (const file of files) {
        const filePath = path.join(buildFolder, file);
        const fileContent = fs.readFileSync(filePath);
    
        const params = {
          Bucket: bucketName,
          Key: file,
          Body: fileContent,
          ContentType: file.endsWith('.html') ? 'text/html' : file.endsWith('.css') ? 'text/css' : 'application/javascript',
        };
    
        try {
          await s3.upload(params).promise();
          console.log(`Uploaded: ${file}`);
        } catch (err) {
          console.error(`Error uploading ${file}:`, err);
        }
      }
    };
    
    // Run the deployment
    (async () => {
      await createBucket();
      await uploadFiles();
    })();
  2. Run the script:

    node deploy.js

4. Make the S3 Bucket Publicly Accessible

  1. Enable static website hosting: Update the bucket policy to allow public read access:

    const makeBucketPublic = async () => {
      const bucketPolicy = {
        Version: '2012-10-17',
        Statement: [
          {
            Sid: 'PublicReadGetObject',
            Effect: 'Allow',
            Principal: '*',
            Action: 's3:GetObject',
            Resource: `arn:aws:s3:::${bucketName}/*`,
          },
        ],
      };
    
      const params = {
        Bucket: bucketName,
        Policy: JSON.stringify(bucketPolicy),
      };
    
      try {
        await s3.putBucketPolicy(params).promise();
        console.log('Bucket policy updated to public.');
      } catch (err) {
        console.error('Error updating bucket policy:', err);
      }
    };
    
    // Add this to the deployment script
    (async () => {
      await createBucket();
      await uploadFiles();
      await makeBucketPublic();
    })();
  2. Enable static website hosting: Update the bucket to host a static website:

    const enableStaticWebsiteHosting = async () => {
      const params = {
        Bucket: bucketName,
        WebsiteConfiguration: {
          ErrorDocument: {
            Key: 'index.html',
          },
          IndexDocument: {
            Suffix: 'index.html',
          },
        },
      };
    
      try {
        await s3.putBucketWebsite(params).promise();
        console.log('Static website hosting enabled.');
      } catch (err) {
        console.error('Error enabling static website hosting:', err);
      }
    };
    
    // Add this to the deployment script
    (async () => {
      await createBucket();
      await uploadFiles();
      await makeBucketPublic();
      await enableStaticWebsiteHosting();
    })();

5. Access the Application

  1. Get the public URL: The public URL for your S3-hosted website will be:

    http://<bucket-name>.s3-website-<region>.amazonaws.com
    

    Example:

    http://my-react-ts-app-bucket.s3-website-us-east-1.amazonaws.com
    
  2. Open the URL in your browser to see your React-TypeScript app.


6. Automate Deployment (Optional)

You can automate the deployment process by adding the script to your package.json:

"scripts": {
  "deploy": "npm run build && node deploy.js"
}

Run the deployment with:

npm run deploy

7. Clean Up

If you no longer need the bucket, delete it to avoid unnecessary charges:

const deleteBucket = async () => {
  try {
    await s3.deleteBucket({ Bucket: bucketName }).promise();
    console.log(`Bucket deleted: ${bucketName}`);
  } catch (err) {
    console.error('Error deleting bucket:', err);
  }
};

// Add this to the deployment script if needed
(async () => {
  await deleteBucket();
})();

This process will deploy your React-TypeScript app to an S3 bucket and make it publicly accessible.

const AWS = require('aws-sdk');
const fs = require('fs');
const path = require('path');

// Configure AWS SDK
AWS.config.update({ region: 'us-east-1' });
const s3 = new AWS.S3();

// Bucket name (must be unique globally)
const bucketName = 'my-react-ts-app-bucket';

// Function to create S3 bucket (if it doesn't exist)
const createBucket = async () => {
  try {
    await s3.createBucket({ Bucket: bucketName }).promise();
    console.log(`Bucket created: ${bucketName}`);
  } catch (err) {
    if (err.code !== 'BucketAlreadyOwnedByYou') {
      console.error('Error creating bucket:', err);
    }
  }
};

// Function to list all objects in the bucket
const listObjects = async () => {
  try {
    const data = await s3.listObjectsV2({ Bucket: bucketName }).promise();
    return data.Contents || [];
  } catch (err) {
    console.error('Error listing objects:', err);
    return [];
  }
};

// Function to move objects to a backup directory
const moveObjectsToBackup = async (objects, backupDir) => {
  for (const object of objects) {
    const copyParams = {
      Bucket: bucketName,
      CopySource: `${bucketName}/${object.Key}`,
      Key: `${backupDir}/${object.Key}`,
    };

    const deleteParams = {
      Bucket: bucketName,
      Key: object.Key,
    };

    try {
      // Copy object to backup directory
      await s3.copyObject(copyParams).promise();
      console.log(`Copied to backup: ${object.Key}`);

      // Delete original object
      await s3.deleteObject(deleteParams).promise();
      console.log(`Deleted original: ${object.Key}`);
    } catch (err) {
      console.error(`Error moving ${object.Key}:`, err);
    }
  }
};

// Function to upload new files to S3
const uploadFiles = async () => {
  const distFolder = path.join(__dirname, 'dist');
  const files = fs.readdirSync(distFolder);

  for (const file of files) {
    const filePath = path.join(distFolder, file);
    const fileContent = fs.readFileSync(filePath);

    const params = {
      Bucket: bucketName,
      Key: file,
      Body: fileContent,
      ContentType: file.endsWith('.html') ? 'text/html' : file.endsWith('.css') ? 'text/css' : 'application/javascript',
    };

    try {
      await s3.upload(params).promise();
      console.log(`Uploaded: ${file}`);
    } catch (err) {
      console.error(`Error uploading ${file}:`, err);
    }
  }
};

// Function to make the bucket publicly accessible
const makeBucketPublic = async () => {
  const bucketPolicy = {
    Version: '2012-10-17',
    Statement: [
      {
        Sid: 'PublicReadGetObject',
        Effect: 'Allow',
        Principal: '*',
        Action: 's3:GetObject',
        Resource: `arn:aws:s3:::${bucketName}/*`,
      },
    ],
  };

  const params = {
    Bucket: bucketName,
    Policy: JSON.stringify(bucketPolicy),
  };

  try {
    await s3.putBucketPolicy(params).promise();
    console.log('Bucket policy updated to public.');
  } catch (err) {
    console.error('Error updating bucket policy:', err);
  }
};

// Function to enable static website hosting
const enableStaticWebsiteHosting = async () => {
  const params = {
    Bucket: bucketName,
    WebsiteConfiguration: {
      ErrorDocument: {
        Key: 'index.html',
      },
      IndexDocument: {
        Suffix: 'index.html',
      },
    },
  };

  try {
    await s3.putBucketWebsite(params).promise();
    console.log('Static website hosting enabled.');
  } catch (err) {
    console.error('Error enabling static website hosting:', err);
  }
};

// Run the deployment
(async () => {
  try {
    // Create bucket if it doesn't exist
    await createBucket();

    // List existing objects in the bucket
    const objects = await listObjects();

    if (objects.length > 0) {
      // Create a timestamped backup directory
      const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
      const backupDir = `backup/${timestamp}`;

      // Move existing objects to the backup directory
      await moveObjectsToBackup(objects, backupDir);
    }

    // Upload new files
    await uploadFiles();

    // Make the bucket publicly accessible
    await makeBucketPublic();

    // Enable static website hosting
    await enableStaticWebsiteHosting();

    console.log('Deployment completed successfully!');
  } catch (err) {
    console.error('Deployment failed:', err);
  }
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment