StaticSite
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 AWS:
- The assets are deployed to an S3 Bucket and served out from a CloudFront CDN for fast content delivery.
- It enables you to configure custom domains for the website URL.
- It also enables you to automatically set the environment variables for your static site directly from the outputs in your SST app.
Working locally
To work on your static site locally with SST:
Start SST in your project root.
npx sst dev
Then start your static site. With Vite for example:
npm run dev
This should run
sst bind vite
.
note
When running sst dev
, SST does not deploy your static site. It's meant to be run locally.
Custom domains
You can configure the website with a custom domain hosted either on Route 53 or externally.
new StaticSite(stack, "Site", {
path: "path/to/site",
customDomain: "my-app.com",
});
Note that visitors to the http://
URL will be redirected to the https://
URL.
You can also configure an alias domain to point to the main domain. For example, to setup www.my-app.com
redirecting to my-app.com
:
new StaticSite(stack, "Site", {
path: "path/to/site",
customDomain: {
domainName: "my-app.com",
domainAlias: "www.my-app.com",
},
});
Environment variables
The StaticSite
construct allows you to set the environment variables in your site based on outputs from other constructs in your SST app. So you don't have to hardcode the config from your backend. Let's look at how.
Imagine you have a Vite site and an API created using the Api
construct. To fetch data from the API, you would pass the API's endpoint to your site.
const api = new Api(stack, "Api", {
// ...
});
new StaticSite(stack, "Site", {
path: "vite-app/",
environment: {
VITE_APP_API_URL: api.url,
},
});
Then you can access the API's URL in your frontend js code:
fetch(import.meta.env.VITE_APP_API_URL);
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_APP_API_URL: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
This tells your editor the environment variables that are available and autocompletes them for you.
You can also override the path for the generated type definitions file.
new StaticSite(stack, "Site", {
path: "vite-app/",
environment: {
VITE_APP_API_URL: api.url,
},
vite: {
types: "types/my-env.d.ts",
},
});
While deploying
On sst deploy
, the environment variables will first be replaced by placeholder values, ie. {{ VITE_APP_API_URL }}
, when building the Vite site. And after the api has been created, 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 dev
to start the Live Lambda Development environment.
npx sst dev
Then in your Vite app to reference these variables, add the sst bind
command.
"scripts": {
"dev": "sst bind vite",
"build": "vite build",
"preview": "vite preview"
},
Now you can start your Vite app as usual 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:
- The
sst dev
command generates a file with the values specified byStaticSite
'senvironment
prop. - The
sst bind
CLI will traverse up the directories to look for the root of your SST app. - It'll then find the file that's generated in step 1.
- It'll load these as environment variables before running the start command.
note
sst bind
only works if the Next.js app is located inside the SST app or inside one of its subdirectories. For example:
/
sst.json
vite-app/
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_APP_API_URL: api.url,
},
});
Note that Vite has a convention to only pass the environment variables that begin with VITE_
.
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_APP_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_APP_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",
});
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 to 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 to 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",
assets: {
fileOptions: [
{
files: "**",
cacheControl: "max-age=0,no-cache,no-store,must-revalidate",
},
{
files: ["**/*.js", "**/*.css"],
cacheControl: "max-age=31536000,public,immutable",
},
],
}
});
This configures the .js
and .css
files to be cached forever, while the .html
and other files to not be cached by the browser.
Note that the file options later in the array will take precedence over those earlier in the array.
Content type
The content type for files is automatically assigned based on their extensions by default. For files without an extension, the default content type is binary/octet-stream
. However, you can override this default setting:
new StaticSite(stack, "frontend", {
path: "path/to/site",
buildOutput: "build",
buildCommand: "npm run build",
errorPage: "redirect_to_index_page",
assets: {
fileOptions: [
{
files: "**/a-json-file-without-extension",
cacheControl: "max-age=31536000,public,immutable",
contentType: "application/json",
},
],
}
});
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.
import { HttpVersion } from "aws-cdk-lib/aws-cloudfront";
new StaticSite(stack, "frontend", {
path: "path/to/site",
cdk: {
distribution: {
httpVersion: HttpVersion.HTTP3,
},
},
});
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_18_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
- scope Construct
- id string
- props StaticSiteProps
StaticSiteProps
assets?
Type :
assets.fileOptions?
Type : Array<StaticSiteFileOptions>
Default : No cache control for HTML files, and a 1 year cache control for JS/CSS files.
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.
assets: {
fileOptions: [
{
files: "**/*.zip",
cacheControl: "private,no-cache,no-store,must-revalidate",
contentType: "application/zip",
},
],
}
assets.textEncoding?
Type : "ascii" | "utf-8" | "none" | "iso-8859-1" | "windows-1252"
Default : utf-8
Character encoding for text based assets uploaded to S3 (ex: html, css, js, etc.). If "none" is specified, no charset will be returned in header.
assets: {
textEncoding: "iso-8859-1"
}
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, "frontend", {
path: "path/to/src",
customDomain: "domain.com",
});
new StaticSite(stack, "frontend", {
path: "path/to/src",
customDomain: {
domainName: "domain.com",
domainAlias: "www.domain.com",
hostedZone: "domain.com"
}
});
dev?
Type :
dev.deploy?
Type : boolean
Default : false
When running
sst dev, site is not deployed. This is to ensure
sst dev` can start up quickly.
new StaticSite(stack, "frontend", {
dev: {
deploy: true
}
});
dev.url?
Type : string
The local site URL when running
sst dev
.
new StaticSite(stack, "frontend", {
dev: {
url: "http://localhost:3000"
}
});
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.
404.html
Or the constant
"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",
});
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
Default : "."
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?
Type :
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 : false
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: true
});
cdk?
Type :
cdk.bucket?
Type : IBucket | BucketProps
Allows you to override default settings this construct uses internally to create the bucket
new StaticSite(stack, "Site", {
path: "path/to/src",
cdk: {
bucket: {
bucketName: "mybucket",
},
}
});
cdk.distribution?
Type : IDistribution | StaticSiteCdkDistributionProps
Configure the internally created CDK
Distribution
instance or provide an existing distribution
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.
customDomainUrl
Type : undefined | string
If the custom domain is enabled, this is the URL of the website with the custom domain.
id
Type : string
url
Type : undefined | string
The CloudFront URL of the website.
cdk
Type : undefined |
cdk.bucket
Type : Bucket
cdk.certificate
Type : undefined | ICertificate
cdk.distribution
Type : IDistribution
cdk.hostedZone
Type : undefined | IHostedZone
The internally created CDK resources.
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
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?
Type :
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
contentType?
Type : string
files
Type : string | Array<string>
ignore?
Type : string | Array<string>
StaticSiteReplaceProps
files
Type : string
replace
Type : string
search
Type : string
StaticSiteCdkDistributionProps
defaultBehavior?
Type :