Skip to main content

StaticSite

caution

This is the SST v1.x Constructs doc. SST v2 is now released. If you are using v2, see the v2 Constructs doc. If you are looking to upgrade to v2, check out the upgrade steps.

The StaticSite construct is a higher level CDK construct that makes it easy to create a static website. It provides a simple way to build and deploy the site to an S3 bucket; setup a CloudFront CDN for fast content delivery; and configure a custom domain for the website URL. In addition:

  • Visitors to the http:// url will be redirected to the https:// URL.
  • If a domain alias is configured, visitors to the alias domain will be redirected to the main one. So if www.example.com is the domain alias for example.com, visitors to www.example.com will be redirected to example.com.

Examples

The StaticSite construct is designed to make it easy to get started with, while allowing for a way to fully configure it as well. Let's look at how, through a couple of examples.

Creating a React site

Deploys a React site created using Vite.

new StaticSite(stack, "react", {
path: "path/to/site",
buildOutput: "dist",
buildCommand: "npm run build",
environment: {
// Pass in the API endpoint to our app
VITE_API_URL: api.url,
},
});

Creating a Vue.js site

Deploys a Vue site created using Vite.

new StaticSite(stack, "vue", {
path: "path/to/site",
buildOutput: "dist",
buildCommand: "npm run build",
environment: {
// Pass in the API endpoint to our app
VITE_API_URL: api.url,
},
});

Creating a Svelte site

Deploys a Svelte site created using Vite.

new StaticSite(stack, "svelte", {
path: "path/to/site",
buildOutput: "dist",
buildCommand: "npm run build",
environment: {
// Pass in the API endpoint to our app
VITE_API_URL: api.url,
},
});

Creating a Gatsby site

new StaticSite(stack, "gatsby", {
path: "path/to/site",
errorPage: "404.html",
buildOutput: "public",
buildCommand: "npm run build",
});

Creating a Jekyll site

new StaticSite(stack, "jekyll", {
path: "path/to/site",
errorPage: "404.html",
buildOutput: "_site",
buildCommand: "bundle exec jekyll build",
});

Creating an Angular site

new StaticSite(stack, "angular", {
path: "path/to/site",
buildOutput: "dist",
buildCommand: "ng build --output-path dist",
});

Creating a CRA site

Deploys a React site created using Create React App.

new StaticSite(stack, "react", {
path: "path/to/site",
buildOutput: "build",
buildCommand: "npm run build",
environment: {
// Pass in the API endpoint to our app
REACT_APP_API_URL: api.url,
},
});

Creating a plain HTML site

Deploys a plain HTML website in the path/to/site directory.

import { StaticSite } from "@serverless-stack/resources";

new StaticSite(stack, "frontend", {
path: "path/to/site",
});

Environment variables

The StaticSite construct allows you to set the environment variables that are passed through your build system based on outputs from other constructs in your SST app. So you don't have to hard code the config from your backend.

You need to be using a build tool that supports setting build time environment variables (most do). For example, Vite supports setting build time environment variables. In your JS files this looks like:

src/App.js
console.log(import.meta.env.VITE_API_URL);
console.log(import.meta.env.VITE_USER_POOL_CLIENT);

You can pass these in directly from the construct.

new StaticSite(stack, "frontend", {
path: "path/to/site",
environment: {
VITE_API_URL: api.url,
VITE_USER_POOL_CLIENT: auth.cognitoUserPoolClient.userPoolClientId,
},
});

Where api.url or auth.cognitoUserPoolClient.userPoolClientId are coming from other constructs in your SST app.

Type definitions

If a vite.config.js file is detected in the path folder, SST also creates a type definition file for the environment variables in src/sst-env.d.ts.

/// <reference types="vite/client" />

interface ImportMetaEnv {
readonly VITE_API_URL: string;
readonly VITE_USER_POOL_CLIENT: string;
}

interface ImportMeta {
readonly env: ImportMetaEnv;
}

This tells your editor the environment variables that are available and autocompletes them for you.

Vite environment variables autocomplete

You can also override the path for the generated type definitions file.

