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

In most cases you will use the combination of CloudFront and S3 to host a static website on AWS. But depending on the requirements you might not be able to use it.

In this Post I’m going to describe how you can use (or abuse) the relatively new S3 Integration in API Gateway to host a serverless static website. You can use it to deploy a Single Page App with React, Vue Angular or any other framework as well.

In short, I am going to use a Rest API to forward requests to an S3 Bucket. API Gateway has an awful lot of features. I am going to configure it to forward the Content-Type of the files in S3. This way the browser will be able to correctly interpret the files and render our website properly.

Sample Website

You can create the starter by running (you need yarn to be installed):

yarn create vuepress-site

This will create the vuepress project and we will leave it mostly untouched. We only need to set the base url as we will use the generated domain by API Gateway which automatically adds /prod (or the according stage). This is done by adding base: /prod/ to the file src/.vuepress/config.js.

That’s all for the sample website as this is not the main focus on this post.

API Gateway and S3 Setup

S3 Bucket:

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:

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:

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);

We can also configure catchall or proxy+ routes is the name in API Gateway. In our example this is usable for the assets folder. Important part is to define the requestParameters to forward the path param to S3.

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}});

Evaluation

Pros

  • 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 )

Cons

  • The routing configuration can get quite big depending how your static site is structured.

Summary

In a production app you will probably add a custom domain and authentication.

I used cdk to define the API but you can use any other supported method (OpenAPI, Smithy).

Freelance Web Developer