How to host a serverless static website on AWS with API Gateway

Sample Website

For illustration I will use a starter vuepress site. But you can also use React, Angular or any other framework.

yarn create vuepress-site

API Gateway and S3 Setup

Now comes the interesting part. I am using cdk to setup an S3 Bucket that holds the static website and a Rest API in API Gateway with the according resources that will get the right files through the S3 Proxy integration. To keep it readable the code samples are not complete. But you can find the full code at github.

S3 Bucket:

This part is straight forward. We create a new bucket and deploy our static assets. The cool thing about the BucketDeployment Construct is that it already sets the right Content-Type for us in S3.

const bucketWebsite = new s3.Bucket(this, "WebsiteBucket");// This will deploy the sources to the destination bucket
new s3Deploy.BucketDeployment(
this, "deploy-frontend", { sources: [ s3Deploy.Source.asset("../docs/src/.vuepress/dist") ], destinationBucket: bucketWebsite });

API Gateway Rest API:

The Rest API is mostly standard. The most important part here is to define the supported Binary Media Types so that we can forward them from S3 to the Browser.

const api = new apigateway.RestApi(  this,  "ApiGatewayS3Proxy",  {    restApiName: "StaticWebsite",    // The regional endpoint is faster to deploy as it does not create a CloudFront distribution    endpointTypes: [apigateway.EndpointType.REGIONAL],

// We need to configure the supported binary media types so that they are forwarded from S3 through API Gateway to the Browser
binaryMediaTypes: [ "application/javascript", "image/png", "image/jpeg", "application/font-woff2", "application/font-woff", "font/woff", "font/woff2", ], });

API Gateway S3 Integration:

In the S3 integration we configure through responseParameters which headers should be forwarded from S3 to the browser. The code below maps all requests to / to the index.html int the S3 Bucket.

const indexPageIntegration = new apigateway.AwsIntegration({  service: "s3",  integrationHttpMethod: "GET",  path: `${bucket.bucketName}/index.html`,  options: {    credentialsRole: apiGatewayS3ReadRole,    passthroughBehavior: PassthroughBehavior.WHEN_NO_MATCH,    integrationResponses: [    {      statusCode: "200",      responseParameters: {        "method.response.header.Content-Type": "integration.response.header.Content-Type",        "method.response.header.Timestamp":  "integration.response.header.Date"      },    },  ]},});const methodOptions: MethodOptions = { methodResponses: [  { statusCode: '200', responseParameters:   {"method.response.header.Content-Type": true,  "method.response.header.Timestamp": true}},  { statusCode: '400' },  { statusCode: '500' }]};// we add a GET method to the root resource. api.root.addMethod("GET", indexPageIntegration, methodOptions);
const assetsIntegration = new apigateway.AwsIntegration({  service: "s3",  integrationHttpMethod: "GET",  path: `${bucket.bucketName}/assets/{path}`,  options: {    credentialsRole: apiGatewayS3ReadRole,    passthroughBehavior: apigateway.PassthroughBehavior.WHEN_NO_MATCH,    requestParameters: {      "integration.request.path.path": "method.request.path.path"    },    integrationResponses: [    {      statusCode: "200",      responseParameters: {        "method.response.header.Content-Type": "integration.response.header.Content-Type",        "method.response.header.Timestamp": "integration.response.header.Date"      },    },  ]},});const assets = root.addResource("assets");assets.addResource("{path+}")
.addMethod("GET", assetsIntegration, {...methodOptions, requestParameters: {"method.request.path.path": true}});



  • Faster deployment (without CloudFront)
  • Can be deployed without any resources in us-east-1 (without CloudFront)
  • very flexible and lots of features (Throttling, API Keys, Custom Domain …)
  • same API Gateway can be used for Backend routes
  • Authentication can be added easily with API Gateway Authorizers ( Cognito Integration, IAM or Custom Authorizer )


  • Compared to CloudFront API Gateway can get very expensive if you receive a lot of requests (millions), so handle with care
  • The routing configuration can get quite big depending how your static site is structured.


We managed to host a serverless static website in AWS without involving CloudFront. For high traffic websites this might not be very useful. But with special requirements it makes sense.



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store