new StaticSite(stack, "frontend", {
path: "path/to/site",
environment: {
VITE_API_URL: api.url,
VITE_USER_POOL_CLIENT: auth.cognitoUserPoolClient.userPoolClientId,
},
vite: {
types: "types/my-env.d.ts",
},
});

While deploying

On sst deploy, the environment variables will first be replaced by placeholder values, {{ VITE_API_URL }} and {{ VITE_USER_POOL_CLIENT }}, when building the Vite app. And after the referenced resources have been created, the Api and User Pool in this case, the placeholders in the HTML and JS files will then be replaced with the actual values.

While developing

To use these values while developing, run sst start to start the Live Lambda Development environment.

npx sst start

Then in your Vite app to reference these variables, add the sst-env package.

npm install --save-dev @serverless-stack/static-site-env

And tweak the Vite dev script to:

package.json
"scripts": {
"dev": "sst-env -- vite",
"build": "vite build",
"preview": "vite preview"
},

Now you can start your Vite app as usualy and it'll have the environment variables from your SST app.

npm run dev

There are a couple of things happening behind the scenes here:

  1. The sst start command generates a file with the values specified by StaticSite's environment prop.
  2. The sst-env CLI will traverse up the directories to look for the root of your SST app.
  3. It'll then find the file that's generated in step 1.
  4. It'll load these as environment variables before running the start command.
note

sst-env only works if the Vite app is located inside the SST app or inside one of its subdirectories. For example:

/
sst.json
vite-app/

Custom domains

You can configure the website with a custom domain hosted either on Route 53 or externally.

Using the basic config (Route 53 domains)

new StaticSite(stack, "frontend", {
path: "path/to/site",
customDomain: "domain.com",
});

Redirect www to non-www (Route 53 domains)

new StaticSite(stack, "frontend", {
path: "path/to/site",
customDomain: {
domainName: "domain.com",
domainAlias: "www.domain.com",
},
});

Configuring domains across stages (Route 53 domains)

new StaticSite(stack, "frontend", {
path: "path/to/site",
customDomain: {
domainName:
scope.stage === "prod" ? "domain.com" : `${scope.stage}.domain.com`,
domainAlias: scope.stage === "prod" ? "www.domain.com" : undefined,
},
});

Configuring alternate domain names (Route 53 domains)

You can specify additional domain names for the site url. Note that the certificate for these names will not be automatically generated, so the certificate option must be specified. Also note that you need to manually create the Route 53 records for the alternate domain names.

import * as acm from "aws-cdk-lib/aws-certificatemanager";
import * as route53 from "aws-cdk-lib/aws-route53";
import * as route53Targets from "aws-cdk-lib/aws-route53-targets";

// Look up hosted zone
const hostedZone = route53.HostedZone.fromLookup(stack, "HostedZone", {
domainName: "domain.com",
});

// Create a certificate with alternate domain names
const certificate = new acm.DnsValidatedCertificate(stack, "Certificate", {
domainName: "foo.domain.com",
hostedZone,
region: "us-east-1",
subjectAlternativeNames: ["bar.domain.com"],
});

// Create site
const site = new StaticSite(stack, "frontend", {
path: "path/to/site",
customDomain: {
domainName: "foo.domain.com",
alternateNames: ["bar.domain.com"],
cdk: {
hostedZone,
certificate,
},
},
});

// Create A and AAAA records for the alternate domain names
const recordProps = {
recordName: "bar.domain.com",
zone: hostedZone,
target: route53.RecordTarget.fromAlias(
new route53Targets.CloudFrontTarget(site.cdk.distribution)
),
};
new route53.ARecord(stack, "AlternateARecord", recordProps);
new route53.AaaaRecord(stack, "AlternateAAAARecord", recordProps);

Importing an existing certificate (Route 53 domains)

import { Certificate } from "aws-cdk-lib/aws-certificatemanager";

new StaticSite(stack, "frontend", {
path: "path/to/site",
customDomain: {
domainName: "domain.com",
cdk: {
certificate: Certificate.fromCertificateArn(stack, "MyCert", certArn),
},
},
});

