sst 2.42.0 → 2.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/constructs/AstroSite.d.ts +6 -32
- package/constructs/NextjsSite.d.ts +37 -127
- package/constructs/NextjsSite.js +188 -278
- package/constructs/RemixSite.d.ts +3 -0
- package/constructs/SolidStartSite.d.ts +3 -0
- package/constructs/SsrFunction.d.ts +4 -2
- package/constructs/SsrFunction.js +6 -1
- package/constructs/SsrSite.d.ts +16 -8
- package/constructs/SsrSite.js +30 -26
- package/constructs/SvelteKitSite.d.ts +3 -0
- package/package.json +2 -2
- package/support/ssr-warmer/index.mjs +52 -49
|
@@ -18,38 +18,9 @@ export declare class AstroSite extends SsrSite {
|
|
|
18
18
|
private static getBuildMeta;
|
|
19
19
|
private static getCFRoutingFunction;
|
|
20
20
|
protected plan(): {
|
|
21
|
-
cloudFrontFunctions?: Record<string,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}> | undefined;
|
|
25
|
-
edgeFunctions?: Record<string, {
|
|
26
|
-
constructId: string;
|
|
27
|
-
function: import("./EdgeFunction.js").EdgeFunctionProps;
|
|
28
|
-
}> | undefined;
|
|
29
|
-
origins: Record<string, {
|
|
30
|
-
type: "function";
|
|
31
|
-
constructId: string;
|
|
32
|
-
function: import("./SsrFunction.js").SsrFunctionProps;
|
|
33
|
-
injections?: string[] | undefined;
|
|
34
|
-
streaming?: boolean | undefined;
|
|
35
|
-
} | {
|
|
36
|
-
type: "image-optimization-function";
|
|
37
|
-
function: import("aws-cdk-lib/aws-lambda").FunctionProps;
|
|
38
|
-
} | {
|
|
39
|
-
type: "s3";
|
|
40
|
-
originPath?: string | undefined;
|
|
41
|
-
copy: {
|
|
42
|
-
from: string;
|
|
43
|
-
to: string;
|
|
44
|
-
cached: boolean;
|
|
45
|
-
versionedSubDir?: string | undefined;
|
|
46
|
-
}[];
|
|
47
|
-
} | {
|
|
48
|
-
type: "group";
|
|
49
|
-
primaryOriginName: string;
|
|
50
|
-
fallbackOriginName: string;
|
|
51
|
-
fallbackStatusCodes?: number[] | undefined;
|
|
52
|
-
}>;
|
|
21
|
+
cloudFrontFunctions?: Record<string, import("./SsrSite.js").CloudFrontFunctionConfig> | undefined;
|
|
22
|
+
edgeFunctions?: Record<string, import("./SsrSite.js").EdgeFunctionConfig> | undefined;
|
|
23
|
+
origins: Record<string, import("./SsrSite.js").FunctionOriginConfig | import("./SsrSite.js").ImageOptimizationFunctionOriginConfig | import("./SsrSite.js").S3OriginConfig | import("./SsrSite.js").OriginGroupConfig>;
|
|
53
24
|
edge: boolean;
|
|
54
25
|
behaviors: {
|
|
55
26
|
cacheType: "server" | "static";
|
|
@@ -64,6 +35,9 @@ export declare class AstroSite extends SsrSite {
|
|
|
64
35
|
allowedHeaders?: string[] | undefined;
|
|
65
36
|
} | undefined;
|
|
66
37
|
buildId?: string | undefined;
|
|
38
|
+
warmer?: {
|
|
39
|
+
function: string;
|
|
40
|
+
} | undefined;
|
|
67
41
|
};
|
|
68
42
|
getConstructMetadata(): {
|
|
69
43
|
data: {
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import { Construct } from "constructs";
|
|
2
|
-
import { Runtime, FunctionProps, Architecture } from "aws-cdk-lib/aws-lambda";
|
|
3
|
-
import { SsrSite, SsrSiteNormalizedProps, SsrSiteProps } from "./SsrSite.js";
|
|
2
|
+
import { Runtime, FunctionProps as CdkFunctionProps, Architecture } from "aws-cdk-lib/aws-lambda";
|
|
3
|
+
import { EdgeFunctionConfig, FunctionOriginConfig, SsrSite, SsrSiteNormalizedProps, SsrSiteProps } from "./SsrSite.js";
|
|
4
4
|
import { Size } from "./util/size.js";
|
|
5
5
|
import { Bucket } from "aws-cdk-lib/aws-s3";
|
|
6
|
-
|
|
6
|
+
type OpenNextS3Origin = {
|
|
7
|
+
type: "s3";
|
|
8
|
+
originPath: string;
|
|
9
|
+
copy: {
|
|
10
|
+
from: string;
|
|
11
|
+
to: string;
|
|
12
|
+
cached: boolean;
|
|
13
|
+
versionedSubDir?: string;
|
|
14
|
+
}[];
|
|
15
|
+
};
|
|
7
16
|
export interface NextjsSiteProps extends Omit<SsrSiteProps, "nodejs"> {
|
|
8
17
|
/**
|
|
9
18
|
* OpenNext version for building the Next.js site.
|
|
@@ -14,17 +23,6 @@ export interface NextjsSiteProps extends Omit<SsrSiteProps, "nodejs"> {
|
|
|
14
23
|
* ```
|
|
15
24
|
*/
|
|
16
25
|
openNextVersion?: string;
|
|
17
|
-
/**
|
|
18
|
-
* How the logs are stored in CloudWatch
|
|
19
|
-
* - "combined" - Logs from all routes are stored in the same log group.
|
|
20
|
-
* - "per-route" - Logs from each route are stored in a separate log group.
|
|
21
|
-
* @default "per-route"
|
|
22
|
-
* @example
|
|
23
|
-
* ```js
|
|
24
|
-
* logging: "combined",
|
|
25
|
-
* ```
|
|
26
|
-
*/
|
|
27
|
-
logging?: "combined" | "per-route";
|
|
28
26
|
/**
|
|
29
27
|
* The server function is deployed to Lambda in a single region. Alternatively, you can enable this option to deploy to Lambda@Edge.
|
|
30
28
|
* @default false
|
|
@@ -42,59 +40,20 @@ export interface NextjsSiteProps extends Omit<SsrSiteProps, "nodejs"> {
|
|
|
42
40
|
* ```
|
|
43
41
|
*/
|
|
44
42
|
memorySize?: number | Size;
|
|
45
|
-
};
|
|
46
|
-
openNext?: {
|
|
47
|
-
/**
|
|
48
|
-
* Specify a custom build output path for cases when running OpenNext from
|
|
49
|
-
* a monorepo with decentralized build output. This is passed to the
|
|
50
|
-
* `--build-output-path` flag of OpenNext.
|
|
51
|
-
* @default Default build output path
|
|
52
|
-
* @example
|
|
53
|
-
* ```js
|
|
54
|
-
* buildOutputPath: "dist/apps/example-app"
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
57
|
-
buildOutputPath?: string;
|
|
58
|
-
};
|
|
59
|
-
experimental?: {
|
|
60
|
-
/**
|
|
61
|
-
* Enable streaming. Currently an experimental feature in OpenNext.
|
|
62
|
-
* @default false
|
|
63
|
-
* @example
|
|
64
|
-
* ```js
|
|
65
|
-
* experimental: {
|
|
66
|
-
* streaming: true,
|
|
67
|
-
* }
|
|
68
|
-
* ```
|
|
69
|
-
*/
|
|
70
|
-
streaming?: boolean;
|
|
71
43
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* Note that it is possible to disable incremental cache while leaving on-demand revalidation enabled.
|
|
44
|
+
* If set to true, already computed image will return 304 Not Modified.
|
|
45
|
+
* This means that image needs to be immutable, the etag will be computed based on the image href, format and width and the next BUILD_ID.
|
|
75
46
|
* @default false
|
|
76
47
|
* @example
|
|
77
48
|
* ```js
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* }
|
|
81
|
-
* ```
|
|
82
|
-
*/
|
|
83
|
-
disableIncrementalCache?: boolean;
|
|
84
|
-
/**
|
|
85
|
-
* Disabling DynamoDB cache will cause on-demand revalidation by path (`revalidatePath`) and by cache tag (`revalidateTag`) to fail silently.
|
|
86
|
-
* @default false
|
|
87
|
-
* @example
|
|
88
|
-
* ```js
|
|
89
|
-
* experimental: {
|
|
90
|
-
* disableDynamoDBCache: true,
|
|
49
|
+
* imageOptimization: {
|
|
50
|
+
* staticImageOptimization: true,
|
|
91
51
|
* }
|
|
92
|
-
* ```
|
|
93
52
|
*/
|
|
94
|
-
|
|
53
|
+
staticImageOptimization?: boolean;
|
|
95
54
|
};
|
|
96
55
|
cdk?: SsrSiteProps["cdk"] & {
|
|
97
|
-
revalidation?: Pick<
|
|
56
|
+
revalidation?: Pick<CdkFunctionProps, "vpc" | "vpcSubnets">;
|
|
98
57
|
/**
|
|
99
58
|
* Override the CloudFront cache policy properties for responses from the
|
|
100
59
|
* server rendering Lambda.
|
|
@@ -145,8 +104,11 @@ export declare class NextjsSite extends SsrSite {
|
|
|
145
104
|
private appPathsManifest?;
|
|
146
105
|
private pagesManifest?;
|
|
147
106
|
private prerenderManifest?;
|
|
148
|
-
|
|
149
|
-
|
|
107
|
+
private openNextOutput?;
|
|
108
|
+
constructor(scope: Construct, id: string, props?: NextjsSiteProps);
|
|
109
|
+
private createFunctionOrigin;
|
|
110
|
+
private createEcsOrigin;
|
|
111
|
+
private createEdgeOrigin;
|
|
150
112
|
protected plan(bucket: Bucket): {
|
|
151
113
|
cloudFrontFunctions?: {
|
|
152
114
|
serverCfFunction: {
|
|
@@ -154,26 +116,11 @@ export declare class NextjsSite extends SsrSite {
|
|
|
154
116
|
injections: string[];
|
|
155
117
|
};
|
|
156
118
|
} | undefined;
|
|
157
|
-
edgeFunctions?:
|
|
158
|
-
edgeServer: {
|
|
159
|
-
constructId: string;
|
|
160
|
-
function: {
|
|
161
|
-
description: string;
|
|
162
|
-
bundle: string;
|
|
163
|
-
handler: string;
|
|
164
|
-
environment: {
|
|
165
|
-
CACHE_BUCKET_NAME: string;
|
|
166
|
-
CACHE_BUCKET_KEY_PREFIX: string;
|
|
167
|
-
CACHE_BUCKET_REGION: string;
|
|
168
|
-
};
|
|
169
|
-
layers: import("aws-cdk-lib/aws-lambda").ILayerVersion[] | undefined;
|
|
170
|
-
};
|
|
171
|
-
};
|
|
172
|
-
} | undefined;
|
|
119
|
+
edgeFunctions?: Record<string, EdgeFunctionConfig> | undefined;
|
|
173
120
|
origins: {
|
|
121
|
+
s3: OpenNextS3Origin;
|
|
174
122
|
imageOptimizer: {
|
|
175
123
|
type: "image-optimization-function";
|
|
176
|
-
constructId: string;
|
|
177
124
|
function: {
|
|
178
125
|
description: string;
|
|
179
126
|
handler: string;
|
|
@@ -181,73 +128,39 @@ export declare class NextjsSite extends SsrSite {
|
|
|
181
128
|
runtime: Runtime;
|
|
182
129
|
architecture: Architecture;
|
|
183
130
|
environment: {
|
|
131
|
+
OPENNEXT_STATIC_ETAG?: string | undefined;
|
|
184
132
|
BUCKET_NAME: string;
|
|
185
133
|
BUCKET_KEY_PREFIX: string;
|
|
186
134
|
};
|
|
135
|
+
permissions: string[];
|
|
187
136
|
memorySize: number;
|
|
188
137
|
};
|
|
189
138
|
};
|
|
190
|
-
|
|
191
|
-
type: "s3";
|
|
192
|
-
originPath: string;
|
|
193
|
-
copy: ({
|
|
194
|
-
from: string;
|
|
195
|
-
to: string;
|
|
196
|
-
cached: true;
|
|
197
|
-
versionedSubDir: string;
|
|
198
|
-
} | {
|
|
199
|
-
from: string;
|
|
200
|
-
to: string;
|
|
201
|
-
cached: false;
|
|
202
|
-
versionedSubDir?: undefined;
|
|
203
|
-
})[];
|
|
204
|
-
};
|
|
205
|
-
regionalServer?: {
|
|
206
|
-
type: "function";
|
|
207
|
-
constructId: string;
|
|
208
|
-
function: {
|
|
209
|
-
description: string;
|
|
210
|
-
bundle: string;
|
|
211
|
-
handler: string;
|
|
212
|
-
environment: {
|
|
213
|
-
CACHE_BUCKET_NAME: string;
|
|
214
|
-
CACHE_BUCKET_KEY_PREFIX: string;
|
|
215
|
-
CACHE_BUCKET_REGION: string;
|
|
216
|
-
};
|
|
217
|
-
layers: import("aws-cdk-lib/aws-lambda").ILayerVersion[] | undefined;
|
|
218
|
-
};
|
|
219
|
-
streaming: boolean | undefined;
|
|
220
|
-
injections: string[];
|
|
221
|
-
} | undefined;
|
|
139
|
+
default: FunctionOriginConfig;
|
|
222
140
|
};
|
|
223
141
|
edge: boolean;
|
|
224
142
|
behaviors: {
|
|
225
143
|
cacheType: "server" | "static";
|
|
226
144
|
pattern?: string | undefined;
|
|
227
|
-
origin: "
|
|
145
|
+
origin: "default" | "s3" | "imageOptimizer";
|
|
228
146
|
allowedMethods?: import("aws-cdk-lib/aws-cloudfront").AllowedMethods | undefined;
|
|
229
147
|
cfFunction?: "serverCfFunction" | undefined;
|
|
230
|
-
edgeFunction?:
|
|
148
|
+
edgeFunction?: string | undefined;
|
|
231
149
|
}[];
|
|
232
150
|
errorResponses?: import("aws-cdk-lib/aws-cloudfront").ErrorResponse[] | undefined;
|
|
233
151
|
serverCachePolicy?: {
|
|
234
152
|
allowedHeaders?: string[] | undefined;
|
|
235
153
|
} | undefined;
|
|
236
154
|
buildId?: string | undefined;
|
|
155
|
+
warmer?: {
|
|
156
|
+
function: string;
|
|
157
|
+
} | undefined;
|
|
237
158
|
};
|
|
238
|
-
private
|
|
159
|
+
private setMiddlewareEnv;
|
|
239
160
|
private createRevalidationQueue;
|
|
240
161
|
private createRevalidationTable;
|
|
241
162
|
getConstructMetadata(): {
|
|
242
|
-
type: "NextjsSite";
|
|
243
163
|
data: {
|
|
244
|
-
routes: {
|
|
245
|
-
logGroupPrefix: string;
|
|
246
|
-
data: {
|
|
247
|
-
route: string;
|
|
248
|
-
logGroupPath: string;
|
|
249
|
-
}[];
|
|
250
|
-
} | undefined;
|
|
251
164
|
mode: "placeholder" | "deployed";
|
|
252
165
|
path: string;
|
|
253
166
|
runtime: "nodejs16.x" | "nodejs18.x" | "nodejs20.x";
|
|
@@ -258,23 +171,20 @@ export declare class NextjsSite extends SsrSite {
|
|
|
258
171
|
secrets: string[];
|
|
259
172
|
prefetchSecrets: boolean | undefined;
|
|
260
173
|
};
|
|
174
|
+
type: "NextjsSite";
|
|
261
175
|
};
|
|
262
|
-
private removeSourcemaps;
|
|
263
176
|
private useRoutes;
|
|
264
177
|
private useRoutesManifest;
|
|
265
178
|
private useAppPathRoutesManifest;
|
|
266
179
|
private useAppPathsManifest;
|
|
267
180
|
private usePagesManifest;
|
|
268
181
|
private usePrerenderManifest;
|
|
269
|
-
private
|
|
270
|
-
private
|
|
182
|
+
private useCloudFrontFunctionCacheHeaderKey;
|
|
183
|
+
private useCloudfrontGeoHeadersInjection;
|
|
271
184
|
private getBuildId;
|
|
272
185
|
private getSourcemapForAppRoute;
|
|
273
186
|
private getSourcemapForPagesRoute;
|
|
274
|
-
private isPerRouteLoggingEnabled;
|
|
275
187
|
private handleMissingSourcemap;
|
|
276
|
-
private disableDefaultLogging;
|
|
277
|
-
private uploadSourcemaps;
|
|
278
188
|
private static buildCloudWatchRouteName;
|
|
279
189
|
private static buildCloudWatchRouteHash;
|
|
280
190
|
static _test: {
|
package/constructs/NextjsSite.js
CHANGED
|
@@ -1,35 +1,20 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import zlib from "zlib";
|
|
4
3
|
import crypto from "crypto";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { Code, Runtime, Function as CdkFunction, Architecture, LayerVersion, } from "aws-cdk-lib/aws-lambda";
|
|
4
|
+
import { Duration as CdkDuration, RemovalPolicy, CustomResource, Fn, } from "aws-cdk-lib/core";
|
|
5
|
+
import { Code, Runtime, Function as CdkFunction, Architecture, } from "aws-cdk-lib/aws-lambda";
|
|
8
6
|
import { AttributeType, Billing, TableV2 as Table, } from "aws-cdk-lib/aws-dynamodb";
|
|
9
7
|
import { Provider } from "aws-cdk-lib/custom-resources";
|
|
10
8
|
import { Queue } from "aws-cdk-lib/aws-sqs";
|
|
11
9
|
import { SqsEventSource } from "aws-cdk-lib/aws-lambda-event-sources";
|
|
12
10
|
import { Stack } from "./Stack.js";
|
|
13
|
-
import { SsrSite } from "./SsrSite.js";
|
|
11
|
+
import { SsrSite, } from "./SsrSite.js";
|
|
14
12
|
import { toCdkSize } from "./util/size.js";
|
|
15
|
-
import {
|
|
13
|
+
import { PolicyStatement } from "aws-cdk-lib/aws-iam";
|
|
16
14
|
import { RetentionDays } from "aws-cdk-lib/aws-logs";
|
|
17
15
|
import { VisibleError } from "../error.js";
|
|
18
|
-
import { Asset } from "aws-cdk-lib/aws-s3-assets";
|
|
19
|
-
import { useFunctions } from "./Function.js";
|
|
20
|
-
import { useDeferredTasks } from "./deferred_task.js";
|
|
21
16
|
import { Logger } from "../logger.js";
|
|
22
|
-
const
|
|
23
|
-
const DEFAULT_OPEN_NEXT_VERSION = "2.3.8";
|
|
24
|
-
const DEFAULT_CACHE_POLICY_ALLOWED_HEADERS = [
|
|
25
|
-
"accept",
|
|
26
|
-
"rsc",
|
|
27
|
-
"next-router-prefetch",
|
|
28
|
-
"next-router-state-tree",
|
|
29
|
-
"next-url",
|
|
30
|
-
"x-prerender-bypass",
|
|
31
|
-
"x-prerender-revalidate",
|
|
32
|
-
];
|
|
17
|
+
const DEFAULT_OPEN_NEXT_VERSION = "3.0.2";
|
|
33
18
|
/**
|
|
34
19
|
* The `NextjsSite` construct is a higher level CDK construct that makes it easy to create a Next.js app.
|
|
35
20
|
* @example
|
|
@@ -48,118 +33,135 @@ export class NextjsSite extends SsrSite {
|
|
|
48
33
|
appPathsManifest;
|
|
49
34
|
pagesManifest;
|
|
50
35
|
prerenderManifest;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
logging: rawProps?.logging ?? "per-route",
|
|
54
|
-
experimental: {
|
|
55
|
-
streaming: rawProps?.experimental?.streaming ?? false,
|
|
56
|
-
disableDynamoDBCache: rawProps?.experimental?.disableDynamoDBCache ?? false,
|
|
57
|
-
disableIncrementalCache: rawProps?.experimental?.disableIncrementalCache ?? false,
|
|
58
|
-
...rawProps?.experimental,
|
|
59
|
-
},
|
|
60
|
-
...rawProps,
|
|
61
|
-
};
|
|
36
|
+
openNextOutput;
|
|
37
|
+
constructor(scope, id, props = {}) {
|
|
62
38
|
super(scope, id, {
|
|
63
39
|
buildCommand: [
|
|
64
40
|
"npx",
|
|
65
41
|
"--yes",
|
|
66
42
|
`open-next@${props?.openNextVersion ?? DEFAULT_OPEN_NEXT_VERSION}`,
|
|
67
43
|
"build",
|
|
68
|
-
...(props.openNext?.buildOutputPath
|
|
69
|
-
? ["--build-output-path", props.openNext.buildOutputPath]
|
|
70
|
-
: []),
|
|
71
|
-
...(props.experimental.streaming ? ["--streaming"] : []),
|
|
72
|
-
...(props.experimental.disableDynamoDBCache
|
|
73
|
-
? ["--dangerously-disable-dynamodb-cache"]
|
|
74
|
-
: []),
|
|
75
|
-
...(props.experimental.disableIncrementalCache
|
|
76
|
-
? ["--dangerously-disable-incremental-cache"]
|
|
77
|
-
: []),
|
|
78
44
|
].join(" "),
|
|
79
45
|
...props,
|
|
80
46
|
});
|
|
47
|
+
const disableIncrementalCache = this.openNextOutput?.additionalProps?.disableIncrementalCache ?? false;
|
|
48
|
+
const disableTagCache = this.openNextOutput?.additionalProps?.disableTagCache ?? false;
|
|
81
49
|
this.handleMissingSourcemap();
|
|
82
|
-
if (this.
|
|
83
|
-
|
|
84
|
-
this.uploadSourcemaps();
|
|
50
|
+
if (this.openNextOutput?.edgeFunctions?.middleware) {
|
|
51
|
+
this.setMiddlewareEnv();
|
|
85
52
|
}
|
|
86
|
-
if (!
|
|
53
|
+
if (!disableIncrementalCache) {
|
|
87
54
|
this.createRevalidationQueue();
|
|
88
|
-
if (!
|
|
55
|
+
if (!disableTagCache) {
|
|
89
56
|
this.createRevalidationTable();
|
|
90
57
|
}
|
|
91
58
|
}
|
|
92
59
|
}
|
|
93
|
-
|
|
94
|
-
|
|
60
|
+
createFunctionOrigin(fn, key, bucket) {
|
|
61
|
+
const { path: sitePath, environment, cdk } = this.props;
|
|
62
|
+
const baseServerConfig = {
|
|
63
|
+
description: "Next.js Server",
|
|
64
|
+
environment: {
|
|
65
|
+
CACHE_BUCKET_NAME: bucket.bucketName,
|
|
66
|
+
CACHE_BUCKET_KEY_PREFIX: "_cache",
|
|
67
|
+
CACHE_BUCKET_REGION: Stack.of(this).region,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
type: "function",
|
|
72
|
+
constructId: `${key}ServerFunction`,
|
|
73
|
+
function: {
|
|
74
|
+
...baseServerConfig,
|
|
75
|
+
handler: fn.handler,
|
|
76
|
+
bundle: path.join(sitePath, fn.bundle),
|
|
77
|
+
runtime: "nodejs18.x",
|
|
78
|
+
architecture: Architecture.ARM_64,
|
|
79
|
+
memorySize: 1536,
|
|
80
|
+
environment: {
|
|
81
|
+
...environment,
|
|
82
|
+
...baseServerConfig.environment,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
streaming: fn.streaming,
|
|
86
|
+
injections: [],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
createEcsOrigin(ecs, key, bucket) {
|
|
90
|
+
throw new Error("Ecs origin are not supported yet");
|
|
95
91
|
}
|
|
96
|
-
|
|
97
|
-
const { path: sitePath,
|
|
98
|
-
const
|
|
99
|
-
const serverConfig = {
|
|
100
|
-
description: "Next.js server",
|
|
101
|
-
bundle: path.join(sitePath, ".open-next", "server-function"),
|
|
102
|
-
handler: "index.handler",
|
|
92
|
+
createEdgeOrigin(fn, key, bucket) {
|
|
93
|
+
const { path: sitePath, cdk, environment } = this.props;
|
|
94
|
+
const baseServerConfig = {
|
|
103
95
|
environment: {
|
|
104
96
|
CACHE_BUCKET_NAME: bucket.bucketName,
|
|
105
97
|
CACHE_BUCKET_KEY_PREFIX: "_cache",
|
|
106
98
|
CACHE_BUCKET_REGION: Stack.of(this).region,
|
|
107
99
|
},
|
|
108
|
-
layers: this.isPerRouteLoggingEnabled()
|
|
109
|
-
? [
|
|
110
|
-
LayerVersion.fromLayerVersionArn(this, "SSTExtension", cdk?.server?.architecture?.name === Architecture.X86_64.name
|
|
111
|
-
? `arn:aws:lambda:${stack.region}:226609089145:layer:sst-extension-amd64:${LAYER_VERSION}`
|
|
112
|
-
: `arn:aws:lambda:${stack.region}:226609089145:layer:sst-extension-arm64:${LAYER_VERSION}`),
|
|
113
|
-
]
|
|
114
|
-
: undefined,
|
|
115
100
|
};
|
|
116
|
-
|
|
101
|
+
return {
|
|
102
|
+
constructId: `${key}EdgeFunction`,
|
|
103
|
+
function: {
|
|
104
|
+
handler: fn.handler,
|
|
105
|
+
bundle: path.join(sitePath, fn.bundle),
|
|
106
|
+
runtime: "nodejs18.x",
|
|
107
|
+
memorySize: 1024,
|
|
108
|
+
environment: {
|
|
109
|
+
...environment,
|
|
110
|
+
...baseServerConfig.environment,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
plan(bucket) {
|
|
116
|
+
const { path: sitePath } = this.props;
|
|
117
|
+
const imageOptimization = this.props.imageOptimization;
|
|
118
|
+
const openNextOutputPath = path.join(sitePath ?? ".", ".open-next", "open-next.output.json");
|
|
119
|
+
if (!fs.existsSync(openNextOutputPath)) {
|
|
120
|
+
throw new VisibleError(`Failed to load ".open-next/output.json" for the "${this.id}" site.`);
|
|
121
|
+
}
|
|
122
|
+
const openNextOutput = JSON.parse(fs.readFileSync(openNextOutputPath).toString());
|
|
123
|
+
this.openNextOutput = openNextOutput;
|
|
124
|
+
const imageOpt = openNextOutput.origins
|
|
125
|
+
.imageOptimizer;
|
|
126
|
+
const defaultOrigin = openNextOutput.origins.default;
|
|
127
|
+
const remainingOrigins = Object.entries(openNextOutput.origins).filter(([key, value]) => {
|
|
128
|
+
const result = key !== "imageOptimizer" && key !== "default" && key !== "s3";
|
|
129
|
+
return result;
|
|
130
|
+
});
|
|
131
|
+
const edgeFunctions = Object.entries(openNextOutput.edgeFunctions).reduce((acc, [key, value]) => {
|
|
132
|
+
return { ...acc, [key]: this.createEdgeOrigin(value, key, bucket) };
|
|
133
|
+
}, {});
|
|
117
134
|
return this.validatePlan({
|
|
118
|
-
edge:
|
|
135
|
+
edge: false,
|
|
119
136
|
cloudFrontFunctions: {
|
|
120
137
|
serverCfFunction: {
|
|
121
138
|
constructId: "CloudFrontFunction",
|
|
122
139
|
injections: [
|
|
123
140
|
this.useCloudFrontFunctionHostHeaderInjection(),
|
|
124
|
-
this.
|
|
141
|
+
this.useCloudFrontFunctionCacheHeaderKey(),
|
|
142
|
+
this.useCloudfrontGeoHeadersInjection(),
|
|
125
143
|
],
|
|
126
144
|
},
|
|
127
145
|
},
|
|
128
|
-
edgeFunctions
|
|
129
|
-
? {
|
|
130
|
-
edgeServer: {
|
|
131
|
-
constructId: "ServerFunction",
|
|
132
|
-
function: serverConfig,
|
|
133
|
-
},
|
|
134
|
-
}
|
|
135
|
-
: undefined,
|
|
146
|
+
edgeFunctions,
|
|
136
147
|
origins: {
|
|
137
|
-
|
|
138
|
-
? {}
|
|
139
|
-
: {
|
|
140
|
-
regionalServer: {
|
|
141
|
-
type: "function",
|
|
142
|
-
constructId: "ServerFunction",
|
|
143
|
-
function: serverConfig,
|
|
144
|
-
streaming: experimental?.streaming,
|
|
145
|
-
injections: this.isPerRouteLoggingEnabled()
|
|
146
|
-
? [this.useServerFunctionPerRouteLoggingInjection()]
|
|
147
|
-
: [],
|
|
148
|
-
},
|
|
149
|
-
}),
|
|
148
|
+
s3: openNextOutput.origins.s3,
|
|
150
149
|
imageOptimizer: {
|
|
151
150
|
type: "image-optimization-function",
|
|
152
|
-
constructId: "ImageFunction",
|
|
153
151
|
function: {
|
|
154
|
-
description: "Next.js
|
|
155
|
-
handler:
|
|
156
|
-
code: Code.fromAsset(path.join(sitePath,
|
|
152
|
+
description: "Next.js Image Optimization Function",
|
|
153
|
+
handler: imageOpt.handler,
|
|
154
|
+
code: Code.fromAsset(path.join(sitePath, imageOpt.bundle)),
|
|
157
155
|
runtime: Runtime.NODEJS_18_X,
|
|
158
156
|
architecture: Architecture.ARM_64,
|
|
159
157
|
environment: {
|
|
160
158
|
BUCKET_NAME: bucket.bucketName,
|
|
161
159
|
BUCKET_KEY_PREFIX: "_assets",
|
|
160
|
+
...(this.props.imageOptimization?.staticImageOptimization
|
|
161
|
+
? { OPENNEXT_STATIC_ETAG: "true" }
|
|
162
|
+
: {}),
|
|
162
163
|
},
|
|
164
|
+
permissions: ["s3"],
|
|
163
165
|
memorySize: imageOptimization?.memorySize
|
|
164
166
|
? typeof imageOptimization.memorySize === "string"
|
|
165
167
|
? toCdkSize(imageOptimization.memorySize).toMebibytes()
|
|
@@ -167,98 +169,55 @@ export class NextjsSite extends SsrSite {
|
|
|
167
169
|
: 1536,
|
|
168
170
|
},
|
|
169
171
|
},
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
},
|
|
180
|
-
{ from: ".open-next/cache", to: "_cache", cached: false },
|
|
181
|
-
],
|
|
182
|
-
},
|
|
172
|
+
default: defaultOrigin.type === "ecs"
|
|
173
|
+
? this.createEcsOrigin(defaultOrigin, "default", bucket)
|
|
174
|
+
: this.createFunctionOrigin(defaultOrigin, "default", bucket),
|
|
175
|
+
...Object.fromEntries(remainingOrigins.map(([key, value]) => [
|
|
176
|
+
key,
|
|
177
|
+
value.type === "ecs"
|
|
178
|
+
? this.createEcsOrigin(value, key, bucket)
|
|
179
|
+
: this.createFunctionOrigin(value, key, bucket),
|
|
180
|
+
])),
|
|
183
181
|
},
|
|
184
|
-
behaviors:
|
|
185
|
-
|
|
186
|
-
?
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
cfFunction: "serverCfFunction",
|
|
190
|
-
edgeFunction: "edgeServer",
|
|
191
|
-
origin: "s3",
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
cacheType: "server",
|
|
195
|
-
pattern: this.prefixPattern("api/*"),
|
|
196
|
-
cfFunction: "serverCfFunction",
|
|
197
|
-
edgeFunction: "edgeServer",
|
|
198
|
-
origin: "s3",
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
cacheType: "server",
|
|
202
|
-
pattern: this.prefixPattern("_next/data/*"),
|
|
203
|
-
cfFunction: "serverCfFunction",
|
|
204
|
-
edgeFunction: "edgeServer",
|
|
205
|
-
origin: "s3",
|
|
206
|
-
},
|
|
207
|
-
]
|
|
208
|
-
: [
|
|
209
|
-
{
|
|
210
|
-
cacheType: "server",
|
|
211
|
-
cfFunction: "serverCfFunction",
|
|
212
|
-
origin: "regionalServer",
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
cacheType: "server",
|
|
216
|
-
pattern: this.prefixPattern("api/*"),
|
|
217
|
-
cfFunction: "serverCfFunction",
|
|
218
|
-
origin: "regionalServer",
|
|
219
|
-
},
|
|
220
|
-
{
|
|
221
|
-
cacheType: "server",
|
|
222
|
-
pattern: this.prefixPattern("_next/data/*"),
|
|
223
|
-
cfFunction: "serverCfFunction",
|
|
224
|
-
origin: "regionalServer",
|
|
225
|
-
},
|
|
226
|
-
]),
|
|
227
|
-
{
|
|
228
|
-
cacheType: "server",
|
|
229
|
-
pattern: this.prefixPattern("_next/image*"),
|
|
182
|
+
behaviors: openNextOutput.behaviors.map((behavior) => {
|
|
183
|
+
return {
|
|
184
|
+
pattern: behavior.pattern === "*" ? undefined : behavior.pattern,
|
|
185
|
+
origin: behavior.origin,
|
|
186
|
+
cacheType: behavior.origin === "s3" ? "static" : "server",
|
|
230
187
|
cfFunction: "serverCfFunction",
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
: item),
|
|
241
|
-
origin: "s3",
|
|
242
|
-
})),
|
|
243
|
-
],
|
|
188
|
+
edgeFunction: behavior.edgeFunction ?? "",
|
|
189
|
+
};
|
|
190
|
+
}),
|
|
191
|
+
buildId: this.getBuildId(),
|
|
192
|
+
warmer: openNextOutput.additionalProps?.warmer
|
|
193
|
+
? {
|
|
194
|
+
function: path.join(sitePath, openNextOutput.additionalProps.warmer.bundle),
|
|
195
|
+
}
|
|
196
|
+
: undefined,
|
|
244
197
|
serverCachePolicy: {
|
|
245
|
-
allowedHeaders:
|
|
198
|
+
allowedHeaders: ["x-open-next-cache-key"],
|
|
246
199
|
},
|
|
247
|
-
buildId: this.getBuildId(),
|
|
248
200
|
});
|
|
249
201
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
202
|
+
setMiddlewareEnv() {
|
|
203
|
+
const origins = this.serverFunctions.reduce((acc, server) => {
|
|
204
|
+
return {
|
|
205
|
+
...acc,
|
|
206
|
+
[server.function
|
|
207
|
+
? server.id.replace("ServerFunction", "")
|
|
208
|
+
: server.id.replace("ServerContainer", "")]: {
|
|
209
|
+
host: Fn.parseDomainName(server.url ?? ""),
|
|
210
|
+
port: 443,
|
|
211
|
+
protocol: "https",
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}, {});
|
|
215
|
+
this.edgeFunctions?.middleware?.addEnvironment("OPEN_NEXT_ORIGIN", Fn.toJsonString(origins));
|
|
256
216
|
}
|
|
257
217
|
createRevalidationQueue() {
|
|
258
218
|
if (!this.serverFunction)
|
|
259
219
|
return;
|
|
260
220
|
const { cdk } = this.props;
|
|
261
|
-
const server = this.serverFunction;
|
|
262
221
|
const queue = new Queue(this, "RevalidationQueue", {
|
|
263
222
|
fifo: true,
|
|
264
223
|
receiveMessageWaitTime: CdkDuration.seconds(20),
|
|
@@ -272,16 +231,17 @@ export class NextjsSite extends SsrSite {
|
|
|
272
231
|
...cdk?.revalidation,
|
|
273
232
|
});
|
|
274
233
|
consumer.addEventSource(new SqsEventSource(queue, { batchSize: 5 }));
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
234
|
+
this.serverFunctions.forEach((server) => {
|
|
235
|
+
// Allow server to send messages to the queue
|
|
236
|
+
server.addEnvironment("REVALIDATION_QUEUE_URL", queue.queueUrl);
|
|
237
|
+
server.addEnvironment("REVALIDATION_QUEUE_REGION", Stack.of(this).region);
|
|
238
|
+
queue.grantSendMessages(server.role);
|
|
239
|
+
});
|
|
279
240
|
}
|
|
280
241
|
createRevalidationTable() {
|
|
281
242
|
if (!this.serverFunction)
|
|
282
243
|
return;
|
|
283
244
|
const { path: sitePath } = this.props;
|
|
284
|
-
const server = this.serverFunction;
|
|
285
245
|
const table = new Table(this, "RevalidationTable", {
|
|
286
246
|
partitionKey: { name: "tag", type: AttributeType.STRING },
|
|
287
247
|
sortKey: { name: "path", type: AttributeType.STRING },
|
|
@@ -296,8 +256,10 @@ export class NextjsSite extends SsrSite {
|
|
|
296
256
|
],
|
|
297
257
|
removalPolicy: RemovalPolicy.DESTROY,
|
|
298
258
|
});
|
|
299
|
-
server
|
|
300
|
-
|
|
259
|
+
this.serverFunctions.forEach((server) => {
|
|
260
|
+
server?.addEnvironment("CACHE_DYNAMO_TABLE", table.tableName);
|
|
261
|
+
table.grantReadWriteData(server.role);
|
|
262
|
+
});
|
|
301
263
|
const dynamodbProviderPath = path.join(sitePath, ".open-next", "dynamodb-provider");
|
|
302
264
|
if (fs.existsSync(dynamodbProviderPath)) {
|
|
303
265
|
// Provision 128MB of memory for every 4,000 prerendered routes,
|
|
@@ -338,35 +300,11 @@ export class NextjsSite extends SsrSite {
|
|
|
338
300
|
}
|
|
339
301
|
}
|
|
340
302
|
getConstructMetadata() {
|
|
341
|
-
const metadata = this.getConstructMetadataBase();
|
|
342
303
|
return {
|
|
343
|
-
...metadata,
|
|
344
304
|
type: "NextjsSite",
|
|
345
|
-
|
|
346
|
-
...metadata.data,
|
|
347
|
-
routes: this.isPerRouteLoggingEnabled()
|
|
348
|
-
? {
|
|
349
|
-
logGroupPrefix: `/sst/lambda/${this.serverFunction.functionName}`,
|
|
350
|
-
data: this.useRoutes().map(({ route, logGroupPath }) => ({
|
|
351
|
-
route,
|
|
352
|
-
logGroupPath,
|
|
353
|
-
})),
|
|
354
|
-
}
|
|
355
|
-
: undefined,
|
|
356
|
-
},
|
|
305
|
+
...this.getConstructMetadataBase(),
|
|
357
306
|
};
|
|
358
307
|
}
|
|
359
|
-
removeSourcemaps() {
|
|
360
|
-
const { path: sitePath } = this.props;
|
|
361
|
-
const files = globSync("**/*.js.map", {
|
|
362
|
-
cwd: path.join(sitePath, ".open-next", "server-function"),
|
|
363
|
-
nodir: true,
|
|
364
|
-
dot: true,
|
|
365
|
-
});
|
|
366
|
-
for (const file of files) {
|
|
367
|
-
fs.rmSync(path.join(sitePath, ".open-next", "server-function", file));
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
308
|
useRoutes() {
|
|
371
309
|
if (this._routes)
|
|
372
310
|
return this._routes;
|
|
@@ -510,38 +448,57 @@ export class NextjsSite extends SsrSite {
|
|
|
510
448
|
Logger.debug("Failed to load prerender-manifest.json", e);
|
|
511
449
|
}
|
|
512
450
|
}
|
|
513
|
-
|
|
451
|
+
// This function is used to improve cache hit ratio by setting the cache key based on the request headers and the path
|
|
452
|
+
// next/image only need the accept header, and this header is not useful for the rest of the query
|
|
453
|
+
useCloudFrontFunctionCacheHeaderKey() {
|
|
514
454
|
return `
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
455
|
+
function getHeader(key) {
|
|
456
|
+
var header = request.headers[key];
|
|
457
|
+
if(header) {
|
|
458
|
+
if(header.multiValue){
|
|
459
|
+
return header.multiValue.map((header) => header.value).join(",");
|
|
460
|
+
}
|
|
461
|
+
if(header.value){
|
|
462
|
+
return header.value;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return ""
|
|
466
|
+
}
|
|
467
|
+
var cacheKey = "";
|
|
468
|
+
if(request.uri.startsWith("/_next/image")) {
|
|
469
|
+
cacheKey = getHeader("accept");
|
|
470
|
+
}else {
|
|
471
|
+
cacheKey = getHeader("rsc") + getHeader("next-router-prefetch") + getHeader("next-router-state-tree") + getHeader("next-url") + getHeader("x-prerender-revalidate");
|
|
472
|
+
}
|
|
473
|
+
if(request.cookies["__prerender_bypass"]) {
|
|
474
|
+
cacheKey += request.cookies["__prerender_bypass"] ? request.cookies["__prerender_bypass"].value : "";
|
|
534
475
|
}
|
|
535
|
-
|
|
476
|
+
var crypto = require('crypto');
|
|
477
|
+
|
|
478
|
+
var hashedKey = crypto.createHash('md5').update(cacheKey).digest('hex');
|
|
479
|
+
request.headers["x-open-next-cache-key"] = {value: hashedKey};
|
|
480
|
+
`;
|
|
536
481
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
// header "x-prerender-bypass", and add it to cache policy's allowed headers.
|
|
482
|
+
// Inject the CloudFront viewer country, region, latitude, and longitude headers into the request headers
|
|
483
|
+
// for OpenNext to use them
|
|
484
|
+
useCloudfrontGeoHeadersInjection() {
|
|
541
485
|
return `
|
|
542
|
-
if
|
|
543
|
-
request.headers["x-
|
|
544
|
-
}
|
|
486
|
+
if(request.headers["cloudfront-viewer-city"]) {
|
|
487
|
+
request.headers["x-open-next-city"] = request.headers["cloudfront-viewer-city"];
|
|
488
|
+
}
|
|
489
|
+
if(request.headers["cloudfront-viewer-country"]) {
|
|
490
|
+
request.headers["x-open-next-country"] = request.headers["cloudfront-viewer-country"];
|
|
491
|
+
}
|
|
492
|
+
if(request.headers["cloudfront-viewer-region"]) {
|
|
493
|
+
request.headers["x-open-next-region"] = request.headers["cloudfront-viewer-region"];
|
|
494
|
+
}
|
|
495
|
+
if(request.headers["cloudfront-viewer-latitude"]) {
|
|
496
|
+
request.headers["x-open-next-latitude"] = request.headers["cloudfront-viewer-latitude"];
|
|
497
|
+
}
|
|
498
|
+
if(request.headers["cloudfront-viewer-longitude"]) {
|
|
499
|
+
request.headers["x-open-next-longitude"] = request.headers["cloudfront-viewer-longitude"];
|
|
500
|
+
}
|
|
501
|
+
`;
|
|
545
502
|
}
|
|
546
503
|
getBuildId() {
|
|
547
504
|
const { path: sitePath } = this.props;
|
|
@@ -605,11 +562,6 @@ if (request.cookies["__prerender_bypass"]) {
|
|
|
605
562
|
return;
|
|
606
563
|
return sourcemapPath;
|
|
607
564
|
}
|
|
608
|
-
isPerRouteLoggingEnabled() {
|
|
609
|
-
return (!this.doNotDeploy &&
|
|
610
|
-
!this.props.edge &&
|
|
611
|
-
this.props.logging === "per-route");
|
|
612
|
-
}
|
|
613
565
|
handleMissingSourcemap() {
|
|
614
566
|
if (this.doNotDeploy || this.props.edge)
|
|
615
567
|
return;
|
|
@@ -618,48 +570,6 @@ if (request.cookies["__prerender_bypass"]) {
|
|
|
618
570
|
return;
|
|
619
571
|
this.serverFunction._overrideMissingSourcemap();
|
|
620
572
|
}
|
|
621
|
-
disableDefaultLogging() {
|
|
622
|
-
const stack = Stack.of(this);
|
|
623
|
-
const server = this.serverFunction;
|
|
624
|
-
const policy = new Policy(this, "DisableLoggingPolicy", {
|
|
625
|
-
statements: [
|
|
626
|
-
new PolicyStatement({
|
|
627
|
-
effect: Effect.DENY,
|
|
628
|
-
actions: [
|
|
629
|
-
"logs:CreateLogGroup",
|
|
630
|
-
"logs:CreateLogStream",
|
|
631
|
-
"logs:PutLogEvents",
|
|
632
|
-
],
|
|
633
|
-
resources: [
|
|
634
|
-
`arn:aws:logs:${stack.region}:${stack.account}:log-group:/aws/lambda/${server.functionName}`,
|
|
635
|
-
`arn:aws:logs:${stack.region}:${stack.account}:log-group:/aws/lambda/${server.functionName}:*`,
|
|
636
|
-
],
|
|
637
|
-
}),
|
|
638
|
-
],
|
|
639
|
-
});
|
|
640
|
-
server.role?.attachInlinePolicy(policy);
|
|
641
|
-
}
|
|
642
|
-
uploadSourcemaps() {
|
|
643
|
-
const stack = Stack.of(this);
|
|
644
|
-
const server = this.serverFunction;
|
|
645
|
-
this.useRoutes().forEach(({ sourcemapPath, sourcemapKey }) => {
|
|
646
|
-
if (!sourcemapPath || !sourcemapKey)
|
|
647
|
-
return;
|
|
648
|
-
useDeferredTasks().add(async () => {
|
|
649
|
-
// zip sourcemap
|
|
650
|
-
const zipPath = `${sourcemapPath}.gz.zip`;
|
|
651
|
-
const data = await fs.promises.readFile(sourcemapPath);
|
|
652
|
-
await fs.promises.writeFile(zipPath, zlib.gzipSync(data));
|
|
653
|
-
const asset = new Asset(this, `Sourcemap-${sourcemapKey}`, {
|
|
654
|
-
path: zipPath,
|
|
655
|
-
});
|
|
656
|
-
useFunctions().sourcemaps.add(stack.stackName, {
|
|
657
|
-
asset,
|
|
658
|
-
tarKey: path.join(server.functionArn, sourcemapKey),
|
|
659
|
-
});
|
|
660
|
-
});
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
573
|
static buildCloudWatchRouteName(route) {
|
|
664
574
|
return route.replace(/[^a-zA-Z0-9_\-/.#]/g, "");
|
|
665
575
|
}
|
|
@@ -90,6 +90,9 @@ export declare class RemixSite extends SsrSite {
|
|
|
90
90
|
allowedHeaders?: string[] | undefined;
|
|
91
91
|
} | undefined;
|
|
92
92
|
buildId?: string | undefined;
|
|
93
|
+
warmer?: {
|
|
94
|
+
function: string;
|
|
95
|
+
} | undefined;
|
|
93
96
|
};
|
|
94
97
|
private hasViteConfig;
|
|
95
98
|
private getServerModuleFormat;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Construct } from "constructs";
|
|
2
2
|
import { IGrantable } from "aws-cdk-lib/aws-iam";
|
|
3
3
|
import { RetentionDays } from "aws-cdk-lib/aws-logs";
|
|
4
|
-
import { FunctionOptions, Function as CdkFunction, FunctionUrlOptions } from "aws-cdk-lib/aws-lambda";
|
|
4
|
+
import { FunctionOptions, Function as CdkFunction, FunctionUrlOptions, FunctionUrl } from "aws-cdk-lib/aws-lambda";
|
|
5
5
|
import { NodeJSProps, FunctionCopyFilesProps } from "./Function.js";
|
|
6
6
|
import { SSTConstruct } from "./Construct.js";
|
|
7
7
|
import { BindingResource } from "./util/binding.js";
|
|
@@ -29,6 +29,7 @@ export declare class SsrFunction extends Construct implements SSTConstruct {
|
|
|
29
29
|
/** @internal */
|
|
30
30
|
readonly _doNotAllowOthersToBind = true;
|
|
31
31
|
function: CdkFunction;
|
|
32
|
+
private functionUrl?;
|
|
32
33
|
private assetReplacer;
|
|
33
34
|
private assetReplacerPolicy;
|
|
34
35
|
private missingSourcemap?;
|
|
@@ -37,8 +38,9 @@ export declare class SsrFunction extends Construct implements SSTConstruct {
|
|
|
37
38
|
get role(): import("aws-cdk-lib/aws-iam").IRole | undefined;
|
|
38
39
|
get functionArn(): string;
|
|
39
40
|
get functionName(): string;
|
|
41
|
+
get url(): string | undefined;
|
|
40
42
|
addEnvironment(key: string, value: string): CdkFunction;
|
|
41
|
-
addFunctionUrl(props?: FunctionUrlOptions):
|
|
43
|
+
addFunctionUrl(props?: FunctionUrlOptions): FunctionUrl;
|
|
42
44
|
grantInvoke(grantee: IGrantable): import("aws-cdk-lib/aws-iam").Grant;
|
|
43
45
|
attachPermissions(permissions: Permissions): void;
|
|
44
46
|
_overrideMissingSourcemap(): void;
|
|
@@ -29,6 +29,7 @@ export class SsrFunction extends Construct {
|
|
|
29
29
|
/** @internal */
|
|
30
30
|
_doNotAllowOthersToBind = true;
|
|
31
31
|
function;
|
|
32
|
+
functionUrl;
|
|
32
33
|
assetReplacer;
|
|
33
34
|
assetReplacerPolicy;
|
|
34
35
|
missingSourcemap;
|
|
@@ -80,11 +81,15 @@ export class SsrFunction extends Construct {
|
|
|
80
81
|
get functionName() {
|
|
81
82
|
return this.function.functionName;
|
|
82
83
|
}
|
|
84
|
+
get url() {
|
|
85
|
+
return this.functionUrl?.url;
|
|
86
|
+
}
|
|
83
87
|
addEnvironment(key, value) {
|
|
84
88
|
return this.function.addEnvironment(key, value);
|
|
85
89
|
}
|
|
86
90
|
addFunctionUrl(props) {
|
|
87
|
-
|
|
91
|
+
this.functionUrl = this.function.addFunctionUrl(props);
|
|
92
|
+
return this.functionUrl;
|
|
88
93
|
}
|
|
89
94
|
grantInvoke(grantee) {
|
|
90
95
|
return this.function.grantInvoke(grantee);
|
package/constructs/SsrSite.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Construct } from "constructs";
|
|
2
2
|
import { Bucket, BucketProps, IBucket } from "aws-cdk-lib/aws-s3";
|
|
3
3
|
import { Function as CdkFunction, FunctionProps as CdkFunctionProps } from "aws-cdk-lib/aws-lambda";
|
|
4
|
-
import { ICachePolicy, IResponseHeadersPolicy, ViewerProtocolPolicy, AllowedMethods,
|
|
4
|
+
import { ICachePolicy, IResponseHeadersPolicy, ViewerProtocolPolicy, AllowedMethods, ErrorResponse } from "aws-cdk-lib/aws-cloudfront";
|
|
5
5
|
import { S3OriginProps } from "aws-cdk-lib/aws-cloudfront-origins";
|
|
6
6
|
import { DistributionDomainProps } from "./Distribution.js";
|
|
7
7
|
import { SSTConstruct } from "./Construct.js";
|
|
@@ -13,26 +13,27 @@ import { Size } from "./util/size.js";
|
|
|
13
13
|
import { Duration } from "./util/duration.js";
|
|
14
14
|
import { Permissions } from "./util/permission.js";
|
|
15
15
|
import { BindingResource, BindingProps } from "./util/binding.js";
|
|
16
|
-
type CloudFrontFunctionConfig = {
|
|
16
|
+
export type CloudFrontFunctionConfig = {
|
|
17
17
|
constructId: string;
|
|
18
18
|
injections: string[];
|
|
19
19
|
};
|
|
20
|
-
type EdgeFunctionConfig = {
|
|
20
|
+
export type EdgeFunctionConfig = {
|
|
21
21
|
constructId: string;
|
|
22
22
|
function: EdgeFunctionProps;
|
|
23
23
|
};
|
|
24
|
-
type FunctionOriginConfig = {
|
|
24
|
+
export type FunctionOriginConfig = {
|
|
25
25
|
type: "function";
|
|
26
26
|
constructId: string;
|
|
27
27
|
function: SsrFunctionProps;
|
|
28
28
|
injections?: string[];
|
|
29
29
|
streaming?: boolean;
|
|
30
|
+
warm?: number;
|
|
30
31
|
};
|
|
31
|
-
type ImageOptimizationFunctionOriginConfig = {
|
|
32
|
+
export type ImageOptimizationFunctionOriginConfig = {
|
|
32
33
|
type: "image-optimization-function";
|
|
33
34
|
function: CdkFunctionProps;
|
|
34
35
|
};
|
|
35
|
-
type S3OriginConfig = {
|
|
36
|
+
export type S3OriginConfig = {
|
|
36
37
|
type: "s3";
|
|
37
38
|
originPath?: string;
|
|
38
39
|
copy: {
|
|
@@ -42,7 +43,7 @@ type S3OriginConfig = {
|
|
|
42
43
|
versionedSubDir?: string;
|
|
43
44
|
}[];
|
|
44
45
|
};
|
|
45
|
-
type OriginGroupConfig = {
|
|
46
|
+
export type OriginGroupConfig = {
|
|
46
47
|
type: "group";
|
|
47
48
|
primaryOriginName: string;
|
|
48
49
|
fallbackOriginName: string;
|
|
@@ -424,11 +425,12 @@ export declare abstract class SsrSite extends Construct implements SSTConstruct
|
|
|
424
425
|
protected doNotDeploy: boolean;
|
|
425
426
|
protected bucket: Bucket;
|
|
426
427
|
protected serverFunction?: EdgeFunction | SsrFunction;
|
|
428
|
+
protected serverFunctions: SsrFunction[];
|
|
429
|
+
protected edgeFunctions: Record<string, EdgeFunction>;
|
|
427
430
|
private serverFunctionForDev?;
|
|
428
431
|
private edge?;
|
|
429
432
|
private distribution;
|
|
430
433
|
constructor(scope: Construct, id: string, rawProps?: SsrSiteProps);
|
|
431
|
-
protected static buildDefaultServerCachePolicyProps(allowedHeaders: string[]): CachePolicyProps;
|
|
432
434
|
/**
|
|
433
435
|
* The CloudFront URL of the website.
|
|
434
436
|
*/
|
|
@@ -495,6 +497,9 @@ export declare abstract class SsrSite extends Construct implements SSTConstruct
|
|
|
495
497
|
allowedHeaders?: string[];
|
|
496
498
|
};
|
|
497
499
|
buildId?: string;
|
|
500
|
+
warmer?: {
|
|
501
|
+
function: string;
|
|
502
|
+
};
|
|
498
503
|
}): {
|
|
499
504
|
cloudFrontFunctions?: CloudFrontFunctions | undefined;
|
|
500
505
|
edgeFunctions?: EdgeFunctions | undefined;
|
|
@@ -513,6 +518,9 @@ export declare abstract class SsrSite extends Construct implements SSTConstruct
|
|
|
513
518
|
allowedHeaders?: string[] | undefined;
|
|
514
519
|
} | undefined;
|
|
515
520
|
buildId?: string | undefined;
|
|
521
|
+
warmer?: {
|
|
522
|
+
function: string;
|
|
523
|
+
} | undefined;
|
|
516
524
|
};
|
|
517
525
|
}
|
|
518
526
|
export declare const useSites: () => {
|
package/constructs/SsrSite.js
CHANGED
|
@@ -49,6 +49,8 @@ export class SsrSite extends Construct {
|
|
|
49
49
|
doNotDeploy;
|
|
50
50
|
bucket;
|
|
51
51
|
serverFunction;
|
|
52
|
+
serverFunctions = [];
|
|
53
|
+
edgeFunctions = {};
|
|
52
54
|
serverFunctionForDev;
|
|
53
55
|
edge;
|
|
54
56
|
distribution;
|
|
@@ -87,6 +89,7 @@ export class SsrSite extends Construct {
|
|
|
87
89
|
}
|
|
88
90
|
let s3DeployCRs = [];
|
|
89
91
|
let ssrFunctions = [];
|
|
92
|
+
let warmConfig = [];
|
|
90
93
|
let singletonUrlSigner;
|
|
91
94
|
let singletonCachePolicy;
|
|
92
95
|
let singletonOriginRequestPolicy;
|
|
@@ -107,6 +110,8 @@ export class SsrSite extends Construct {
|
|
|
107
110
|
createWarmer();
|
|
108
111
|
this.bucket = bucket;
|
|
109
112
|
this.distribution = distribution;
|
|
113
|
+
this.serverFunctions = [...ssrFunctions];
|
|
114
|
+
this.edgeFunctions = { ...edgeFunctions };
|
|
110
115
|
this.serverFunction = ssrFunctions[0] ?? Object.values(edgeFunctions)[0];
|
|
111
116
|
this.edge = plan.edge;
|
|
112
117
|
app.registerTypes(this);
|
|
@@ -213,27 +218,26 @@ export class SsrSite extends Construct {
|
|
|
213
218
|
// note: Currently all sites have a single server function. When we add
|
|
214
219
|
// support for multiple server functions (ie. route splitting), we
|
|
215
220
|
// need to handle warming multiple functions.
|
|
216
|
-
if (
|
|
217
|
-
|
|
218
|
-
if (warm && plan.edge) {
|
|
219
|
-
throw new VisibleError(`In the "${id}" Site, warming is currently supported only for the regional mode.`);
|
|
221
|
+
if (warm && ssrFunctions[0] instanceof SsrFunction) {
|
|
222
|
+
warmConfig.push({ concurrency: warm, function: ssrFunctions[0] });
|
|
220
223
|
}
|
|
221
|
-
|
|
222
|
-
|
|
224
|
+
const warmParams = warmConfig.map((config) => ({
|
|
225
|
+
concurrency: config.concurrency,
|
|
226
|
+
function: config.function.functionName,
|
|
227
|
+
}));
|
|
223
228
|
// Create warmer function
|
|
224
229
|
const warmer = new CdkFunction(self, "WarmerFunction", {
|
|
225
230
|
description: "SSR warmer",
|
|
226
|
-
code: Code.fromAsset(path.join(__dirname, "../support/ssr-warmer")),
|
|
231
|
+
code: Code.fromAsset(plan.warmer?.function ?? path.join(__dirname, "../support/ssr-warmer")),
|
|
227
232
|
runtime: Runtime.NODEJS_18_X,
|
|
228
233
|
handler: "index.handler",
|
|
229
234
|
timeout: CdkDuration.minutes(15),
|
|
230
235
|
memorySize: 128,
|
|
231
236
|
environment: {
|
|
232
|
-
|
|
233
|
-
CONCURRENCY: warm.toString(),
|
|
237
|
+
WARM_PARAMS: JSON.stringify(warmParams),
|
|
234
238
|
},
|
|
235
239
|
});
|
|
236
|
-
|
|
240
|
+
warmConfig.forEach((config) => config.function.grantInvoke(warmer));
|
|
237
241
|
// Create cron job
|
|
238
242
|
new Rule(self, "WarmerRule", {
|
|
239
243
|
schedule: Schedule.rate(CdkDuration.minutes(5)),
|
|
@@ -461,6 +465,9 @@ function handler(event) {
|
|
|
461
465
|
prefetchSecrets: regional?.prefetchSecrets,
|
|
462
466
|
});
|
|
463
467
|
ssrFunctions.push(fn);
|
|
468
|
+
if (props.warm) {
|
|
469
|
+
warmConfig.push({ concurrency: props.warm, function: fn });
|
|
470
|
+
}
|
|
464
471
|
bucket.grantReadWrite(fn?.role);
|
|
465
472
|
const fnUrl = fn.addFunctionUrl({
|
|
466
473
|
authType: regional?.enableServerUrlIamAuth
|
|
@@ -638,7 +645,19 @@ function handler(event) {
|
|
|
638
645
|
const allowedHeaders = plan.serverCachePolicy?.allowedHeaders ?? [];
|
|
639
646
|
singletonCachePolicy =
|
|
640
647
|
singletonCachePolicy ??
|
|
641
|
-
new CachePolicy(self, "ServerCache",
|
|
648
|
+
new CachePolicy(self, "ServerCache", {
|
|
649
|
+
queryStringBehavior: CacheQueryStringBehavior.all(),
|
|
650
|
+
headerBehavior: allowedHeaders.length > 0
|
|
651
|
+
? CacheHeaderBehavior.allowList(...allowedHeaders)
|
|
652
|
+
: CacheHeaderBehavior.none(),
|
|
653
|
+
cookieBehavior: CacheCookieBehavior.none(),
|
|
654
|
+
defaultTtl: CdkDuration.days(0),
|
|
655
|
+
maxTtl: CdkDuration.days(365),
|
|
656
|
+
minTtl: CdkDuration.days(0),
|
|
657
|
+
enableAcceptEncodingBrotli: true,
|
|
658
|
+
enableAcceptEncodingGzip: true,
|
|
659
|
+
comment: "SST server response cache policy",
|
|
660
|
+
});
|
|
642
661
|
return singletonCachePolicy;
|
|
643
662
|
}
|
|
644
663
|
function useServerBehaviorOriginRequestPolicy() {
|
|
@@ -797,21 +816,6 @@ function handler(event) {
|
|
|
797
816
|
});
|
|
798
817
|
}
|
|
799
818
|
}
|
|
800
|
-
static buildDefaultServerCachePolicyProps(allowedHeaders) {
|
|
801
|
-
return {
|
|
802
|
-
queryStringBehavior: CacheQueryStringBehavior.all(),
|
|
803
|
-
headerBehavior: allowedHeaders.length > 0
|
|
804
|
-
? CacheHeaderBehavior.allowList(...allowedHeaders)
|
|
805
|
-
: CacheHeaderBehavior.none(),
|
|
806
|
-
cookieBehavior: CacheCookieBehavior.none(),
|
|
807
|
-
defaultTtl: CdkDuration.days(0),
|
|
808
|
-
maxTtl: CdkDuration.days(365),
|
|
809
|
-
minTtl: CdkDuration.days(0),
|
|
810
|
-
enableAcceptEncodingBrotli: true,
|
|
811
|
-
enableAcceptEncodingGzip: true,
|
|
812
|
-
comment: "SST server response cache policy",
|
|
813
|
-
};
|
|
814
|
-
}
|
|
815
819
|
/**
|
|
816
820
|
* The CloudFront URL of the website.
|
|
817
821
|
*/
|
|
@@ -103,6 +103,9 @@ export declare class SvelteKitSite extends SsrSite {
|
|
|
103
103
|
allowedHeaders?: string[] | undefined;
|
|
104
104
|
} | undefined;
|
|
105
105
|
buildId?: string | undefined;
|
|
106
|
+
warmer?: {
|
|
107
|
+
function: string;
|
|
108
|
+
} | undefined;
|
|
106
109
|
};
|
|
107
110
|
getConstructMetadata(): {
|
|
108
111
|
data: {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"sideEffects": false,
|
|
3
3
|
"name": "sst",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.43.0",
|
|
5
5
|
"bin": {
|
|
6
6
|
"sst": "cli/sst.js"
|
|
7
7
|
},
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
"@types/ws": "^8.5.3",
|
|
119
119
|
"@types/yargs": "^17.0.13",
|
|
120
120
|
"archiver": "^5.3.1",
|
|
121
|
-
"astro-sst": "2.
|
|
121
|
+
"astro-sst": "2.43.0",
|
|
122
122
|
"async": "^3.2.4",
|
|
123
123
|
"tsx": "^3.12.1",
|
|
124
124
|
"typescript": "^5.2.2",
|
|
@@ -30914,62 +30914,65 @@ var require_dist_cjs59 = __commonJS({
|
|
|
30914
30914
|
// support/ssr-warmer/index.ts
|
|
30915
30915
|
var import_client_lambda = __toESM(require_dist_cjs59(), 1);
|
|
30916
30916
|
var lambda = new import_client_lambda.LambdaClient({});
|
|
30917
|
-
var
|
|
30918
|
-
var CONCURRENCY = parseInt(process.env.CONCURRENCY);
|
|
30917
|
+
var warmParams = JSON.parse(process.env.WARM_PARAMS);
|
|
30919
30918
|
function generateUniqueId() {
|
|
30920
30919
|
return Math.random().toString(36).slice(2, 8);
|
|
30921
30920
|
}
|
|
30922
30921
|
async function handler(_event, context) {
|
|
30923
30922
|
const warmerId = `warmer-${generateUniqueId()}`;
|
|
30924
|
-
|
|
30925
|
-
|
|
30926
|
-
|
|
30927
|
-
|
|
30928
|
-
|
|
30929
|
-
|
|
30930
|
-
|
|
30931
|
-
|
|
30932
|
-
|
|
30933
|
-
|
|
30934
|
-
|
|
30935
|
-
|
|
30936
|
-
|
|
30937
|
-
|
|
30938
|
-
|
|
30939
|
-
|
|
30940
|
-
|
|
30941
|
-
|
|
30942
|
-
|
|
30923
|
+
for (const warmParam of warmParams) {
|
|
30924
|
+
const { concurrency: CONCURRENCY, function: FUNCTION_NAME } = warmParam;
|
|
30925
|
+
console.log({
|
|
30926
|
+
event: "warmer invoked",
|
|
30927
|
+
functionName: FUNCTION_NAME,
|
|
30928
|
+
concurrency: CONCURRENCY,
|
|
30929
|
+
warmerId
|
|
30930
|
+
});
|
|
30931
|
+
const ret = await Promise.all(
|
|
30932
|
+
Array.from({ length: CONCURRENCY }, (_v, i) => i).map((i) => {
|
|
30933
|
+
try {
|
|
30934
|
+
return lambda.send(
|
|
30935
|
+
new import_client_lambda.InvokeCommand({
|
|
30936
|
+
FunctionName: FUNCTION_NAME,
|
|
30937
|
+
InvocationType: "RequestResponse",
|
|
30938
|
+
Payload: JSON.stringify({
|
|
30939
|
+
type: "warmer",
|
|
30940
|
+
warmerId,
|
|
30941
|
+
index: i,
|
|
30942
|
+
concurrency: CONCURRENCY,
|
|
30943
|
+
delay: 75
|
|
30944
|
+
})
|
|
30943
30945
|
})
|
|
30944
|
-
|
|
30946
|
+
);
|
|
30947
|
+
} catch (e) {
|
|
30948
|
+
console.error(`failed to warm up #${i}`, e);
|
|
30949
|
+
}
|
|
30950
|
+
})
|
|
30951
|
+
);
|
|
30952
|
+
const warmedServerIds = [];
|
|
30953
|
+
ret.forEach((r, i) => {
|
|
30954
|
+
if (r?.StatusCode !== 200 || !r?.Payload) {
|
|
30955
|
+
console.error(`failed to warm up #${i}:`, r?.Payload?.toString());
|
|
30956
|
+
return;
|
|
30957
|
+
}
|
|
30958
|
+
const payloadString = r.Payload.transformToString();
|
|
30959
|
+
if (payloadString) {
|
|
30960
|
+
const payload = JSON.parse(
|
|
30961
|
+
r.Payload.transformToString()
|
|
30945
30962
|
);
|
|
30946
|
-
|
|
30947
|
-
|
|
30948
|
-
|
|
30949
|
-
|
|
30950
|
-
|
|
30951
|
-
|
|
30952
|
-
|
|
30953
|
-
|
|
30954
|
-
|
|
30955
|
-
|
|
30956
|
-
|
|
30957
|
-
|
|
30958
|
-
|
|
30959
|
-
const payload = JSON.parse(
|
|
30960
|
-
r.Payload.transformToString()
|
|
30961
|
-
);
|
|
30962
|
-
warmedServerIds.push(payload.serverId);
|
|
30963
|
-
} else {
|
|
30964
|
-
warmedServerIds.push("unknown");
|
|
30965
|
-
}
|
|
30966
|
-
});
|
|
30967
|
-
console.log({
|
|
30968
|
-
event: "warmer result",
|
|
30969
|
-
sent: CONCURRENCY,
|
|
30970
|
-
success: warmedServerIds.length,
|
|
30971
|
-
uniqueServersWarmed: [...new Set(warmedServerIds)].length
|
|
30972
|
-
});
|
|
30963
|
+
warmedServerIds.push(payload.serverId);
|
|
30964
|
+
} else {
|
|
30965
|
+
warmedServerIds.push("unknown");
|
|
30966
|
+
}
|
|
30967
|
+
});
|
|
30968
|
+
console.log({
|
|
30969
|
+
event: "warmer result",
|
|
30970
|
+
sent: CONCURRENCY,
|
|
30971
|
+
name: FUNCTION_NAME,
|
|
30972
|
+
success: warmedServerIds.length,
|
|
30973
|
+
uniqueServersWarmed: [...new Set(warmedServerIds)].length
|
|
30974
|
+
});
|
|
30975
|
+
}
|
|
30973
30976
|
}
|
|
30974
30977
|
export {
|
|
30975
30978
|
handler
|