diff --git a/API.md b/API.md
index 7bc95afd..aa6b5e87 100644
--- a/API.md
+++ b/API.md
@@ -215,6 +215,7 @@ Any object.
| revalidation
| NextjsRevalidation
| Revalidation handler and queue. |
| serverFunction
| NextjsServer
| The main NextJS server handler lambda function. |
| staticAssets
| NextjsStaticAssets
| Asset deployment to S3. |
+| domain
| NextjsDomain
| Optional Route53 Hosted Zone, ACM Certificate, and Route53 DNS Records. |
---
@@ -346,6 +347,18 @@ Asset deployment to S3.
---
+##### `domain`Optional
+
+```typescript
+public readonly domain: NextjsDomain;
+```
+
+- *Type:* NextjsDomain
+
+Optional Route53 Hosted Zone, ACM Certificate, and Route53 DNS Records.
+
+---
+
### NextjsBucketDeployment
@@ -784,11 +797,7 @@ Any object.
| distributionDomain
| string
| The domain name of the internally created CloudFront Distribution. |
| distributionId
| string
| The ID of the internally created CloudFront Distribution. |
| url
| string
| The CloudFront URL of the website. |
-| customDomainName
| string
| *No description.* |
-| customDomainUrl
| string
| If the custom domain is enabled, this is the URL of the website with the custom domain. |
| distribution
| aws-cdk-lib.aws_cloudfront.Distribution
| The internally created CloudFront `Distribution` instance. |
-| certificate
| aws-cdk-lib.aws_certificatemanager.ICertificate
| The AWS Certificate Manager certificate for the custom domain. |
-| hostedZone
| aws-cdk-lib.aws_route53.IHostedZone
| The Route 53 hosted zone for the custom domain. |
---
@@ -864,41 +873,164 @@ The CloudFront URL of the website.
---
-##### `customDomainName`Optional
+##### `distribution`Required
```typescript
-public readonly customDomainName: string;
+public readonly distribution: Distribution;
```
-- *Type:* string
+- *Type:* aws-cdk-lib.aws_cloudfront.Distribution
+
+The internally created CloudFront `Distribution` instance.
---
-##### `customDomainUrl`Optional
+
+### NextjsDomain
+
+Use a custom domain with `Nextjs`.
+
+Requires a Route53 hosted zone to have been
+created within the same AWS account. For DNS setups where you cannot use a
+Route53 hosted zone in the same AWS account, use the `defaults.distribution.cdk.distribution`
+prop of {@link NextjsProps}.
+
+See {@link NextjsDomainProps} TS Doc comments for detailed docs on how to customize.
+This construct is helpful to user to not have to worry about interdependencies
+between Route53 Hosted Zone, CloudFront Distribution, and Route53 Hosted Zone Records.
+
+Note, if you're using another service for domain name registration, you can
+still create a Route53 hosted zone. Please see [Configuring DNS Delegation from
+CloudFlare to AWS Route53](https://veducate.co.uk/dns-delegation-route53/)
+as an example.
+
+#### Initializers
```typescript
-public readonly customDomainUrl: string;
+import { NextjsDomain } from 'cdk-nextjs-standalone'
+
+new NextjsDomain(scope: Construct, id: string, props: NextjsDomainProps)
```
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| scope
| constructs.Construct
| *No description.* |
+| id
| string
| *No description.* |
+| props
| NextjsDomainProps
| *No description.* |
+
+---
+
+##### `scope`Required
+
+- *Type:* constructs.Construct
+
+---
+
+##### `id`Required
+
- *Type:* string
-If the custom domain is enabled, this is the URL of the website with the custom domain.
+---
+
+##### `props`Required
+
+- *Type:* NextjsDomainProps
---
-##### `distribution`Required
+#### Methods
+
+| **Name** | **Description** |
+| --- | --- |
+| toString
| Returns a string representation of this construct. |
+| createDnsRecords
| Creates DNS records (A and AAAA) records for {@link NextjsDomainProps.domainName} and {@link NextjsDomainProps.alternateNames} if defined. |
+
+---
+
+##### `toString`
```typescript
-public readonly distribution: Distribution;
+public toString(): string
```
+Returns a string representation of this construct.
+
+##### `createDnsRecords`
+
+```typescript
+public createDnsRecords(distribution: Distribution): void
+```
+
+Creates DNS records (A and AAAA) records for {@link NextjsDomainProps.domainName} and {@link NextjsDomainProps.alternateNames} if defined.
+
+###### `distribution`Required
+
- *Type:* aws-cdk-lib.aws_cloudfront.Distribution
-The internally created CloudFront `Distribution` instance.
+---
+
+#### Static Functions
+
+| **Name** | **Description** |
+| --- | --- |
+| isConstruct
| Checks if `x` is a construct. |
+
+---
+
+##### ~~`isConstruct`~~
+
+```typescript
+import { NextjsDomain } from 'cdk-nextjs-standalone'
+
+NextjsDomain.isConstruct(x: any)
+```
+
+Checks if `x` is a construct.
+
+###### `x`Required
+
+- *Type:* any
+
+Any object.
---
-##### `certificate`Optional
+#### Properties
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| node
| constructs.Node
| The tree node. |
+| domainNames
| string[]
| Concatentation of {@link NextjsDomainProps.domainName} and {@link NextjsDomainProps.alternateNames}. Used in instantiation of CloudFront Distribution in NextjsDistribution. |
+| certificate
| aws-cdk-lib.aws_certificatemanager.ICertificate
| ACM Certificate. |
+| hostedZone
| aws-cdk-lib.aws_route53.IHostedZone
| Route53 Hosted Zone. |
+
+---
+
+##### `node`Required
+
+```typescript
+public readonly node: Node;
+```
+
+- *Type:* constructs.Node
+
+The tree node.
+
+---
+
+##### `domainNames`Required
+
+```typescript
+public readonly domainNames: string[];
+```
+
+- *Type:* string[]
+
+Concatentation of {@link NextjsDomainProps.domainName} and {@link NextjsDomainProps.alternateNames}. Used in instantiation of CloudFront Distribution in NextjsDistribution.
+
+---
+
+##### `certificate`Required
```typescript
public readonly certificate: ICertificate;
@@ -906,11 +1038,11 @@ public readonly certificate: ICertificate;
- *Type:* aws-cdk-lib.aws_certificatemanager.ICertificate
-The AWS Certificate Manager certificate for the custom domain.
+ACM Certificate.
---
-##### `hostedZone`Optional
+##### `hostedZone`Required
```typescript
public readonly hostedZone: IHostedZone;
@@ -918,7 +1050,7 @@ public readonly hostedZone: IHostedZone;
- *Type:* aws-cdk-lib.aws_route53.IHostedZone
-The Route 53 hosted zone for the custom domain.
+Route53 Hosted Zone.
---
@@ -2477,113 +2609,6 @@ Bucket containing assets.
## Structs
-### BaseSiteDomainProps
-
-#### Initializer
-
-```typescript
-import { BaseSiteDomainProps } from 'cdk-nextjs-standalone'
-
-const baseSiteDomainProps: BaseSiteDomainProps = { ... }
-```
-
-#### Properties
-
-| **Name** | **Type** | **Description** |
-| --- | --- | --- |
-| domainName
| string
| The domain to be assigned to the website URL (ie. domain.com). |
-| alternateNames
| string[]
| Specify additional names that should route to the Cloudfront Distribution. |
-| certificate
| aws-cdk-lib.aws_certificatemanager.ICertificate
| Import the certificate for the domain. |
-| domainAlias
| string
| An alternative domain to be assigned to the website URL. |
-| hostedZone
| aws-cdk-lib.aws_route53.IHostedZone
| Import the underlying Route 53 hosted zone. |
-| isExternalDomain
| boolean
| Set this option if the domain is not hosted on Amazon Route 53. |
-
----
-
-##### `domainName`Required
-
-```typescript
-public readonly domainName: string;
-```
-
-- *Type:* string
-
-The domain to be assigned to the website URL (ie. domain.com).
-
-Supports domains that are hosted either on [Route 53](https://aws.amazon.com/route53/) or externally.
-
----
-
-##### `alternateNames`Optional
-
-```typescript
-public readonly alternateNames: string[];
-```
-
-- *Type:* string[]
-
-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.
-
----
-
-##### `certificate`Optional
-
-```typescript
-public readonly certificate: ICertificate;
-```
-
-- *Type:* aws-cdk-lib.aws_certificatemanager.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.
-
----
-
-##### `domainAlias`Optional
-
-```typescript
-public readonly domainAlias: string;
-```
-
-- *Type:* string
-
-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.
-
----
-
-##### `hostedZone`Optional
-
-```typescript
-public readonly hostedZone: IHostedZone;
-```
-
-- *Type:* aws-cdk-lib.aws_route53.IHostedZone
-
-Import the underlying Route 53 hosted zone.
-
----
-
-##### `isExternalDomain`Optional
-
-```typescript
-public readonly isExternalDomain: boolean;
-```
-
-- *Type:* boolean
-
-Set this option if the domain is not hosted on Amazon Route 53.
-
----
-
### NextjsBucketDeploymentProps
#### Initializer
@@ -3007,9 +3032,9 @@ const nextjsDistributionProps: NextjsDistributionProps = { ... }
| basePath
| string
| *No description.* |
| cachePolicies
| NextjsCachePolicyProps
| Override the default CloudFront cache policies created internally. |
| cdk
| NextjsDistributionCdkProps
| Overrides for created CDK resources. |
-| customDomain
| string \| NextjsDomainProps
| The customDomain for this website. Supports domains that are hosted either on [Route 53](https://aws.amazon.com/route53/) or externally. |
| distribution
| aws-cdk-lib.aws_cloudfront.Distribution
| *No description.* |
| functionUrlAuthType
| aws-cdk-lib.aws_lambda.FunctionUrlAuthType
| Override lambda function url auth type. |
+| nextDomain
| NextjsDomain
| *No description.* |
| originRequestPolicies
| NextjsOriginRequestPolicyProps
| Override the default CloudFront origin request policies created internally. |
---
@@ -3116,38 +3141,6 @@ Overrides for created CDK resources.
---
-##### `customDomain`Optional
-
-```typescript
-public readonly customDomain: string | NextjsDomainProps;
-```
-
-- *Type:* string | NextjsDomainProps
-
-The customDomain for this website. Supports domains that are hosted either on [Route 53](https://aws.amazon.com/route53/) or externally.
-
-Note that you can also migrate externally hosted domains to Route 53 by
-[following this guide](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/MigratingDNS.html).
-
----
-
-*Example*
-
-```typescript
-new NextjsDistribution(this, "Dist", {
- customDomain: "domain.com",
-});
-
-new NextjsDistribution(this, "Dist", {
- customDomain: {
- domainName: "domain.com",
- domainAlias: "www.domain.com",
- hostedZone: "domain.com"
- },
-});
-```
-
-
##### `distribution`Optional
```typescript
@@ -3173,6 +3166,18 @@ Override lambda function url auth type.
---
+##### `nextDomain`Optional
+
+```typescript
+public readonly nextDomain: NextjsDomain;
+```
+
+- *Type:* NextjsDomain
+
+> [{@link NextjsDomain }]({@link NextjsDomain })
+
+---
+
##### `originRequestPolicies`Optional
```typescript
@@ -3199,12 +3204,11 @@ const nextjsDomainProps: NextjsDomainProps = { ... }
| **Name** | **Type** | **Description** |
| --- | --- | --- |
-| domainName
| string
| The domain to be assigned to the website URL (ie. domain.com). |
-| alternateNames
| string[]
| Specify additional names that should route to the Cloudfront Distribution. |
-| certificate
| aws-cdk-lib.aws_certificatemanager.ICertificate
| Import the certificate for the domain. |
-| domainAlias
| string
| An alternative domain to be assigned to the website URL. |
-| hostedZone
| aws-cdk-lib.aws_route53.IHostedZone
| Import the underlying Route 53 hosted zone. |
-| isExternalDomain
| boolean
| Set this option if the domain is not hosted on Amazon Route 53. |
+| domainName
| string
| An easy to remember address of your website. |
+| alternateNames
| string[]
| Alternate domain names that should route to the Cloudfront Distribution. |
+| certificate
| aws-cdk-lib.aws_certificatemanager.ICertificate
| If this prop is `undefined` then an ACM `Certificate` will be created based on {@link NextjsDomainProps.domainName} with DNS Validation. This prop allows you to control the TLS/SSL certificate created. The certificate you create must be in the `us-east-1` (N. Virginia) region as required by AWS CloudFront. |
+| certificateDomainName
| string
| The domain name used in this construct when creating an ACM `Certificate`. |
+| hostedZone
| aws-cdk-lib.aws_route53.IHostedZone
| You must create the hosted zone out-of-band. |
---
@@ -3216,12 +3220,22 @@ public readonly domainName: string;
- *Type:* string
-The domain to be assigned to the website URL (ie. domain.com).
+An easy to remember address of your website.
-Supports domains that are hosted either on [Route 53](https://aws.amazon.com/route53/) or externally.
+Only supports domains hosted
+on [Route 53](https://aws.amazon.com/route53/). Used as `domainName` for
+ACM `Certificate` if {@link NextjsDomainProps.certificate} and
+{@link NextjsDomainProps.certificateDomainName} are `undefined`.
---
+*Example*
+
+```typescript
+"example.com"
+```
+
+
##### `alternateNames`Optional
```typescript
@@ -3230,12 +3244,33 @@ public readonly alternateNames: string[];
- *Type:* string[]
-Specify additional names that should route to the Cloudfront Distribution.
+Alternate domain 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.
+For example, if you specificied `"example.com"` as your {@link NextjsDomainProps.domainName},
+you could specify `["www.example.com", "api.example.com"]`.
+Learn more about the [requirements](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/CNAMEs.html#alternate-domain-names-requirements)
+and [restrictions](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/CNAMEs.html#alternate-domain-names-restrictions)
+for using alternate domain names with CloudFront.
+
+Note, in order to use alternate domain names, they must be covered by your
+certificate. By default, the certificate created in this construct only covers
+the {@link NextjsDomainProps.domainName}. Therefore, you'll need to specify
+a wildcard domain name like `"*.example.com"` with {@link NextjsDomainProps.certificateDomainName}
+so that this construct will create the certificate the covers the alternate
+domain names. Otherwise, you can use {@link NextjsDomainProps.certificate}
+to create the certificate yourself where you'll need to ensure it has a
+wildcard or uses subject alternative names including the
+alternative names specified here.
---
+*Example*
+
+```typescript
+["www.example.com", "api.example.com"]
+```
+
+
##### `certificate`Optional
```typescript
@@ -3244,27 +3279,28 @@ public readonly certificate: ICertificate;
- *Type:* aws-cdk-lib.aws_certificatemanager.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.
+If this prop is `undefined` then an ACM `Certificate` will be created based on {@link NextjsDomainProps.domainName} with DNS Validation. This prop allows you to control the TLS/SSL certificate created. The certificate you create must be 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.
---
-##### `domainAlias`Optional
+##### `certificateDomainName`Optional
```typescript
-public readonly domainAlias: string;
+public readonly certificateDomainName: string;
```
- *Type:* string
-An alternative domain to be assigned to the website URL.
+The domain name used in this construct when creating an ACM `Certificate`.
-Visitors to the alias will be redirected to the main domain. (ie. `www.domain.com`).
+Useful
+when passing {@link NextjsDomainProps.alternateNames} and you need to specify
+a wildcard domain like "*.example.com". If `undefined`, then {@link NextjsDomainProps.domainName}
+will be used.
-Use this to create a `www.` version of your domain and redirect visitors to the root domain.
+If {@link NextjsDomainProps.certificate} is passed, then this prop is ignored.
---
@@ -3276,19 +3312,11 @@ public readonly hostedZone: IHostedZone;
- *Type:* aws-cdk-lib.aws_route53.IHostedZone
-Import the underlying Route 53 hosted zone.
+You must create the hosted zone out-of-band.
----
-
-##### `isExternalDomain`Optional
-
-```typescript
-public readonly isExternalDomain: boolean;
-```
-
-- *Type:* boolean
-
-Set this option if the domain is not hosted on Amazon Route 53.
+You can lookup the hosted zone outside this construct and pass it in via this prop.
+Alternatively if this prop is `undefined`, then the hosted zone will be
+**looked up** (not created) via `HostedZone.fromLookup` with {@link NextjsDomainProps.domainName}.
---
@@ -3452,6 +3480,7 @@ const nextjsProps: NextjsProps = { ... }
| buildPath
| string
| The directory to execute `npm run build` from. |
| defaults
| NextjsDefaultsProps
| Allows you to override defaults for the resources created by this construct. |
| distribution
| aws-cdk-lib.aws_cloudfront.Distribution
| Optional CloudFront Distribution created outside of this construct that will be used to add Next.js behaviors and origins onto. Useful with `basePath`. |
+| domainProps
| NextjsDomainProps
| Props to configure {@link NextjsDomain}. |
| environment
| {[ key: string ]: string}
| Custom environment variables to pass to the NextJS build **and** runtime. |
| imageOptimizationBucket
| aws-cdk-lib.aws_s3.IBucket
| Optional S3 Bucket to use, defaults to assets bucket. |
| quiet
| boolean
| Less build output. |
@@ -3550,6 +3579,21 @@ Optional CloudFront Distribution created outside of this construct that will be
---
+##### `domainProps`Optional
+
+```typescript
+public readonly domainProps: NextjsDomainProps;
+```
+
+- *Type:* NextjsDomainProps
+
+Props to configure {@link NextjsDomain}.
+
+See details on how to customize at
+{@link NextjsDomainProps}
+
+---
+
##### `environment`Optional
```typescript
diff --git a/docs/major-changes.md b/docs/major-changes.md
index c7336db8..c436e9ea 100644
--- a/docs/major-changes.md
+++ b/docs/major-changes.md
@@ -21,6 +21,7 @@
- Remove `NextjsBaseProps` to simplify props
- Remove `projectRoot` as it's not being used
- Remove `tempBuildDir` as it's not being used
+- Create `NextjsDomain`. Remove custom domain related props from `NextjsDistribution`.
## v3
diff --git a/open-next b/open-next
index 34c78cbc..8bc075b9 160000
--- a/open-next
+++ b/open-next
@@ -1 +1 @@
-Subproject commit 34c78cbc7466e8f74ba06b41066fcc0969045b96
+Subproject commit 8bc075b9696857d60ffe520154b8395c3a730b00
diff --git a/src/Nextjs.ts b/src/Nextjs.ts
index 4711503e..3432fcb7 100644
--- a/src/Nextjs.ts
+++ b/src/Nextjs.ts
@@ -3,17 +3,15 @@ import * as lambda from 'aws-cdk-lib/aws-lambda';
import { FunctionOptions } from 'aws-cdk-lib/aws-lambda';
import * as s3 from 'aws-cdk-lib/aws-s3';
import { Construct } from 'constructs';
-import { BaseSiteDomainProps } from './NextjsBase';
import { NextjsBuild } from './NextjsBuild';
import { NextjsDistribution, NextjsDistributionProps } from './NextjsDistribution';
+import { NextjsDomain, NextjsDomainProps } from './NextjsDomain';
import { NextjsImage } from './NextjsImage';
import { NextjsInvalidation } from './NextjsInvalidation';
import { NextjsRevalidation } from './NextjsRevalidation';
import { NextjsServer } from './NextjsServer';
import { NextjsStaticAssets, NextjsStaticAssetsProps } from './NextjsStaticAssets';
-export interface NextjsDomainProps extends BaseSiteDomainProps {}
-
/**
* Defaults for created resources.
* Why `any`? see https://github.com/aws/jsii/issues/2901
@@ -69,6 +67,11 @@ export interface NextjsProps {
* be used to add Next.js behaviors and origins onto. Useful with `basePath`.
*/
readonly distribution?: Distribution;
+ /**
+ * Props to configure {@link NextjsDomain}. See details on how to customize at
+ * {@link NextjsDomainProps}
+ */
+ readonly domainProps?: NextjsDomainProps;
/**
* Custom environment variables to pass to the NextJS build **and** runtime.
*/
@@ -119,27 +122,26 @@ export class Nextjs extends Construct {
* The main NextJS server handler lambda function.
*/
public serverFunction: NextjsServer;
-
/**
* The image optimization handler lambda function.
*/
public imageOptimizationFunction: NextjsImage;
-
/**
* Built NextJS project output.
*/
public nextBuild: NextjsBuild;
-
/**
* Asset deployment to S3.
*/
public staticAssets: NextjsStaticAssets;
-
+ /**
+ * Optional Route53 Hosted Zone, ACM Certificate, and Route53 DNS Records
+ */
+ public domain?: NextjsDomain;
/**
* CloudFront distribution.
*/
public distribution: NextjsDistribution;
-
/**
* Revalidation handler and queue.
*/
@@ -156,41 +158,48 @@ export class Nextjs extends Construct {
// deploy nextjs static assets to s3
this.staticAssets = new NextjsStaticAssets(this, 'StaticAssets', {
+ basePath: props.basePath,
bucket: props.defaults?.assetDeployment?.bucket,
environment: props.environment,
nextBuild: this.nextBuild,
- basePath: props.basePath,
});
this.serverFunction = new NextjsServer(this, 'Server', {
- ...props,
nextBuild: this.nextBuild,
lambda: props.defaults?.lambda,
staticAssetBucket: this.staticAssets.bucket,
});
// build image optimization
this.imageOptimizationFunction = new NextjsImage(this, 'ImgOptFn', {
- ...props,
- nextBuild: this.nextBuild,
bucket: props.imageOptimizationBucket || this.bucket,
lambdaOptions: props.defaults?.lambda,
+ nextBuild: this.nextBuild,
});
// build revalidation queue and handler function
this.revalidation = new NextjsRevalidation(this, 'Revalidation', {
- ...props,
+ lambdaOptions: props.defaults?.lambda,
nextBuild: this.nextBuild,
serverFunction: this.serverFunction,
});
+ if (this.props.domainProps) {
+ this.domain = new NextjsDomain(this, 'Domain', this.props.domainProps);
+ }
this.distribution = new NextjsDistribution(this, 'Distribution', {
- ...props,
+ nextjsPath: props.nextjsPath,
+ basePath: props.basePath,
+ distribution: props.distribution,
...props.defaults?.distribution,
staticAssetsBucket: this.staticAssets.bucket,
nextBuild: this.nextBuild,
+ nextDomain: this.domain,
serverFunction: this.serverFunction.lambdaFunction,
imageOptFunction: this.imageOptimizationFunction,
});
+ if (this.domain) {
+ this.domain.createDnsRecords(this.distribution.distribution);
+ }
if (!this.props.skipFullInvalidation) {
new NextjsInvalidation(this, 'Invalidation', {
@@ -204,7 +213,7 @@ export class Nextjs extends Construct {
* URL of Next.js App.
*/
public get url(): string {
- const customDomain = this.distribution.customDomainName;
+ const customDomain = this.props.domainProps?.domainName;
return customDomain ? `https://${customDomain}` : this.distribution.url;
}
diff --git a/src/NextjsBase.ts b/src/NextjsBase.ts
deleted file mode 100644
index 4661c8d0..00000000
--- a/src/NextjsBase.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { ICertificate } from 'aws-cdk-lib/aws-certificatemanager';
-import { IHostedZone } from 'aws-cdk-lib/aws-route53';
-
-///// stuff below taken from https://github.com/serverless-stack/sst/blob/8d377e941467ced81d8cc31ee67d5a06550f04d4/packages/resources/src/BaseSite.ts
-
-export interface BaseSiteDomainProps {
- /**
- * The domain to be assigned to the website URL (ie. domain.com).
- *
- * Supports domains that are hosted either on [Route 53](https://aws.amazon.com/route53/) or externally.
- */
- readonly domainName: string;
- /**
- * 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.
- */
- readonly domainAlias?: string;
- /**
- * 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.
- */
- readonly alternateNames?: string[];
- /**
- * Set this option if the domain is not hosted on Amazon Route 53.
- */
- readonly isExternalDomain?: boolean;
-
- /**
- * Import the underlying Route 53 hosted zone.
- */
- readonly hostedZone?: IHostedZone;
- /**
- * 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.
- */
- readonly certificate?: ICertificate;
-}
diff --git a/src/NextjsDistribution.ts b/src/NextjsDistribution.ts
index d0418f0f..28dfbfb7 100644
--- a/src/NextjsDistribution.ts
+++ b/src/NextjsDistribution.ts
@@ -1,24 +1,18 @@
import * as fs from 'node:fs';
import * as path from 'path';
import { Duration, Fn, RemovalPolicy } from 'aws-cdk-lib';
-import * as acm from 'aws-cdk-lib/aws-certificatemanager';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
import { Distribution, ResponseHeadersPolicy } from 'aws-cdk-lib/aws-cloudfront';
import * as origins from 'aws-cdk-lib/aws-cloudfront-origins';
import { PolicyStatement, ServicePrincipal } from 'aws-cdk-lib/aws-iam';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { Runtime } from 'aws-cdk-lib/aws-lambda';
-import * as route53 from 'aws-cdk-lib/aws-route53';
-import * as route53Patterns from 'aws-cdk-lib/aws-route53-patterns';
-import * as route53Targets from 'aws-cdk-lib/aws-route53-targets';
import * as s3 from 'aws-cdk-lib/aws-s3';
import { Construct } from 'constructs';
import { DEFAULT_STATIC_MAX_AGE, NEXTJS_BUILD_DIR, NEXTJS_STATIC_DIR } from './constants';
import { NextjsProps } from './Nextjs';
-import { BaseSiteDomainProps } from './NextjsBase';
import { NextjsBuild } from './NextjsBuild';
-
-export interface NextjsDomainProps extends BaseSiteDomainProps {}
+import { NextjsDomain } from './NextjsDomain';
export type NextjsDistributionCdkOverrideProps = cloudfront.DistributionProps;
@@ -61,27 +55,6 @@ export interface NextjsDistributionProps {
* Overrides for created CDK resources.
*/
readonly cdk?: NextjsDistributionCdkProps;
- /**
- * The customDomain for this website. Supports domains that are hosted
- * either on [Route 53](https://aws.amazon.com/route53/) or externally.
- *
- * Note that you can also migrate externally hosted domains to Route 53 by
- * [following this guide](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/MigratingDNS.html).
- *
- * @example
- * new NextjsDistribution(this, "Dist", {
- * customDomain: "domain.com",
- * });
- *
- * new NextjsDistribution(this, "Dist", {
- * customDomain: {
- * domainName: "domain.com",
- * domainAlias: "www.domain.com",
- * hostedZone: "domain.com"
- * },
- * });
- */
- readonly customDomain?: string | NextjsDomainProps;
/**
* @see {@link NextjsProps.distribution}
*/
@@ -100,6 +73,10 @@ export interface NextjsDistributionProps {
* @see {@link NextjsBuild}
*/
readonly nextBuild: NextjsBuild;
+ /**
+ * @see {@link NextjsDomain}
+ */
+ readonly nextDomain?: NextjsDomain;
/**
* @see {@link NextjsProps.nextjsPath}
*/
@@ -160,7 +137,7 @@ export class NextjsDistribution extends Construct {
comment: 'Nextjs Image Default Cache Policy',
};
- protected props: NextjsDistributionProps;
+ private props: NextjsDistributionProps;
/////////////////////
// Public Properties
@@ -169,14 +146,6 @@ export class NextjsDistribution extends Construct {
* The internally created CloudFront `Distribution` instance.
*/
public distribution: Distribution;
- /**
- * The Route 53 hosted zone for the custom domain.
- */
- hostedZone?: route53.IHostedZone;
- /**
- * The AWS Certificate Manager certificate for the custom domain.
- */
- certificate?: acm.ICertificate;
private commonBehaviorOptions: Pick = {
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
@@ -198,11 +167,6 @@ export class NextjsDistribution extends Construct {
this.props = props;
- // Create Custom Domain
- this.validateCustomDomainSettings();
- this.hostedZone = this.lookupHostedZone();
- this.certificate = this.createCertificate();
-
// Create Behaviors
this.s3Origin = new origins.S3Origin(this.props.staticAssetsBucket);
this.staticBehaviorOptions = this.createStaticBehaviorOptions();
@@ -216,9 +180,6 @@ export class NextjsDistribution extends Construct {
this.distribution = this.getCloudFrontDistribution();
this.addStaticBehaviorsToDistribution();
this.addRootPathBehavior();
-
- // Connect Custom Domain to CloudFront Distribution
- this.createRoute53Records();
}
/**
@@ -228,29 +189,6 @@ export class NextjsDistribution extends Construct {
return `https://${this.distribution.distributionDomainName}`;
}
- get customDomainName(): string | undefined {
- const { customDomain } = this.props;
-
- if (!customDomain) {
- return;
- }
-
- if (typeof customDomain === 'string') {
- return customDomain;
- }
-
- return customDomain.domainName;
- }
-
- /**
- * If the custom domain is enabled, this is the URL of the website with the
- * custom domain.
- */
- public get customDomainUrl(): string | undefined {
- const customDomainName = this.customDomainName;
- return customDomainName ? `https://${customDomainName}` : undefined;
- }
-
/**
* The ID of the internally created CloudFront Distribution.
*/
@@ -436,20 +374,17 @@ export class NextjsDistribution extends Construct {
* create a CloudFront Distribution if one is passed in by user.
*/
private createCloudFrontDistribution(cfDistributionProps?: NextjsDistributionCdkOverrideProps) {
- // build domainNames
- const domainNames = this.buildDistributionDomainNames();
-
return new cloudfront.Distribution(this, 'Distribution', {
// defaultRootObject: "index.html",
defaultRootObject: '',
minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021,
+ domainNames: this.props.nextDomain?.domainNames,
+ certificate: this.props.nextDomain?.certificate,
// Override props.
...cfDistributionProps,
// these values can NOT be overwritten by cfDistributionProps
- domainNames,
- certificate: this.certificate,
defaultBehavior: this.serverBehaviorOptions,
});
}
@@ -509,154 +444,4 @@ export class NextjsDistribution extends Construct {
return pathPattern;
}
-
- private buildDistributionDomainNames(): string[] {
- const customDomain =
- typeof this.props.customDomain === 'string' ? this.props.customDomain : this.props.customDomain?.domainName;
-
- const alternateNames =
- typeof this.props.customDomain === 'string' ? [] : this.props.customDomain?.alternateNames || [];
-
- return customDomain ? [customDomain, ...alternateNames] : [];
- }
-
- /////////////////////
- // Custom Domain
- /////////////////////
-
- protected validateCustomDomainSettings() {
- const { customDomain } = this.props;
-
- if (!customDomain) {
- return;
- }
-
- if (typeof customDomain === 'string') {
- return;
- }
-
- if (customDomain.isExternalDomain === true) {
- if (!customDomain.certificate) {
- throw new Error('A valid certificate is required when "isExternalDomain" is set to "true".');
- }
- if (customDomain.domainAlias) {
- throw new Error(
- 'Domain alias is only supported for domains hosted on Amazon Route 53. Do not set the "customDomain.domainAlias" when "isExternalDomain" is enabled.'
- );
- }
- if (customDomain.hostedZone) {
- throw new Error(
- 'Hosted zones can only be configured for domains hosted on Amazon Route 53. Do not set the "customDomain.hostedZone" when "isExternalDomain" is enabled.'
- );
- }
- }
- }
-
- protected lookupHostedZone(): route53.IHostedZone | undefined {
- const { customDomain } = this.props;
-
- // Skip if customDomain is not configured
- if (!customDomain) {
- return;
- }
-
- let hostedZone;
-
- if (typeof customDomain === 'string') {
- hostedZone = route53.HostedZone.fromLookup(this, 'HostedZone', {
- domainName: customDomain,
- });
- } else if (typeof customDomain.hostedZone === 'string') {
- hostedZone = route53.HostedZone.fromLookup(this, 'HostedZone', {
- domainName: customDomain.hostedZone,
- });
- } else if (customDomain.hostedZone) {
- hostedZone = customDomain.hostedZone;
- } else if (typeof customDomain.domainName === 'string') {
- // Skip if domain is not a Route53 domain
- if (customDomain.isExternalDomain === true) {
- return;
- }
-
- hostedZone = route53.HostedZone.fromLookup(this, 'HostedZone', {
- domainName: customDomain.domainName,
- });
- } else {
- hostedZone = customDomain.hostedZone;
- }
-
- return hostedZone;
- }
-
- private createCertificate(): acm.ICertificate | undefined {
- const { customDomain } = this.props;
-
- if (!customDomain) {
- return;
- }
-
- let acmCertificate;
-
- // HostedZone is set for Route 53 domains
- if (this.hostedZone) {
- if (typeof customDomain === 'string') {
- acmCertificate = new acm.DnsValidatedCertificate(this, 'Certificate', {
- domainName: customDomain,
- hostedZone: this.hostedZone,
- region: 'us-east-1',
- });
- } else if (customDomain.certificate) {
- acmCertificate = customDomain.certificate;
- } else {
- acmCertificate = new acm.DnsValidatedCertificate(this, 'Certificate', {
- domainName: customDomain.domainName,
- hostedZone: this.hostedZone,
- region: 'us-east-1',
- });
- }
- }
- // HostedZone is NOT set for non-Route 53 domains
- else {
- if (typeof customDomain !== 'string') {
- acmCertificate = customDomain.certificate;
- }
- }
-
- return acmCertificate;
- }
-
- private createRoute53Records(): void {
- const { customDomain } = this.props;
-
- if (!customDomain || !this.hostedZone) {
- return;
- }
-
- let recordName;
- let domainAlias;
- if (typeof customDomain === 'string') {
- recordName = customDomain;
- } else {
- recordName = customDomain.domainName;
- domainAlias = customDomain.domainAlias;
- }
-
- // Create DNS record
- const recordProps = {
- recordName,
- zone: this.hostedZone,
- target: route53.RecordTarget.fromAlias(new route53Targets.CloudFrontTarget(this.distribution)),
- };
- new route53.ARecord(this, 'AliasRecord', recordProps);
- new route53.AaaaRecord(this, 'AliasRecordAAAA', recordProps);
-
- // Create Alias redirect record
- if (domainAlias) {
- new route53Patterns.HttpsRedirect(this, 'Redirect', {
- zone: this.hostedZone,
- recordNames: [domainAlias],
- targetDomain: recordName,
- });
- }
- }
}
diff --git a/src/NextjsDomain.ts b/src/NextjsDomain.ts
new file mode 100644
index 00000000..5bafc9b9
--- /dev/null
+++ b/src/NextjsDomain.ts
@@ -0,0 +1,166 @@
+import { ICertificate, Certificate, CertificateValidation } from 'aws-cdk-lib/aws-certificatemanager';
+import { Distribution } from 'aws-cdk-lib/aws-cloudfront';
+import {
+ ARecord,
+ ARecordProps,
+ AaaaRecord,
+ AaaaRecordProps,
+ HostedZone,
+ IHostedZone,
+ RecordTarget,
+} from 'aws-cdk-lib/aws-route53';
+import { CloudFrontTarget } from 'aws-cdk-lib/aws-route53-targets';
+import { Construct } from 'constructs';
+import { NextjsProps } from '.';
+
+export interface NextjsDomainProps {
+ /**
+ * An easy to remember address of your website. Only supports domains hosted
+ * on [Route 53](https://aws.amazon.com/route53/). Used as `domainName` for
+ * ACM `Certificate` if {@link NextjsDomainProps.certificate} and
+ * {@link NextjsDomainProps.certificateDomainName} are `undefined`.
+ * @example "example.com"
+ */
+ readonly domainName: string;
+ /**
+ * Alternate domain names that should route to the Cloudfront Distribution.
+ * For example, if you specificied `"example.com"` as your {@link NextjsDomainProps.domainName},
+ * you could specify `["www.example.com", "api.example.com"]`.
+ * Learn more about the [requirements](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/CNAMEs.html#alternate-domain-names-requirements)
+ * and [restrictions](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/CNAMEs.html#alternate-domain-names-restrictions)
+ * for using alternate domain names with CloudFront.
+ *
+ * Note, in order to use alternate domain names, they must be covered by your
+ * certificate. By default, the certificate created in this construct only covers
+ * the {@link NextjsDomainProps.domainName}. Therefore, you'll need to specify
+ * a wildcard domain name like `"*.example.com"` with {@link NextjsDomainProps.certificateDomainName}
+ * so that this construct will create the certificate the covers the alternate
+ * domain names. Otherwise, you can use {@link NextjsDomainProps.certificate}
+ * to create the certificate yourself where you'll need to ensure it has a
+ * wildcard or uses subject alternative names including the
+ * alternative names specified here.
+ * @example ["www.example.com", "api.example.com"]
+ */
+ readonly alternateNames?: string[];
+ /**
+ * You must create the hosted zone out-of-band.
+ * You can lookup the hosted zone outside this construct and pass it in via this prop.
+ * Alternatively if this prop is `undefined`, then the hosted zone will be
+ * **looked up** (not created) via `HostedZone.fromLookup` with {@link NextjsDomainProps.domainName}.
+ */
+ readonly hostedZone?: IHostedZone;
+ /**
+ * If this prop is `undefined` then an ACM `Certificate` will be created based on {@link NextjsDomainProps.domainName}
+ * with DNS Validation. This prop allows you to control the TLS/SSL
+ * certificate created. The certificate you create must be 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.
+ */
+ readonly certificate?: ICertificate;
+ /**
+ * The domain name used in this construct when creating an ACM `Certificate`. Useful
+ * when passing {@link NextjsDomainProps.alternateNames} and you need to specify
+ * a wildcard domain like "*.example.com". If `undefined`, then {@link NextjsDomainProps.domainName}
+ * will be used.
+ *
+ * If {@link NextjsDomainProps.certificate} is passed, then this prop is ignored.
+ */
+ readonly certificateDomainName?: string;
+}
+
+/**
+ * Use a custom domain with `Nextjs`. Requires a Route53 hosted zone to have been
+ * created within the same AWS account. For DNS setups where you cannot use a
+ * Route53 hosted zone in the same AWS account, use the `defaults.distribution.cdk.distribution`
+ * prop of {@link NextjsProps}.
+ *
+ * See {@link NextjsDomainProps} TS Doc comments for detailed docs on how to customize.
+ * This construct is helpful to user to not have to worry about interdependencies
+ * between Route53 Hosted Zone, CloudFront Distribution, and Route53 Hosted Zone Records.
+ *
+ * Note, if you're using another service for domain name registration, you can
+ * still create a Route53 hosted zone. Please see [Configuring DNS Delegation from
+ * CloudFlare to AWS Route53](https://veducate.co.uk/dns-delegation-route53/)
+ * as an example.
+ */
+export class NextjsDomain extends Construct {
+ /**
+ * Concatentation of {@link NextjsDomainProps.domainName} and {@link NextjsDomainProps.alternateNames}.
+ * Used in instantiation of CloudFront Distribution in NextjsDistribution
+ */
+ get domainNames(): string[] {
+ const names = [this.props.domainName];
+ if (this.props.alternateNames?.length) {
+ names.push(...this.props.alternateNames);
+ }
+ return names;
+ }
+ /**
+ * Route53 Hosted Zone.
+ */
+ hostedZone: IHostedZone;
+ /**
+ * ACM Certificate.
+ */
+ certificate: ICertificate;
+
+ private props: NextjsDomainProps;
+
+ constructor(scope: Construct, id: string, props: NextjsDomainProps) {
+ super(scope, id);
+ this.props = props;
+ this.hostedZone = this.getHostedZone();
+ this.certificate = this.getCertificate();
+ }
+
+ private getHostedZone(): IHostedZone {
+ if (!this.props.hostedZone) {
+ return HostedZone.fromLookup(this, 'HostedZone', {
+ domainName: this.props.domainName,
+ });
+ } else {
+ return this.props.hostedZone;
+ }
+ }
+
+ private getCertificate(): ICertificate {
+ if (!this.props.certificate) {
+ return new Certificate(this, 'Certificate', {
+ domainName: this.props.certificateDomainName ?? this.props.domainName,
+ validation: CertificateValidation.fromDns(this.hostedZone),
+ });
+ } else {
+ return this.props.certificate;
+ }
+ }
+
+ /**
+ * Creates DNS records (A and AAAA) records for {@link NextjsDomainProps.domainName}
+ * and {@link NextjsDomainProps.alternateNames} if defined.
+ */
+ createDnsRecords(distribution: Distribution): void {
+ // Create DNS record
+ const recordProps: ARecordProps & AaaaRecordProps = {
+ recordName: this.props.domainName,
+ zone: this.hostedZone,
+ target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),
+ };
+ new ARecord(this, 'ARecordMain', recordProps); // IPv4
+ new AaaaRecord(this, 'AaaaRecordMain', recordProps); // IPv6
+ if (this.props.alternateNames?.length) {
+ let i = 1;
+ for (const alternateName of this.props.alternateNames) {
+ new ARecord(this, 'ARecordAlt' + i, {
+ ...recordProps,
+ recordName: `${alternateName}.`,
+ });
+ new AaaaRecord(this, 'AaaaRecordAlt' + i, {
+ ...recordProps,
+ recordName: `${alternateName}.`,
+ });
+ i++;
+ }
+ }
+ }
+}
diff --git a/src/index.ts b/src/index.ts
index b5914291..074b065a 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,6 +1,3 @@
-export { BaseSiteDomainProps } from './NextjsBase';
-
-// L2 constructs
export { NextjsStaticAssets, NextjsStaticAssetsProps } from './NextjsStaticAssets';
export { NextjsRevalidation, NextjsRevalidationProps } from './NextjsRevalidation';
export { NextjsBuild, NextjsBuildProps } from './NextjsBuild';
@@ -12,11 +9,9 @@ export {
NextjsDistributionCdkProps,
NextjsDistributionCdkOverrideProps,
NextjsDistributionProps,
- NextjsDomainProps,
NextjsCachePolicyProps,
NextjsOriginRequestPolicyProps,
} from './NextjsDistribution';
export { NextjsInvalidation, NextjsInvalidationProps } from './NextjsInvalidation';
-
-// L3 constructs
+export { NextjsDomain, NextjsDomainProps } from './NextjsDomain';
export { Nextjs, NextjsProps, NextjsDefaultsProps } from './Nextjs';