Note that, the certificate needs be created in the us-east-1(N. Virginia) region as required by AWS CloudFront.

Specifying a hosted zone (Route 53 domains)

If you have multiple hosted zones for a given domain, you can choose the one you want to use to configure the domain.

import { HostedZone } from "aws-cdk-lib/aws-route53";

new StaticSite(stack, "frontend", {
path: "path/to/site",
customDomain: {
domainName: "domain.com",
cdk: {
hostedZone: HostedZone.fromHostedZoneAttributes(stack, "MyZone", {
hostedZoneId,
zoneName,
}),
},
},
});

Configuring externally hosted domain

import { Certificate } from "aws-cdk-lib/aws-certificatemanager";

new StaticSite(stack, "frontend", {
path: "path/to/site",
customDomain: {
isExternalDomain: true,
domainName: "domain.com",
cdk: {
certificate: Certificate.fromCertificateArn(stack, "MyCert", certArn),
},
},
});

Note that the certificate needs be created in the us-east-1(N. Virginia) region as required by AWS CloudFront, and validated. After the Distribution has been created, create a CNAME DNS record for your domain name with the Distribution's URL as the value. Here are more details on configuring SSL Certificate on externally hosted domains.

Also note that you can also migrate externally hosted domains to Route 53 by following this guide.

Caching

Configure the Cache Control settings based on different file types.

new StaticSite(stack, "frontend", {
path: "path/to/site",
buildOutput: "build",
buildCommand: "npm run build",
errorPage: "redirect_to_index_page",
fileOptions: [
{
exclude: "*",
include: "*.html",
cacheControl: "max-age=0,no-cache,no-store,must-revalidate",
},
{
exclude: "*",
include: ["*.js", "*.css"],
cacheControl: "max-age=31536000,public,immutable",
},
],
});

This configures all the .html files to not be cached by the, while the .js and .css files to be cached forever.

Note that, you need to specify the exclude: "*" along with the include option. It allows you to pick the files you want, while excluding everything else.

Advanced examples

Configuring the S3 Bucket

Configure the internally created CDK Bucket instance.

import { RemovalPolicy } from "aws-cdk-lib";

new StaticSite(stack, "frontend", {
path: "path/to/site",
cdk: {
bucket: {
removalPolicy: RemovalPolicy.DESTROY,
},
},
});

Using an existing S3 Bucket

import * as s3 from "aws-cdk-lib/aws-s3";

new StaticSite(stack, "frontend", {
path: "path/to/site",
cdk: {
bucket: s3.Bucket.fromBucketName(stack, "Bucket", "my-bucket"),
},
});

Configuring the CloudFront Distribution

Configure the internally created CDK Distribution instance.

new StaticSite(stack, "frontend", {
path: "path/to/site",
cdk: {
distribution: {
comment: "Distribution for my React website",
},
},
});

Configuring the CloudFront default behavior

The default behavior of the CloudFront distribution uses the internally created S3 bucket as the origin. You can configure this behavior.

import {
ViewerProtocolPolicy,
AllowedMethods,
} from "aws-cdk-lib/aws-cloudfront";

new StaticSite(stack, "frontend", {
path: "path/to/site",
cdk: {
distribution: {
defaultBehavior: {
viewerProtocolPolicy: ViewerProtocolPolicy.HTTPS_ONLY,
allowedMethods: AllowedMethods.ALLOW_ALL,
},
},
},
});

Using Lambda@Edge

import { Code, Runtime } from "aws-cdk-lib/aws-lambda";
import { LambdaEdgeEventType, experimental } from "aws-cdk-lib/aws-cloudfront";

const edgeFunc = new experimental.EdgeFunction(stack, "MyFunction", {
runtime: Runtime.NODEJS_16_X,
handler: "lambda.handler",
code: Code.fromAsset("path/to/dir"),
stackId: `${scope.logicalPrefixedName("edge-lambda")}`,
});

new StaticSite(stack, "frontend", {
path: "path/to/site",
cdk: {
distribution: {
defaultBehavior: {
edgeLambdas: [
{
functionVersion: edgeFunc.currentVersion,
eventType: LambdaEdgeEventType.VIEWER_RESPONSE,
},
],
},
},
},
});

