The server-side-website
construct deploys websites where the HTML is rendered "server-side", i.e. on AWS Lambda.
This is usually done with backend frameworks like Laravel/Symfony (PHP), Ruby on Rails, Django/Flask (Python), Express (Node), etc.
To build a SPA or static website, use the Static Website construct instead.
service: my-app
provider:
name: aws
functions:
home:
handler: home.handler
events:
- httpApi: 'GET /'
# ...
constructs:
website:
type: server-side-website
assets:
'/css/*': public/css
'/js/*': public/js
plugins:
- serverless-lift
On serverless deploy
, the example above will set up a website that serves both:
https://<domain>/*
-> the website through API Gateway + Lambdahttps://<domain>/css/*
andhttps://<domain>/js/*
-> assets through S3
Note: the first deployment takes 5 minutes.
The website is served over HTTPS and cached all over the world via the CloudFront CDN.
On the first serverless deploy
, Lift creates:
- an S3 bucket to serve static assets (CSS, JS, etc.)
- a CloudFront CDN
CloudFront serves the website over HTTPS with caching at the edge. It also provides an "HTTP to HTTPS" redirection which is not supported by API Gateway. For websites, this is problematic because it means someone typing website.com
in a browser will get a blank page: API Gateway will not even redirect this to HTTPS.
Finally, CloudFront also acts as a router:
- URLs that points to static assets are served by S3
- all the other URLs are served by API Gateway
The construct uses the API Gateway configured in functions defined in serverless.yml
.
Additionally, every time serverless deploy
runs, Lift:
- uploads the static assets to the S3 bucket
- invalidates the CloudFront CDN cache
Note: the S3 bucket is public and entirely managed by Lift. Do not store or upload files to the bucket, they will be removed by Lift on the next deployment. Instead, create a separate bucket to store any extra file.
CloudFront is configured to cache static assets by default, but not cache dynamic content by default. It will forward cookies, query strings and most headers to the backend running on Lambda.
To define website routes, create Lambda functions in functions:
with httpApi
events:
# serverless.yml
# ...
functions:
home:
handler: home.handler
events:
- httpApi: 'GET /'
search:
handler: search.handler
events:
- httpApi: 'GET /search'
# ...
constructs:
website:
type: server-side-website
# ...
Check out the official documentation on how to set up HTTP events.
When using backend frameworks that provide a routing feature, another option is to define a single Lambda function that captures all the HTTP routes:
# serverless.yml
# ...
functions:
backend:
handler: index.handler
events:
- httpApi: '*'
constructs:
website:
type: server-side-website
# ...
The server-side-website
construct exposes the following variables:
url
: the URL of the deployed website (either the CloudFront URL or the first custom domain, if configured)
For example:
constructs:
website:
type: server-side-website
# ...
functions:
backend:
# ...
environment:
WEBSITE_URL: ${construct:website.url}
How it works: the ${construct:website.url}
variable will automatically be replaced with a CloudFormation reference.
cname
: the CloudFront domain to point custom domains to, for exampled1111abcdef8.cloudfront.net
This can be used to configure a custom domain with Route53, for example:
constructs:
website:
type: server-side-website
# ...
resources:
Resources:
Route53Record:
Type: AWS::Route53::RecordSet
Properties:
HostedZoneId: ZXXXXXXXXXXXXXXXXXXJ # Your HostedZoneId
Name: app.mydomain
Type: A
AliasTarget:
HostedZoneId: Z2FDTNDATAQYW2 # Cloudfront Route53 HostedZoneId. This does not change.
DNSName: ${construct:website.cname}
assetsBucketName
: the S3 assets bucketname
This can be used to configure a S3 bucket policy for example:
constructs:
website:
type: server-side-website
# ...
resources:
Resources:
BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: ${construct:website.assetsBucketName}
# ...
serverless deploy
deploys everything configured in serverless.yml
and uploads assets.
When iterating, it is possible to skip the CloudFormation deployment and directly publish changes via:
serverless deploy function -f <function-name>
to deploy a single Lambda functionserverless <construct-name>:assets:upload
to upload assets to S3 (the CloudFront cache will be cleared as well)
constructs:
website:
# ...
assets:
'/assets/*': dist/
The assets
section lets users define routing for static assets (like JavaScript, CSS, images, etc.).
- The key defines the URL pattern.
- The value defines the local path to upload.
Assets can be either whole directories, or single files:
constructs:
website:
# ...
assets:
# Directories: routes must end with `/*`
'/css/*': dist/css
'/images/*': assets/animations
# Files:
'/favicon.ico': public/favicon.ico
With the example above:
https://<domain>/*
-> Lambdahttps://<domain>/css/*
-> serves the files uploaded from the localdist/css
directoryhttps://<domain>/images/*
-> serves the files uploaded from the localassets/animations
directoryhttps://<domain>/favicon.ico
-> serves the file uploaded frompublic/favicon.ico
API Gateway provides 2 versions of APIs:
- v1: REST API
- v2: HTTP API, the fastest and cheapest
By default, the server-side-website
construct supports v2 HTTP APIs.
If your Lambda functions uses http
events (v1 REST API) instead of httpApi
events (v2 HTTP API), use the apiGateway: "rest"
option:
constructs:
website:
type: server-side-website
apiGateway: 'rest' # either "rest" (v1) or "http" (v2, the default)
functions:
v1:
handler: foo.handler
events:
- http: 'GET /' # REST API (v1)
v2:
handler: bar.handler
events:
- httpApi: 'GET /' # HTTP API (v2)
constructs:
website:
# ...
domain: mywebsite.com
# ARN of an ACM certificate for the domain, registered in us-east-1
certificate: arn:aws:acm:us-east-1:123456615250:certificate/0a28e63d-d3a9-4578-9f8b-14347bfe8123
The configuration above will activate the custom domain mywebsite.com
on CloudFront, using the provided HTTPS certificate.
After running serverless deploy
(or serverless info
), you should see the following output in the terminal:
website:
url: https://mywebsite.com
cname: s13hocjp.cloudfront.net
Create a CNAME DNS entry that points your domain to the xxx.cloudfront.net
domain. After a few minutes/hours, the domain should be available.
To create the HTTPS certificate:
- Open the ACM Console in the
us-east-1
region (CDN certificates must be in us-east-1, regardless of where your application is hosted) - Click "Request a new certificate", add your domain name and click "Next"
- Choose a domain validation method:
- Domain validation will require you to add CNAME entries to your DNS configuration
- Email validation will require you to click a link in an email sent to
admin@your-domain.com
After the certificate is created and validated, you should see the ARN of the certificate.
It is possible to set up multiple domains:
constructs:
website:
# ...
domain:
- mywebsite.com
- app.mywebsite.com
Usually, we can retrieve which domain a user is visiting via the Host
HTTP header. This doesn't work with API Gateway (Host
contains the API Gateway domain).
The server-side-website
construct offers a workaround: the X-Forwarded-Host
header is automatically populated via CloudFront Functions. Code running on Lambda will be able to access the original Host
header via this X-Forwarded-Host
header.
It is sometimes necessary to redirect every request to a single domain. A common example is to redirect the root domain to the www
version.
constructs:
website:
# ...
domain:
- www.mywebsite.com
- mywebsite.com
redirectToMainDomain: true
The first domain in the list will be considered the main domain. In this case, mywebsite.com
will redirect to www.mywebsite.com
.
constructs:
website:
# ...
errorPage: error500.html
In case a web page throws an error, API Gateway's default Internal Error
blank page shows up. This can be overridden by providing an HTML error page.
Applications are of course free to catch errors and display custom error pages. However, sometimes even error pages and frameworks fail completely: this is where the API Gateway error page shows up.
Note:
In previous Lift versions, the headers forwarded to your Lambda backend were limited. You were able to add new headers via the
forwardedHeaders
option.This is no longer the case: all headers are now always forwarded.
All headers are forwarded to your Lambda backend. If you used the forwardedHeaders
option in the past, you can safely remove it.
You can specify an extensions
property on the server-side-website
construct to extend the underlying CloudFormation resources. In the exemple below, the CloudFront Distribution CloudFormation resource generated by the website
server-side-website construct will be extended with the new Comment: Landing distribution
CloudFormation property.
constructs:
website:
type: server-side-website
assets:
'/css/*': public/css
'/js/*': public/js
extensions:
distribution:
Properties:
DistributionConfig:
Comment: Landing distribution
Extension key | CloudFormation resource | CloudFormation documentation |
---|---|---|
distribution | AWS::CloudFront::Distribution | Link |
bucket | AWS::S3::Bucket | Link |
Feel like a common extension pattern should be implemented as part of the construct configuration? Open a GitHub issue.