diff --git a/.projen/deps.json b/.projen/deps.json
index db054760..c0215ba5 100644
--- a/.projen/deps.json
+++ b/.projen/deps.json
@@ -49,7 +49,7 @@
},
{
"name": "aws-cdk-lib",
- "version": "2.93.0",
+ "version": "2.99.1",
"type": "build"
},
{
@@ -173,7 +173,7 @@
},
{
"name": "aws-cdk-lib",
- "version": "^2.93.0",
+ "version": "^2.99.1",
"type": "peer"
},
{
diff --git a/.projenrc.ts b/.projenrc.ts
index 358411ce..94a9344e 100644
--- a/.projenrc.ts
+++ b/.projenrc.ts
@@ -49,7 +49,7 @@ const project = new awscdk.AwsCdkConstructLibrary({
gitignore: ['.idea'],
// dependency config
jsiiVersion: '~5.0.0',
- cdkVersion: '2.93.0',
+ cdkVersion: '2.99.1',
bundledDeps: ['esbuild'] /* Runtime dependencies of this module. */,
devDeps: [
'@aws-crypto/sha256-js',
diff --git a/API.md b/API.md
index 40963fd7..3e63de57 100644
--- a/API.md
+++ b/API.md
@@ -602,6 +602,7 @@ Any object.
| node
| constructs.Node
| The tree node. |
| nextCacheDir
| string
| Cache directory for generated data. |
| nextImageFnDir
| string
| Contains function for processessing image requests. |
+| nextRevalidateDynamoDBProviderFnDir
| string
| Contains function for inserting revalidation items into the table. |
| nextRevalidateFnDir
| string
| Contains function for processing items from revalidation queue. |
| nextServerFnDir
| string
| Contains server code and dependencies. |
| nextStaticDir
| string
| Static files containing client-side code. |
@@ -647,6 +648,18 @@ Should be arm64.
---
+##### `nextRevalidateDynamoDBProviderFnDir`Required
+
+```typescript
+public readonly nextRevalidateDynamoDBProviderFnDir: string;
+```
+
+- *Type:* string
+
+Contains function for inserting revalidation items into the table.
+
+---
+
##### `nextRevalidateFnDir`Required
```typescript
@@ -2075,7 +2088,7 @@ The tree node.
### NextjsRevalidation
-Builds the system for revalidating Next.js resources. This includes a Lambda function handler and queue system.
+Builds the system for revalidating Next.js resources. This includes a Lambda function handler and queue system as well as the DynamoDB table and provider function.
> [{@link https://github.com/serverless-stack/open-next/blob/main/README.md?plain=1#L65}]({@link https://github.com/serverless-stack/open-next/blob/main/README.md?plain=1#L65})
@@ -2160,8 +2173,10 @@ Any object.
| **Name** | **Type** | **Description** |
| --- | --- | --- |
| node
| constructs.Node
| The tree node. |
-| function
| aws-cdk-lib.aws_lambda.Function
| *No description.* |
| queue
| aws-cdk-lib.aws_sqs.Queue
| *No description.* |
+| queueFunction
| aws-cdk-lib.aws_lambda.Function
| *No description.* |
+| table
| aws-cdk-lib.aws_dynamodb.TableV2
| *No description.* |
+| tableFunction
| aws-cdk-lib.aws_lambda.Function
| *No description.* |
---
@@ -2177,23 +2192,43 @@ The tree node.
---
-##### `function`Required
+##### `queue`Required
```typescript
-public readonly function: Function;
+public readonly queue: Queue;
+```
+
+- *Type:* aws-cdk-lib.aws_sqs.Queue
+
+---
+
+##### `queueFunction`Required
+
+```typescript
+public readonly queueFunction: Function;
```
- *Type:* aws-cdk-lib.aws_lambda.Function
---
-##### `queue`Required
+##### `table`Required
```typescript
-public readonly queue: Queue;
+public readonly table: TableV2;
```
-- *Type:* aws-cdk-lib.aws_sqs.Queue
+- *Type:* aws-cdk-lib.aws_dynamodb.TableV2
+
+---
+
+##### `tableFunction`Optional
+
+```typescript
+public readonly tableFunction: Function;
+```
+
+- *Type:* aws-cdk-lib.aws_lambda.Function
---
@@ -2596,7 +2631,7 @@ public readonly buildCommand: string;
```
- *Type:* string
-- *Default:* 'npx --yes open-next@2 build'
+- *Default:* 'npx --yes open-next@^2 build'
Optional value used to install NextJS node dependencies.
@@ -2885,7 +2920,7 @@ public readonly buildCommand: string;
```
- *Type:* string
-- *Default:* 'npx --yes open-next@2 build'
+- *Default:* 'npx --yes open-next@^2 build'
Optional value used to install NextJS node dependencies.
@@ -3217,7 +3252,7 @@ public readonly buildCommand: string;
```
- *Type:* string
-- *Default:* 'npx --yes open-next@2 build'
+- *Default:* 'npx --yes open-next@^2 build'
Optional value used to install NextJS node dependencies.
@@ -3674,7 +3709,7 @@ public readonly buildCommand: string;
```
- *Type:* string
-- *Default:* 'npx --yes open-next@2 build'
+- *Default:* 'npx --yes open-next@^2 build'
Optional value used to install NextJS node dependencies.
@@ -3949,7 +3984,7 @@ public readonly buildCommand: string;
```
- *Type:* string
-- *Default:* 'npx --yes open-next@2 build'
+- *Default:* 'npx --yes open-next@^2 build'
Optional value used to install NextJS node dependencies.
@@ -4174,7 +4209,7 @@ public readonly buildCommand: string;
```
- *Type:* string
-- *Default:* 'npx --yes open-next@2 build'
+- *Default:* 'npx --yes open-next@^2 build'
Optional value used to install NextJS node dependencies.
@@ -4363,7 +4398,7 @@ public readonly buildCommand: string;
```
- *Type:* string
-- *Default:* 'npx --yes open-next@2 build'
+- *Default:* 'npx --yes open-next@^2 build'
Optional value used to install NextJS node dependencies.
diff --git a/package.json b/package.json
index b8645da7..1385f36d 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,7 @@
"@types/node": "^18",
"@typescript-eslint/eslint-plugin": "^5",
"@typescript-eslint/parser": "^5",
- "aws-cdk-lib": "2.93.0",
+ "aws-cdk-lib": "2.99.1",
"aws-lambda": "^1.0.7",
"constructs": "10.0.5",
"esbuild": "^0.19.2",
@@ -81,7 +81,7 @@
"undici": "^5.23.0"
},
"peerDependencies": {
- "aws-cdk-lib": "^2.93.0",
+ "aws-cdk-lib": "^2.99.1",
"constructs": "^10.0.5"
},
"dependencies": {
diff --git a/src/NextjsBase.ts b/src/NextjsBase.ts
index d8bdaff9..e8369d00 100644
--- a/src/NextjsBase.ts
+++ b/src/NextjsBase.ts
@@ -37,7 +37,7 @@ export interface NextjsBaseProps {
/**
* Optional value used to install NextJS node dependencies.
- * @default 'npx --yes open-next@2 build'
+ * @default 'npx --yes open-next@^2 build'
*/
readonly buildCommand?: string;
diff --git a/src/NextjsBuild.ts b/src/NextjsBuild.ts
index af20d7e1..a982d822 100644
--- a/src/NextjsBuild.ts
+++ b/src/NextjsBuild.ts
@@ -10,6 +10,7 @@ import {
NEXTJS_BUILD_SERVER_FN_DIR,
NEXTJS_STATIC_DIR,
NEXTJS_CACHE_DIR,
+ NEXTJS_BUILD_DYNAMODB_PROVIDER_FN_DIR,
} from './constants';
import { NextjsBaseProps } from './NextjsBase';
import { NextjsBucketDeployment } from './NextjsBucketDeployment';
@@ -51,6 +52,14 @@ export class NextjsBuild extends Construct {
this.warnIfMissing(fnPath);
return fnPath;
}
+ /**
+ * Contains function for inserting revalidation items into the table.
+ */
+ public get nextRevalidateDynamoDBProviderFnDir(): string {
+ const fnPath = path.join(this.getNextBuildDir(), NEXTJS_BUILD_DYNAMODB_PROVIDER_FN_DIR);
+ this.warnIfMissing(fnPath);
+ return fnPath;
+ }
/**
* Static files containing client-side code.
*/
@@ -101,7 +110,7 @@ export class NextjsBuild extends Construct {
private build() {
const buildPath = this.props.buildPath ?? this.props.nextjsPath;
- const buildCommand = this.props.buildCommand ?? 'npx open-next@2 build';
+ const buildCommand = this.props.buildCommand ?? 'npx open-next@^2 build';
// run build
if (!this.props.quiet) {
console.debug(`├ Running "${buildCommand}" in`, buildPath);
diff --git a/src/NextjsRevalidation.ts b/src/NextjsRevalidation.ts
index 48271f57..82db7252 100644
--- a/src/NextjsRevalidation.ts
+++ b/src/NextjsRevalidation.ts
@@ -1,8 +1,12 @@
-import { Duration, Stack } from 'aws-cdk-lib';
+import * as fs from 'fs';
+import { CustomResource, Duration, RemovalPolicy, Stack } from 'aws-cdk-lib';
+import { AttributeType, Billing, TableV2 as Table } from 'aws-cdk-lib/aws-dynamodb';
import { AnyPrincipal, Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam';
import { Code, Function as LambdaFunction, FunctionOptions } from 'aws-cdk-lib/aws-lambda';
import { SqsEventSource } from 'aws-cdk-lib/aws-lambda-event-sources';
+import { RetentionDays } from 'aws-cdk-lib/aws-logs';
import { Queue } from 'aws-cdk-lib/aws-sqs';
+import { Provider } from 'aws-cdk-lib/custom-resources';
import { Construct } from 'constructs';
import { NextjsBaseProps } from './NextjsBase';
import { NextjsBuild } from './NextjsBuild';
@@ -27,14 +31,17 @@ export interface NextjsRevalidationProps extends NextjsBaseProps {
}
/**
- * Builds the system for revalidating Next.js resources. This includes a Lambda function handler and queue system.
+ * Builds the system for revalidating Next.js resources. This includes a Lambda function handler and queue system as well
+ * as the DynamoDB table and provider function.
*
* @see {@link https://github.com/serverless-stack/open-next/blob/main/README.md?plain=1#L65}
*
*/
export class NextjsRevalidation extends Construct {
queue: Queue;
- function: LambdaFunction;
+ table: Table;
+ queueFunction: LambdaFunction;
+ tableFunction: LambdaFunction | undefined;
private props: NextjsRevalidationProps;
constructor(scope: Construct, id: string, props: NextjsRevalidationProps) {
@@ -42,10 +49,19 @@ export class NextjsRevalidation extends Construct {
this.props = props;
this.queue = this.createQueue();
- this.function = this.createFunction();
+ this.queueFunction = this.createQueueFunction();
- // allow server fn to send messages to queue
- props.serverFunction.lambdaFunction?.addEnvironment('REVALIDATION_QUEUE_URL', this.queue.queueUrl);
+ this.table = this.createRevalidationTable();
+ this.tableFunction = this.createRevalidationInsertFunction(this.table);
+
+ this.props.serverFunction.lambdaFunction.addEnvironment('CACHE_DYNAMO_TABLE', this.table.tableName);
+
+ if (this.props.serverFunction.lambdaFunction.role) {
+ this.table.grantReadWriteData(this.props.serverFunction.lambdaFunction.role);
+ }
+
+ this.props.serverFunction.lambdaFunction // allow server fn to send messages to queue
+ ?.addEnvironment('REVALIDATION_QUEUE_URL', this.queue.queueUrl);
props.serverFunction.lambdaFunction?.addEnvironment('REVALIDATION_QUEUE_REGION', Stack.of(this).region);
}
@@ -72,18 +88,81 @@ export class NextjsRevalidation extends Construct {
return queue;
}
- private createFunction(): LambdaFunction {
+ private createQueueFunction(): LambdaFunction {
const commonFnProps = getCommonFunctionProps(this);
- const fn = new LambdaFunction(this, 'Fn', {
+ const fn = new LambdaFunction(this, 'QueueFn', {
...commonFnProps,
// open-next revalidation-function
// see: https://github.com/serverless-stack/open-next/blob/274d446ed7e940cfbe7ce05a21108f4c854ee37a/README.md?plain=1#L65
code: Code.fromAsset(this.props.nextBuild.nextRevalidateFnDir),
handler: 'index.handler',
- description: 'Next.js revalidation function',
+ description: 'Next.js Queue Revalidation Function',
timeout: Duration.seconds(30),
});
fn.addEventSource(new SqsEventSource(this.queue, { batchSize: 5 }));
return fn;
}
+
+ private createRevalidationTable() {
+ return new Table(this, 'Table', {
+ partitionKey: { name: 'tag', type: AttributeType.STRING },
+ sortKey: { name: 'path', type: AttributeType.STRING },
+ billing: Billing.onDemand(),
+ globalSecondaryIndexes: [
+ {
+ indexName: 'revalidate',
+ partitionKey: { name: 'path', type: AttributeType.STRING },
+ sortKey: { name: 'revalidatedAt', type: AttributeType.NUMBER },
+ },
+ ],
+ removalPolicy: RemovalPolicy.DESTROY,
+ });
+ }
+
+ /**
+ * This function will insert the initial batch of tag / path / revalidation data into the DynamoDB table during deployment.
+ * @see: {@link https://open-next.js.org/inner_workings/isr#tags}
+ *
+ * @param revalidationTable table to grant function access to
+ * @returns the revalidation insert provider function
+ */
+ private createRevalidationInsertFunction(revalidationTable: Table) {
+ const dynamodbProviderPath = this.props.nextBuild.nextRevalidateDynamoDBProviderFnDir;
+
+ // note the function may not exist - it only exists if there are cache tags values defined in Next.js build meta files to be inserted
+ // see: https://github.com/sst/open-next/blob/c2b05e3a5f82de40da1181e11c087265983c349d/packages/open-next/src/build.ts#L426-L458
+ if (fs.existsSync(dynamodbProviderPath)) {
+ const commonFnProps = getCommonFunctionProps(this);
+ const insertFn = new LambdaFunction(this, 'DynamoDBProviderFn', {
+ ...commonFnProps,
+ // open-next revalidation-function
+ // see: https://github.com/serverless-stack/open-next/blob/274d446ed7e940cfbe7ce05a21108f4c854ee37a/README.md?plain=1#L65
+ code: Code.fromAsset(this.props.nextBuild.nextRevalidateDynamoDBProviderFnDir),
+ handler: 'index.handler',
+ description: 'Next.js Revalidation DynamoDB Provider',
+ timeout: Duration.minutes(1),
+ environment: {
+ CACHE_DYNAMO_TABLE: revalidationTable.tableName,
+ },
+ });
+
+ revalidationTable.grantReadWriteData(insertFn);
+
+ const provider = new Provider(this, 'DynamoDBProvider', {
+ onEventHandler: insertFn,
+ logRetention: RetentionDays.ONE_DAY,
+ });
+
+ new CustomResource(this, 'DynamoDBResource', {
+ serviceToken: provider.serviceToken,
+ properties: {
+ version: Date.now().toString(),
+ },
+ });
+
+ return insertFn;
+ }
+
+ return undefined;
+ }
}
diff --git a/src/NextjsServer.ts b/src/NextjsServer.ts
index 86fcbf27..81efc3ae 100644
--- a/src/NextjsServer.ts
+++ b/src/NextjsServer.ts
@@ -116,6 +116,7 @@ export class NextjsServer extends Construct {
...getCommonFunctionProps(this),
code: Code.fromBucket(asset.bucket, asset.s3ObjectKey),
handler: 'index.handler',
+ description: 'Next.js Server Handler',
...this.props.lambda,
// `environment` needs to go after `this.props.lambda` b/c if
// `this.props.lambda.environment` is defined, it will override
diff --git a/src/constants.ts b/src/constants.ts
index 9e5b6d4e..18e0856e 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -9,5 +9,6 @@ export const NEXTJS_STATIC_DIR = 'assets';
export const NEXTJS_BUILD_DIR = '.open-next';
export const NEXTJS_CACHE_DIR = 'cache';
export const NEXTJS_BUILD_REVALIDATE_FN_DIR = 'revalidation-function';
+export const NEXTJS_BUILD_DYNAMODB_PROVIDER_FN_DIR = 'dynamodb-provider';
export const NEXTJS_BUILD_IMAGE_FN_DIR = 'image-optimization-function';
export const NEXTJS_BUILD_SERVER_FN_DIR = 'server-function';
diff --git a/yarn.lock b/yarn.lock
index 4d3684d8..38f3fbef 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2515,10 +2515,10 @@ available-typed-arrays@^1.0.5:
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
-aws-cdk-lib@2.93.0:
- version "2.93.0"
- resolved "https://registry.yarnpkg.com/aws-cdk-lib/-/aws-cdk-lib-2.93.0.tgz#545bc0072bc0f2e27cb0fecb0c9e54de29b10731"
- integrity sha512-kKbcKkts272Ju5xjGKI3pXTOpiJxW4OQbDF8Vmw/NIkkuJLo8GlRCFfeOfoN/hilvlYQgENA67GCgSWccbvu7w==
+aws-cdk-lib@2.99.1:
+ version "2.99.1"
+ resolved "https://registry.yarnpkg.com/aws-cdk-lib/-/aws-cdk-lib-2.99.1.tgz#e2cd3e091fa9c65ca2835ad9a4bb6564e6b42189"
+ integrity sha512-mUhuT2JTy3VyX9o9IOSuy7UYDimFHGnmpASwTb4rD10Hksb1dTqqN2BsXU5kogHakYevBD3vwYc87rOVso4M7Q==
dependencies:
"@aws-cdk/asset-awscli-v1" "^2.2.200"
"@aws-cdk/asset-kubectl-v20" "^2.1.2"