Note that, Lambda@Edge functions will be created in the us-east-1 region, regardless of the region of your SST app. If the app is in us-east-1, the Lambda function is created directly in the stack. If the app is not in us-east-1, the Lambda function will be created in a new stack with the provided stackId. And the new stack will be deployed to us-east-1.

caution

On sst remove, the Lambda@Edge functions cannot be removed right away. CloudFront needs to remove the function replicas from the edge locations. This can take up to a few hours. If the stack fails to remove, simply wait for some time and retry.

Constructor

new StaticSite(scope, id, props)

Parameters

StaticSiteProps

buildCommand?

Type : string

Default : no build command

The command for building the website

new StaticSite(stack, "Site", {
buildCommand: "npm run build",
});

buildOutput?

Type : string

Default : entire "path" directory

The directory with the content that will be uploaded to the S3 bucket. If a buildCommand is provided, this is usually where the build output is generated. The path is relative to the path where the website source is located.

new StaticSite(stack, "Site", {
buildOutput: "build",
});

customDomain?

Type : string | StaticSiteDomainProps

The customDomain for this website. SST supports domains that are hosted either on Route 53 or externally. Note that you can also migrate externally hosted domains to Route 53 by following this guide.

new StaticSite(stack, "Site", {
path: "path/to/src",
customDomain: "domain.com",
});
new StaticSite(stack, "Site", {
path: "path/to/src",
customDomain: {
domainName: "domain.com",
domainAlias: "www.domain.com",
hostedZone: "domain.com"
}
});

disablePlaceholder?

Type : boolean

Default : false

When running sst start, a placeholder site is deployed. This is to ensure that the site content remains unchanged, and subsequent sst start can start up quickly.

new StaticSite(stack, "frontend", {
disablePlaceholder: true
});

environment?

Type : Record<string, string>

An object with the key being the environment variable name. Note, this requires your build tool to support build time environment variables.

new StaticSite(stack, "frontend", {
environment: {
REACT_APP_API_URL: api.url,
REACT_APP_USER_POOL_CLIENT: auth.cognitoUserPoolClient.userPoolClientId,
},
});

errorPage?

Type : "redirect_to_index_page" | Omit<string, "redirect_to_index_page">

Default : redirect_to_index_page

The error page behavior for this website. Takes either an HTML page (ie. "404.html") or the "redirect_to_index_page" to redirect to the index page. Note that, if the error pages are redirected to the index page, the HTTP status code is set to 200. This is necessary for single page apps, that handle 404 pages on the client side.

new StaticSite(stack, "Site", {
errorPage: "redirect_to_index_page",
});

fileOptions?

Type : Array<StaticSiteFileOptions>

Pass in a list of file options to configure cache control for different files. Behind the scenes, the StaticSite construct uses a combination of the s3 cp and s3 sync commands to upload the website content to the S3 bucket. An s3 cp command is run for each file option block, and the options are passed in as the command options. Defaults to no cache control for HTML files, and a 1 year cache control for JS/CSS files.

[
{
exclude: "*",
include: "*.html",
cacheControl: "max-age=0,no-cache,no-store,must-revalidate",
},
{
exclude: "*",
include: ["*.js", "*.css"],
cacheControl: "max-age=31536000,public,immutable",
},
]
new StaticSite(stack, "Site", {
buildOutput: "dist",
fileOptions: [{
exclude: "*",
include: "*.js",
cacheControl: "max-age=31536000,public,immutable",
}]
});

indexPage?

Type : string

Default : index.html

The name of the index page (e.g. "index.html") of the website.

new StaticSite(stack, "Site", {
indexPage: "other-index.html",
});

path

Type : string

Path to the directory where the website source is located.

new StaticSite(stack, "Site", {
path: "path/to/src",
});

purgeFiles?

Type : boolean

Default : true

While deploying, SST removes old files that no longer exist. Pass in false to keep the old files around.

new StaticSite(stack, "frontend", {
purgeFiles: false
});

replaceValues?

Type : Array<StaticSiteReplaceProps>

