Custom Domains
Setting custom domains for your frontend or API in SST.
Overview
The easiest way to set a custom domain in SST is by having your domains hosted in Route 53. Simply set the customDomain
prop:
For your frontend
const site = new NextjsSite(stack, "site", {
customDomain: "my-app.com",
});Or API
const api = new Api(stack, "api", {
customDomain: "api.my-app.com",
routes: {
"GET /": "packages/functions/src/lambda.handler",
},
});
This will set the domain and configure SSL automatically. Including redirecting http://
to https://
.
If your domains are hosted elsewhere, check out the section below.
Custom domain URL
Once set, you can output the URLs using customDomainUrl
. Both for the frontend and API.
stack.addOutputs({
Site: site.customDomainUrl || site.url,
API: api.customDomainUrl || api.url,
});
It's good practice to fallback to the auto-generated URL in case the custom domain URL is not set.
Redirect to www
For root domains, people prefer to redirect www.my-app.com
to my-app.com
. You can configure this by setting a domainAlias
.
new NextjsSite(stack, "site", {
customDomain: {
domainName: "my-app.com",
domainAlias: "www.my-app.com",
},
});
Use a subdomain
Route 53 has a concept of hosted zones; a collection of records belonging to a single root domain. If you are using a subdomain as your custom domain, you'll need to specify the Route 53 hosted zone you are using. Usually this is just the root domain.
new NextjsSite(stack, "site", {
customDomain: {
domainName: "dev.my-app.com",
hostedZone: "my-app.com",
},
});
Note that we didn't have to do that for the API example above because behind the scenes the Api
construct defaults to the hosted zone for the domain one level higher. For example, if customDomain
is set to dev.api.my-app.com
, it defaults to the hosted zone api.my-app.com
. To use the my-app.com
hosted zone:
new Api(stack, "api", {
customDomain: {
domainName: "dev.api.my-app.com",
hostedZone: "my-app.com",
},
});
Domains across stages
You might want to configure custom domains just for your production environments.
new NextjsSite(stack, "site", {
customDomain: stack.stage === "prod" ? "my-app.com" : undefined,
});
Or give each stage its own subdomain.
new NextjsSite(stack, "site", {
customDomain:
stack.stage === "prod" ? "my-app.com" : `${stack.stage}.my-app.com`,
});
Where the stage
is the name you pass into sst deploy --stage $STAGE
.
Externally hosted domains
Your domains might not be hosted on Route 53. In this case you could transfer your domain to Route 53. Or if you want to keep it at your original provider, you have a couple of options.
Migrate DNS to Route 53
The simpler approach is moving the DNS service from your current provider to Route 53. AWS has a couple of docs on this.
- Making Route 53 the DNS service for a domain that's in use — this is for cases where the domain is currently receiving a lot of traffic.
- Making Route 53 the DNS service for an inactive domain — this is for cases where the domain is not in use or is not receiving much traffic.
Once completed, you can follow the steps above to set your custom domains.
Point the CNAME to CloudFront
The alternative approach is to create a CNAME record in your existing provider and point it to the CloudFront distribution. This is the auto-generated URL that you get when you deploy your frontend or API, d111111abcdef8.cloudfront.net
.
You cannot point a CNAME to a root domain. For example, you can point it to www.my-app.com
but not my-app.com
.
However, there are a couple of ways to get around this:
- Migrate your DNS to Route 53
- Use a subdomain like
www.my-app.com
- Use a redirect from
my-app.com
towww.my-app.com
- Use a domain provider that supports ALIAS, ANAME, or CNAME flattening.
To point the CNAME, you need to:
Create a certificate in the
us-east-1
region for the domain you want. This is required by CloudFront. You'll need to verify that you own the domain.Get the ARN of the certificate and set it in your construct,
certArn
.import { Certificate } from "aws-cdk-lib/aws-certificatemanager";
new NextjsSite(stack, "site", {
customDomain: {
domainName: "www.my-app.com",
isExternalDomain: true,
cdk: {
certificate: Certificate.fromCertificateArn(stack, "MyCert", certArn),
},
},
});Deploy your app and get the CloudFront distribution URL.
Set the CNAME to the CloudFront distribution in your external domain provider.
You can read more about this over on the AWS docs.
Advanced
You can further configure how your custom domains are set.
Importing a certificate
You can import existing certificates to use with your custom domain.
note
The certificate needs be created in the us-east-1
(N. Virginia) region as required by CloudFront.
import { Certificate } from "aws-cdk-lib/aws-certificatemanager";
new NextjsSite(stack, "site", {
customDomain: {
domainName: "my-app.com",
cdk: {
certificate: Certificate.fromCertificateArn(stack, "MyCert", certArn),
},
},
});
Here certArn
is the ARN of the certificate.
Alternate domain names
In addition to your custom domain, you can specify additional domain names.
Note that the certificate for these names will not be automatically generated, so the certificate prop must be specified. Also note that you need to manually create the Route 53 records for 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: "my-app.com",
});
// Create a certificate with alternate domain names
const certificate = new acm.DnsValidatedCertificate(stack, "Certificate", {
domainName: "foo.my-app.com",
hostedZone,
// The certificates need to be created in us-east-1
region: "us-east-1",
subjectAlternativeNames: ["bar.my-app.com"],
});
// Create site
const site = new NextjsSite(stack, "site", {
customDomain: {
domainName: "foo.my-app.com",
alternateNames: ["bar.my-app.com"],
cdk: {
hostedZone,
certificate,
},
},
});
// Create A and AAAA records for the alternate domain names
// Note: CloudFront distribution is not created when running `sst dev`.
if (site.cdk?.distribution) {
const recordProps = {
recordName: "bar.my-app.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);
}
Sharing domains across accounts
It's good practice to use separate AWS accounts for your environments. However this means that you'll need to share a domain across AWS accounts. Imagine we want the following scheme.
prod
⇒ my-app.comdev
⇒ dev.my-app.com
Our prod
account has the root domain and we want to use dev.my-app.com
in a separate dev
account. Let's look at how to do this.
- Create a hosted zone in the new account. Go into the Route 53 console for the
dev
account and create a new hosted zone calleddev.my-app.com
. - Once created, copy the 4 lines from the Values field of the NS record.
- Now go to the Route 53 hosted zone for
my-app.com
in theprod
account. - Create a new Record set with the following.
- Name:
dev
- Type: "NS - Name server"
- Value: Paste the 4 lines from above
- Name:
That's it. Now you've delegated this subdomain to your dev
account. To use this subdomain, you'll need to specify the new hosted zone you created.
new NextjsSite(stack, "site", {
customDomain: {
domainName: "dev.my-app.com",
hostedZone: "dev.my-app.com",
},
});
Note that we are using dev.my-app.com
and not my-app.com
as the hosted zone because we are using the dev
account.