diff --git a/.projen/deps.json b/.projen/deps.json
index db054760..957aab33 100644
--- a/.projen/deps.json
+++ b/.projen/deps.json
@@ -49,7 +49,7 @@
},
{
"name": "aws-cdk-lib",
- "version": "2.93.0",
+ "version": "2.95.1",
"type": "build"
},
{
@@ -173,7 +173,7 @@
},
{
"name": "aws-cdk-lib",
- "version": "^2.93.0",
+ "version": "^2.95.1",
"type": "peer"
},
{
diff --git a/.projenrc.ts b/.projenrc.ts
index 2eb80301..76a5789b 100644
--- a/.projenrc.ts
+++ b/.projenrc.ts
@@ -39,7 +39,7 @@ const project = new awscdk.AwsCdkConstructLibrary({
tsconfigDev: { compilerOptions: { ...commonTscOptions } },
// dependency config
jsiiVersion: '~5.0.0',
- cdkVersion: '2.93.0',
+ cdkVersion: '2.95.1',
bundledDeps: ['esbuild'] /* Runtime dependencies of this module. */,
devDeps: [
'@aws-crypto/sha256-js',
diff --git a/API.md b/API.md
index 6febcbb8..ae7dc330 100644
--- a/API.md
+++ b/API.md
@@ -626,6 +626,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. |
@@ -671,6 +672,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
@@ -2099,7 +2112,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})
@@ -2184,8 +2197,10 @@ Any object.
| **Name** | **Type** | **Description** |
| --- | --- | --- |
| node
| constructs.Node
| The tree node. |
-| function
| aws-cdk-lib.aws_lambda_nodejs.NodejsFunction
| *No description.* |
| queue
| aws-cdk-lib.aws_sqs.Queue
| *No description.* |
+| queueFunction
| aws-cdk-lib.aws_lambda_nodejs.NodejsFunction
| *No description.* |
+| table
| aws-cdk-lib.aws_dynamodb.TableV2
| *No description.* |
+| tableFunction
| aws-cdk-lib.aws_lambda_nodejs.NodejsFunction
| *No description.* |
---
@@ -2201,23 +2216,43 @@ The tree node.
---
-##### `function`Required
+##### `queue`Required
+
+```typescript
+public readonly queue: Queue;
+```
+
+- *Type:* aws-cdk-lib.aws_sqs.Queue
+
+---
+
+##### `queueFunction`Required
```typescript
-public readonly function: NodejsFunction;
+public readonly queueFunction: NodejsFunction;
```
- *Type:* aws-cdk-lib.aws_lambda_nodejs.NodejsFunction
---
-##### `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: NodejsFunction;
+```
+
+- *Type:* aws-cdk-lib.aws_lambda_nodejs.NodejsFunction
---
diff --git a/package.json b/package.json
index b8645da7..90b131fd 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.95.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.95.1",
"constructs": "^10.0.5"
},
"dependencies": {
diff --git a/src/NextjsBuild.ts b/src/NextjsBuild.ts
index fc3ec25c..f3fb77ec 100644
--- a/src/NextjsBuild.ts
+++ b/src/NextjsBuild.ts
@@ -10,6 +10,7 @@ import {
NEXTJS_BUILD_SERVER_FN_DIR,
NEXTJS_CACHE_DIR,
NEXTJS_STATIC_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.
*/
diff --git a/src/NextjsRevalidation.ts b/src/NextjsRevalidation.ts
index 696123ab..d29b2954 100644
--- a/src/NextjsRevalidation.ts
+++ b/src/NextjsRevalidation.ts
@@ -1,10 +1,14 @@
+import * as fs from 'fs';
import { join } from 'path';
-import { Duration, Stack } from 'aws-cdk-lib';
+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 { FunctionOptions } from 'aws-cdk-lib/aws-lambda';
import { SqsEventSource } from 'aws-cdk-lib/aws-lambda-event-sources';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
+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 { NEXTJS_BUILD_INDEX_FILE } from './constants';
import { NextjsBaseProps } from './NextjsBase';
@@ -31,14 +35,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: NodejsFunction;
+ table: Table;
+ queueFunction: NodejsFunction;
+ tableFunction: NodejsFunction | undefined;
private props: NextjsRevalidationProps;
constructor(scope: Construct, id: string, props: NextjsRevalidationProps) {
@@ -46,10 +53,17 @@ export class NextjsRevalidation extends Construct {
this.props = props;
this.queue = this.createQueue();
- this.function = this.createFunction();
+ this.queueFunction = this.createFunction();
- // 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);
+
+ // todo: set these things
+ this.props.serverFunction.lambdaFunction.addEnvironment('CACHE_DYNAMO_TABLE', this.table.tableName);
+ 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);
}
@@ -78,7 +92,7 @@ export class NextjsRevalidation extends Construct {
private createFunction(): NodejsFunction {
const nodejsFnProps = getCommonNodejsFunctionProps(this);
- const fn = new NodejsFunction(this, 'Fn', {
+ const fn = new NodejsFunction(this, 'QueueRevalidationFn', {
...nodejsFnProps,
bundling: {
...nodejsFnProps.bundling,
@@ -94,13 +108,94 @@ export class NextjsRevalidation extends Construct {
},
},
// open-next revalidation-function
- // see: https://github.com/serverless-stack/open-next/blob/274d446ed7e940cfbe7ce05a21108f4c854ee37a/README.md?plain=1#L65
+ // see: https://github.com/sst/open-next/blob/c2b05e3a5f82de40da1181e11c087265983c349d/docs/pages/advanced/architecture.mdx#L150
entry: join(this.props.nextBuild.nextRevalidateFnDir, NEXTJS_BUILD_INDEX_FILE),
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, 'RevalidationTable', {
+ partitionKey: { name: 'tag', type: AttributeType.STRING },
+ sortKey: { name: 'path', type: AttributeType.STRING },
+ pointInTimeRecovery: true,
+ 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 nodejsFnProps = getCommonNodejsFunctionProps(this);
+ const insertFn = new NodejsFunction(this, 'DynamoDBProviderFn', {
+ ...nodejsFnProps,
+ bundling: {
+ ...nodejsFnProps.bundling,
+ commandHooks: {
+ afterBundling: () => [],
+ beforeBundling: (_inputDir, outputDir) => [
+ // copy non-bundled assets into zip. use node -e so cross-os compatible
+ `node -e "fs.cpSync('${fixPath(dynamodbProviderPath)}', '${fixPath(
+ outputDir
+ )}', { recursive: true, filter: (src) => !src.endsWith('index.mjs') })"`,
+ ],
+ beforeInstall: () => [],
+ },
+ },
+ // open-next dynamo-provider
+ // see: https://github.com/sst/open-next/blob/c2b05e3a5f82de40da1181e11c087265983c349d/docs/pages/advanced/architecture.mdx#L170
+ entry: join(dynamodbProviderPath, NEXTJS_BUILD_INDEX_FILE),
+ handler: 'index.handler',
+ initialPolicy: [
+ new PolicyStatement({
+ actions: ['dynamodb:BatchWriteItem', 'dynamodb:PutItem', 'dynamodb:DescribeTable'],
+ resources: [revalidationTable.tableArn],
+ }),
+ ],
+ environment: {
+ CACHE_DYNAMO_TABLE: revalidationTable.tableName,
+ },
+ description: 'Next.js revalidation DynamoDB provider',
+ timeout: Duration.minutes(15),
+ });
+
+ const provider = new Provider(this, 'RevalidationProvider', {
+ onEventHandler: insertFn,
+ logRetention: RetentionDays.ONE_DAY,
+ });
+
+ new CustomResource(this, 'RevalidationResource', {
+ serviceToken: provider.serviceToken,
+ properties: {
+ version: Date.now().toString(),
+ },
+ });
+
+ return insertFn;
+ }
+
+ return undefined;
+ }
}
diff --git a/src/NextjsServer.ts b/src/NextjsServer.ts
index b7915bb0..51098ce1 100644
--- a/src/NextjsServer.ts
+++ b/src/NextjsServer.ts
@@ -117,6 +117,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 c5c0153d..2ff202d0 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -9,6 +9,7 @@ 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';
export const NEXTJS_BUILD_INDEX_FILE = 'index.mjs';
diff --git a/yarn.lock b/yarn.lock
index 4d3684d8..f6814d67 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.95.1:
+ version "2.95.1"
+ resolved "https://registry.yarnpkg.com/aws-cdk-lib/-/aws-cdk-lib-2.95.1.tgz#624321c7c0b6e6417a9f1bdbc07bd7fc2fee5eee"
+ integrity sha512-FQlnW3+c1j2W7hmu+QMSiWnBgbW1Lhn1ZpBQ6cwYZa97rII1zlEyTowAfzQk6szPIzUhJv5xK03nWZtvCvpAWw==
dependencies:
"@aws-cdk/asset-awscli-v1" "^2.2.200"
"@aws-cdk/asset-kubectl-v20" "^2.1.2"