Pass in a list of placeholder values to be replaced in the website content. For example, the follow configuration:

new StaticSite(stack, "frontend", {
replaceValues: [
{
files: "*.js",
search: "{{ API_URL }}",
replace: api.url,
},
{
files: "*.js",
search: "{{ COGNITO_USER_POOL_CLIENT_ID }}",
replace: auth.cognitoUserPoolClient.userPoolClientId,
},
],
});

vite.types?

Type : string

Default : "src/sst-env.d.ts"

The path where code-gen should place the type definition for environment variables

new StaticSite(stack, "frontend", {
vite: {
types: "./other/path/sst-env.d.ts",
}
});

waitForInvalidation?

Type : boolean

Default : true

While deploying, SST waits for the CloudFront cache invalidation process to finish. This ensures that the new content will be served once the deploy command finishes. However, this process can sometimes take more than 5 mins. For non-prod environments it might make sense to pass in false. That'll skip waiting for the cache to invalidate and speed up the deploy process.

new StaticSite(stack, "frontend", {
waitForInvalidation: false
});

cdk.bucket?

Type : IBucket | BucketProps

Allows you to override default settings this construct uses internally to ceate the bucket

new StaticSite(stack, "Site", {
path: "path/to/src",
cdk: {
bucket: {
bucketName: "mybucket",
},
}
});

cdk.distribution?

Type : StaticSiteCdkDistributionProps

Configure the internally created CDK Distribution instance.

new StaticSite(stack, "Site", {
path: "path/to/src",
cdk: {
distribution: {
comment: "Distribution for my React website",
},
}
});

cdk.id?

Type : string

Allows you to override default id for this construct.

Properties

An instance of StaticSite has the following properties.

bucketArn

Type : string

The ARN of the internally created S3 Bucket.

bucketName

Type : string

The name of the internally created S3 Bucket.

customDomainUrl

Type : undefined | string

If the custom domain is enabled, this is the URL of the website with the custom domain.

distributionDomain

Type : string

The domain name of the internally created CloudFront Distribution.

distributionId

Type : string

The ID of the internally created CloudFront Distribution.

id

Type : string

url

Type : string

The CloudFront URL of the website.

cdk.bucket

Type : Bucket

The internally created CDK Bucket instance.

cdk.certificate?

Type : ICertificate

cdk.distribution

Type : Distribution

The internally created CDK Distribution instance.

cdk.hostedZone?

Type : IHostedZone

The Route 53 hosted zone for the custom domain.

StaticSiteDomainProps

alternateNames?

Type : Array<string>

Default : []

Specify additional names that should route to the Cloudfront Distribution. Note, certificates for these names will not be automatically generated so the certificate option must be specified.

domainAlias?

Type : string

Default : no alias configured

An alternative domain to be assigned to the website URL. Visitors to the alias will be redirected to the main domain. (ie. www.domain.com). Use this to create a www. version of your domain and redirect visitors to the root domain.

domainName

Type : string

The domain to be assigned to the website URL (ie. domain.com). Supports domains that are hosted either on Route 53 or externally.

hostedZone?

Type : string

Default : same as the domainName

The hosted zone in Route 53 that contains the domain. By default, SST will look for a hosted zone matching the domainName that's passed in. Set this option if SST cannot find the hosted zone in Route 53.

isExternalDomain?

Type : boolean

Default : false

Set this option if the domain is not hosted on Amazon Route 53.

cdk.certificate?

Type : ICertificate

Import the certificate for the domain. By default, SST will create a certificate with the domain name. The certificate will be created in the us-east-1(N. Virginia) region as required by AWS CloudFront. Set this option if you have an existing certificate in the us-east-1 region in AWS Certificate Manager you want to use.

cdk.hostedZone?

Type : IHostedZone

Import the underlying Route 53 hosted zone.

StaticSiteFileOptions

cacheControl

Type : string

exclude

Type : string | Array<string>

include

Type : string | Array<string>

StaticSiteReplaceProps

files

Type : string

replace

Type : string

Type : string

StaticSiteCdkDistributionProps

defaultBehavior?

Type :