diff --git a/.projenrc.ts b/.projenrc.ts
index bfb3a5e..4da9b6b 100644
--- a/.projenrc.ts
+++ b/.projenrc.ts
@@ -112,6 +112,7 @@ new ProjenStruct(project, {
filePath: getFilePath('OptionalCloudFrontFunctionProps'),
})
.mixin(Struct.fromFqn('aws-cdk-lib.aws_cloudfront.FunctionProps'))
+ .omit('code')
.allOptional();
new ProjenStruct(project, {
name: 'OptionalDistributionProps',
diff --git a/API.md b/API.md
index 55102e5..c8f528d 100644
--- a/API.md
+++ b/API.md
@@ -3004,7 +3004,6 @@ const nextjsDistributionOverrides: NextjsDistributionOverrides = { ... }
| **Name** | **Type** | **Description** |
| --- | --- | --- |
-| cloudFrontFunctionProps
| OptionalCloudFrontFunctionProps
| *No description.* |
| distributionProps
| OptionalDistributionProps
| *No description.* |
| edgeFunctionProps
| OptionalEdgeFunctionProps
| *No description.* |
| imageBehaviorOptions
| aws-cdk-lib.aws_cloudfront.AddBehaviorOptions
| *No description.* |
@@ -3018,16 +3017,7 @@ const nextjsDistributionOverrides: NextjsDistributionOverrides = { ... }
| serverResponseHeadersPolicyProps
| aws-cdk-lib.aws_cloudfront.ResponseHeadersPolicyProps
| *No description.* |
| staticBehaviorOptions
| aws-cdk-lib.aws_cloudfront.AddBehaviorOptions
| *No description.* |
| staticResponseHeadersPolicyProps
| aws-cdk-lib.aws_cloudfront.ResponseHeadersPolicyProps
| *No description.* |
-
----
-
-##### `cloudFrontFunctionProps`Optional
-
-```typescript
-public readonly cloudFrontFunctionProps: OptionalCloudFrontFunctionProps;
-```
-
-- *Type:* OptionalCloudFrontFunctionProps
+| viewerRequestFunctionProps
| ViewerRequestFunctionProps
| *No description.* |
---
@@ -3161,6 +3151,16 @@ public readonly staticResponseHeadersPolicyProps: ResponseHeadersPolicyProps;
---
+##### `viewerRequestFunctionProps`Optional
+
+```typescript
+public readonly viewerRequestFunctionProps: ViewerRequestFunctionProps;
+```
+
+- *Type:* ViewerRequestFunctionProps
+
+---
+
### NextjsDistributionProps
#### Initializer
@@ -5223,7 +5223,6 @@ const optionalCloudFrontFunctionProps: OptionalCloudFrontFunctionProps = { ... }
| **Name** | **Type** | **Description** |
| --- | --- | --- |
-| code
| aws-cdk-lib.aws_cloudfront.FunctionCode
| The source code of the function. |
| comment
| string
| A comment to describe the function. |
| functionName
| string
| A name to identify the function. |
| keyValueStore
| aws-cdk-lib.aws_cloudfront.IKeyValueStore
| The Key Value Store to associate with this function. |
@@ -5231,18 +5230,6 @@ const optionalCloudFrontFunctionProps: OptionalCloudFrontFunctionProps = { ... }
---
-##### `code`Optional
-
-```typescript
-public readonly code: FunctionCode;
-```
-
-- *Type:* aws-cdk-lib.aws_cloudfront.FunctionCode
-
-The source code of the function.
-
----
-
##### `comment`Optional
```typescript
@@ -9021,5 +9008,101 @@ The name of the TTL attribute.
---
+### ViewerRequestFunctionProps
+
+#### Initializer
+
+```typescript
+import { ViewerRequestFunctionProps } from 'cdk-nextjs-standalone'
+
+const viewerRequestFunctionProps: ViewerRequestFunctionProps = { ... }
+```
+
+#### Properties
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| comment
| string
| A comment to describe the function. |
+| functionName
| string
| A name to identify the function. |
+| keyValueStore
| aws-cdk-lib.aws_cloudfront.IKeyValueStore
| The Key Value Store to associate with this function. |
+| runtime
| aws-cdk-lib.aws_cloudfront.FunctionRuntime
| The runtime environment for the function. |
+| code
| aws-cdk-lib.aws_cloudfront.FunctionCode
| Cloudfront function code that runs on VIEWER_REQUEST. |
+
+---
+
+##### `comment`Optional
+
+```typescript
+public readonly comment: string;
+```
+
+- *Type:* string
+- *Default:* same as `functionName`
+
+A comment to describe the function.
+
+---
+
+##### `functionName`Optional
+
+```typescript
+public readonly functionName: string;
+```
+
+- *Type:* string
+- *Default:* generated from the `id`
+
+A name to identify the function.
+
+---
+
+##### `keyValueStore`Optional
+
+```typescript
+public readonly keyValueStore: IKeyValueStore;
+```
+
+- *Type:* aws-cdk-lib.aws_cloudfront.IKeyValueStore
+- *Default:* no key value store is associated
+
+The Key Value Store to associate with this function.
+
+In order to associate a Key Value Store, the `runtime` must be
+`cloudfront-js-2.0` or newer.
+
+---
+
+##### `runtime`Optional
+
+```typescript
+public readonly runtime: FunctionRuntime;
+```
+
+- *Type:* aws-cdk-lib.aws_cloudfront.FunctionRuntime
+- *Default:* FunctionRuntime.JS_1_0 (unless `keyValueStore` is specified, then `FunctionRuntime.JS_2_0`)
+
+The runtime environment for the function.
+
+---
+
+##### `code`Optional
+
+```typescript
+public readonly code: FunctionCode;
+```
+
+- *Type:* aws-cdk-lib.aws_cloudfront.FunctionCode
+- *Default:* async function handler(event) { // INJECT_CLOUDFRONT_FUNCTION_HOST_HEADER // INJECT_CLOUDFRONT_FUNCTION_CACHE_HEADER_KEY }
+
+Cloudfront function code that runs on VIEWER_REQUEST.
+
+The following comments will be replaced with code snippets
+so you can customize this function.
+
+INJECT_CLOUDFRONT_FUNCTION_HOST_HEADER: Add the required x-forwarded-host header.
+INJECT_CLOUDFRONT_FUNCTION_CACHE_HEADER_KEY: Improves open-next cache key.
+
+---
+
diff --git a/src/NextjsDistribution.ts b/src/NextjsDistribution.ts
index f1a811d..54167e9 100644
--- a/src/NextjsDistribution.ts
+++ b/src/NextjsDistribution.ts
@@ -27,8 +27,25 @@ import { NextjsProps } from './Nextjs';
import { NextjsBuild } from './NextjsBuild';
import { NextjsDomain } from './NextjsDomain';
+export interface ViewerRequestFunctionProps extends OptionalCloudFrontFunctionProps {
+ /**
+ * Cloudfront function code that runs on VIEWER_REQUEST.
+ * The following comments will be replaced with code snippets
+ * so you can customize this function.
+ *
+ * INJECT_CLOUDFRONT_FUNCTION_HOST_HEADER: Add the required x-forwarded-host header.
+ * INJECT_CLOUDFRONT_FUNCTION_CACHE_HEADER_KEY: Improves open-next cache key.
+ *
+ * @default
+ * async function handler(event) {
+ * // INJECT_CLOUDFRONT_FUNCTION_HOST_HEADER
+ * // INJECT_CLOUDFRONT_FUNCTION_CACHE_HEADER_KEY
+ * }
+ */
+ readonly code?: cloudfront.FunctionCode;
+}
export interface NextjsDistributionOverrides {
- readonly cloudFrontFunctionProps?: OptionalCloudFrontFunctionProps;
+ readonly viewerRequestFunctionProps?: ViewerRequestFunctionProps;
readonly distributionProps?: OptionalDistributionProps;
readonly edgeFunctionProps?: OptionalEdgeFunctionProps;
readonly imageBehaviorOptions?: AddBehaviorOptions;
@@ -272,14 +289,7 @@ export class NextjsDistribution extends Construct {
serverBehaviorOptions?.cachePolicy ??
new cloudfront.CachePolicy(this, 'ServerCachePolicy', {
queryStringBehavior: cloudfront.CacheQueryStringBehavior.all(),
- headerBehavior: cloudfront.CacheHeaderBehavior.allowList(
- 'accept',
- 'rsc',
- 'next-router-prefetch',
- 'next-router-state-tree',
- 'next-url',
- 'x-prerender-revalidate'
- ),
+ headerBehavior: cloudfront.CacheHeaderBehavior.allowList('x-open-next-cache-key'),
cookieBehavior: cloudfront.CacheCookieBehavior.all(),
defaultTtl: Duration.seconds(0),
maxTtl: Duration.days(365),
@@ -301,7 +311,7 @@ export class NextjsDistribution extends Construct {
override: false,
// MDN Cache-Control Use Case: Up-to-date contents always
// @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#up-to-date_contents_always
- value: `no-cache`,
+ value: 'no-cache',
},
],
},
@@ -323,19 +333,78 @@ export class NextjsDistribution extends Construct {
};
}
+ private useCloudFrontFunctionHostHeader() {
+ return `event.request.headers["x-forwarded-host"] = event.request.headers.host;`;
+ }
+
+ private useCloudFrontFunctionCacheHeaderKey() {
+ // This function is used to improve cache hit ratio by setting the cache key
+ // based on the request headers and the path. `next/image` only needs the
+ // accept header, and this header is not useful for the rest of the query
+ return `
+ const getHeader = (key) => {
+ const header = event.request.headers[key];
+ if (header) {
+ if (header.multiValue) {
+ return header.multiValue.map((header) => header.value).join(",");
+ }
+ if (header.value) {
+ return header.value;
+ }
+ }
+ return "";
+ }
+
+ let cacheKey = "";
+
+ if (event.request.uri.startsWith("/_next/image")) {
+ cacheKey = getHeader("accept");
+ } else {
+ cacheKey =
+ getHeader("rsc") +
+ getHeader("next-router-prefetch") +
+ getHeader("next-router-state-tree") +
+ getHeader("next-url") +
+ getHeader("x-prerender-revalidate");
+ }
+
+ if (event.request.cookies["__prerender_bypass"]) {
+ cacheKey += event.request.cookies["__prerender_bypass"]
+ ? event.request.cookies["__prerender_bypass"].value
+ : "";
+ }
+ const crypto = require("crypto")
+ const hashedKey = crypto.createHash("md5").update(cacheKey).digest("hex");
+ event.request.headers["x-open-next-cache-key"] = { value: hashedKey };
+ `;
+ }
+
/**
* If this doesn't run, then Next.js Server's `request.url` will be Lambda Function
* URL instead of domain
*/
private createCloudFrontFnAssociations() {
+ let code =
+ this.props.overrides?.viewerRequestFunctionProps?.code?.render() ??
+ `
+async function handler(event) {
+// INJECT_CLOUDFRONT_FUNCTION_HOST_HEADER
+// INJECT_CLOUDFRONT_FUNCTION_CACHE_HEADER_KEY
+}
+ `;
+ code = code.replace(
+ /^\s*\/\/\s*INJECT_CLOUDFRONT_FUNCTION_HOST_HEADER.*$/i,
+ this.useCloudFrontFunctionHostHeader()
+ );
+ code = code.replace(
+ /^\s*\/\/\s*INJECT_CLOUDFRONT_FUNCTION_CACHE_HEADER_KEY.*$/i,
+ this.useCloudFrontFunctionCacheHeaderKey()
+ );
const cloudFrontFn = new cloudfront.Function(this, 'CloudFrontFn', {
- code: cloudfront.FunctionCode.fromInline(`
- function handler(event) {
- var request = event.request;
- request.headers["x-forwarded-host"] = request.headers.host;
- return request;
- }
- `),
+ runtime: cloudfront.FunctionRuntime.JS_2_0,
+ ...this.props.overrides?.viewerRequestFunctionProps,
+ // Override code last to get injections
+ code: cloudfront.FunctionCode.fromInline(code),
});
return [{ eventType: cloudfront.FunctionEventType.VIEWER_REQUEST, function: cloudFrontFn }];
}
@@ -376,7 +445,7 @@ export class NextjsDistribution extends Construct {
override: false,
// MDN Cache-Control Use Case: Up-to-date contents always
// @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#up-to-date_contents_always
- value: `no-cache`,
+ value: 'no-cache',
},
],
},
@@ -474,7 +543,7 @@ export class NextjsDistribution extends Construct {
});
if (publicFiles.length >= 25) {
throw new Error(
- `Too many public/ files in Next.js build. CloudFront limits Distributions to 25 Cache Behaviors. See documented limit here: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html#limits-web-distributions`
+ 'Too many public/ files in Next.js build. CloudFront limits Distributions to 25 Cache Behaviors. See documented limit here: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html#limits-web-distributions'
);
}
for (const publicFile of publicFiles) {
diff --git a/src/generated-structs/OptionalCloudFrontFunctionProps.ts b/src/generated-structs/OptionalCloudFrontFunctionProps.ts
index 24f40c0..3590af8 100644
--- a/src/generated-structs/OptionalCloudFrontFunctionProps.ts
+++ b/src/generated-structs/OptionalCloudFrontFunctionProps.ts
@@ -31,9 +31,4 @@ export interface OptionalCloudFrontFunctionProps {
* @stability stable
*/
readonly comment?: string;
- /**
- * The source code of the function.
- * @stability stable
- */
- readonly code?: aws_cloudfront.FunctionCode;
}
diff --git a/src/index.ts b/src/index.ts
index df02239..981fdec 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -8,7 +8,12 @@ export {
NextjsBucketDeploymentProps,
NextjsBucketDeploymentOverrides,
} from './NextjsBucketDeployment';
-export { NextjsDistribution, NextjsDistributionProps, NextjsDistributionOverrides } from './NextjsDistribution';
+export {
+ NextjsDistribution,
+ NextjsDistributionProps,
+ NextjsDistributionOverrides,
+ ViewerRequestFunctionProps,
+} from './NextjsDistribution';
export { NextjsInvalidation, NextjsInvalidationProps, NextjsInvalidationOverrides } from './NextjsInvalidation';
export { NextjsDomain, NextjsDomainProps, NextjsDomainOverrides } from './NextjsDomain';
export { Nextjs, NextjsProps, NextjsConstructOverrides } from './Nextjs';