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.
@@ -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
- constructId: string;
23
- injections: string[];
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
- import { CachePolicyProps } from "aws-cdk-lib/aws-cloudfront";
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
- * Disabling incremental cache will cause the entire page to be revalidated on each request. This can result in ISR and SSG pages to be in an inconsistent state. Specify this option if you are using SSR pages only.
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
- * experimental: {
79
- * disableIncrementalCache: true,
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
- disableDynamoDBCache?: boolean;
53
+ staticImageOptimization?: boolean;
95
54
  };
96
55
  cdk?: SsrSiteProps["cdk"] & {
97
- revalidation?: Pick<FunctionProps, "vpc" | "vpcSubnets">;
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
- constructor(scope: Construct, id: string, rawProps?: NextjsSiteProps);
149
- static buildDefaultServerCachePolicyProps(): CachePolicyProps;
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
- s3: {
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: "s3" | "regionalServer" | "imageOptimizer";
145
+ origin: "default" | "s3" | "imageOptimizer";
228
146
  allowedMethods?: import("aws-cdk-lib/aws-cloudfront").AllowedMethods | undefined;
229
147
  cfFunction?: "serverCfFunction" | undefined;
230
- edgeFunction?: "edgeServer" | undefined;
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 prefixPattern;
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 useServerFunctionPerRouteLoggingInjection;
270
- private useCloudFrontFunctionPrerenderBypassHeaderInjection;
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: {
@@ -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 { globSync } from "glob";
6
- import { Duration as CdkDuration, RemovalPolicy, CustomResource, } from "aws-cdk-lib/core";
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 { Effect, Policy, PolicyStatement } from "aws-cdk-lib/aws-iam";
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 LAYER_VERSION = "2";
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
- constructor(scope, id, rawProps) {
52
- const props = {
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.isPerRouteLoggingEnabled()) {
83
- //this.disableDefaultLogging();
84
- this.uploadSourcemaps();
50
+ if (this.openNextOutput?.edgeFunctions?.middleware) {
51
+ this.setMiddlewareEnv();
85
52
  }
86
- if (!props.experimental.disableIncrementalCache) {
53
+ if (!disableIncrementalCache) {
87
54
  this.createRevalidationQueue();
88
- if (!props.experimental.disableDynamoDBCache) {
55
+ if (!disableTagCache) {
89
56
  this.createRevalidationTable();
90
57
  }
91
58
  }
92
59
  }
93
- static buildDefaultServerCachePolicyProps() {
94
- return super.buildDefaultServerCachePolicyProps(DEFAULT_CACHE_POLICY_ALLOWED_HEADERS);
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
- plan(bucket) {
97
- const { path: sitePath, edge, experimental, imageOptimization, cdk, } = this.props;
98
- const stack = Stack.of(this);
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
- this.removeSourcemaps();
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: edge ?? false,
135
+ edge: false,
119
136
  cloudFrontFunctions: {
120
137
  serverCfFunction: {
121
138
  constructId: "CloudFrontFunction",
122
139
  injections: [
123
140
  this.useCloudFrontFunctionHostHeaderInjection(),
124
- this.useCloudFrontFunctionPrerenderBypassHeaderInjection(),
141
+ this.useCloudFrontFunctionCacheHeaderKey(),
142
+ this.useCloudfrontGeoHeadersInjection(),
125
143
  ],
126
144
  },
127
145
  },
128
- edgeFunctions: edge
129
- ? {
130
- edgeServer: {
131
- constructId: "ServerFunction",
132
- function: serverConfig,
133
- },
134
- }
135
- : undefined,
146
+ edgeFunctions,
136
147
  origins: {
137
- ...(edge
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 image optimizer",
155
- handler: "index.handler",
156
- code: Code.fromAsset(path.join(sitePath, ".open-next/image-optimization-function")),
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
- s3: {
171
- type: "s3",
172
- originPath: "_assets",
173
- copy: [
174
- {
175
- from: ".open-next/assets",
176
- to: "_assets",
177
- cached: true,
178
- versionedSubDir: "_next",
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
- ...(edge
186
- ? [
187
- {
188
- cacheType: "server",
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
- origin: "imageOptimizer",
232
- },
233
- // create 1 behaviour for each top level asset file/folder
234
- ...fs.readdirSync(path.join(sitePath, ".open-next/assets")).map((item) => ({
235
- cacheType: "static",
236
- pattern: this.prefixPattern(fs
237
- .statSync(path.join(sitePath, ".open-next/assets", item))
238
- .isDirectory()
239
- ? `${item}/*`
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: DEFAULT_CACHE_POLICY_ALLOWED_HEADERS,
198
+ allowedHeaders: ["x-open-next-cache-key"],
246
199
  },
247
- buildId: this.getBuildId(),
248
200
  });
249
201
  }
250
- prefixPattern(pattern) {
251
- // Prefix CloudFront distribution behavior path patterns with `basePath` if configured
252
- const { basePath } = this.useRoutesManifest();
253
- return basePath && basePath.length > 0
254
- ? `${basePath.slice(1)}/${pattern}`
255
- : pattern;
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
- // Allow server to send messages to the queue
276
- server.addEnvironment("REVALIDATION_QUEUE_URL", queue.queueUrl);
277
- server.addEnvironment("REVALIDATION_QUEUE_REGION", Stack.of(this).region);
278
- queue.grantSendMessages(server.role);
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?.addEnvironment("CACHE_DYNAMO_TABLE", table.tableName);
300
- table.grantReadWriteData(server.role);
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
- data: {
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
- useServerFunctionPerRouteLoggingInjection() {
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
- if (event.rawPath) {
516
- const routeData = ${JSON.stringify(
517
- // @ts-expect-error
518
- this.useRoutes().map(({ regexMatch, prefixMatch, logGroupPath }) => ({
519
- regex: regexMatch,
520
- prefix: prefixMatch,
521
- logGroupPath,
522
- })))}.find(({ regex, prefix }) => {
523
- if (regex) return event.rawPath.match(new RegExp(regex));
524
- if (prefix) return event.rawPath === prefix || (event.rawPath === prefix + "/");
525
- return false;
526
- });
527
- if (routeData) {
528
- console.log("::sst::" + JSON.stringify({
529
- action:"log.split",
530
- properties: {
531
- logGroupName:"/sst/lambda/" + context.functionName + routeData.logGroupPath,
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
- useCloudFrontFunctionPrerenderBypassHeaderInjection() {
538
- // In Next.js page router preview mode (depends on the cookie __prerender_bypass),
539
- // to ensure we receive the cached page instead of the preview version, we set the
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 (request.cookies["__prerender_bypass"]) {
543
- request.headers["x-prerender-bypass"] = { value: "true" };
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;
@@ -72,6 +72,9 @@ export declare class SolidStartSite extends SsrSite {
72
72
  allowedHeaders?: string[] | undefined;
73
73
  } | undefined;
74
74
  buildId?: string | undefined;
75
+ warmer?: {
76
+ function: string;
77
+ } | undefined;
75
78
  };
76
79
  getConstructMetadata(): {
77
80
  data: {
@@ -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): import("aws-cdk-lib/aws-lambda").FunctionUrl;
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
- return this.function.addFunctionUrl(props);
91
+ this.functionUrl = this.function.addFunctionUrl(props);
92
+ return this.functionUrl;
88
93
  }
89
94
  grantInvoke(grantee) {
90
95
  return this.function.grantInvoke(grantee);
@@ -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, CachePolicyProps, ErrorResponse } from "aws-cdk-lib/aws-cloudfront";
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: () => {
@@ -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 (!warm)
217
- return;
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
- if (ssrFunctions.length === 0)
222
- return;
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
- FUNCTION_NAME: ssrFunctions[0].functionName,
233
- CONCURRENCY: warm.toString(),
237
+ WARM_PARAMS: JSON.stringify(warmParams),
234
238
  },
235
239
  });
236
- ssrFunctions[0].grantInvoke(warmer);
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", SsrSite.buildDefaultServerCachePolicyProps(allowedHeaders));
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.42.0",
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.42.0",
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 FUNCTION_NAME = process.env.FUNCTION_NAME;
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
- console.log({
30925
- event: "warmer invoked",
30926
- functionName: FUNCTION_NAME,
30927
- concurrency: CONCURRENCY,
30928
- warmerId
30929
- });
30930
- const ret = await Promise.all(
30931
- Array.from({ length: CONCURRENCY }, (_v, i) => i).map((i) => {
30932
- try {
30933
- return lambda.send(
30934
- new import_client_lambda.InvokeCommand({
30935
- FunctionName: FUNCTION_NAME,
30936
- InvocationType: "RequestResponse",
30937
- Payload: JSON.stringify({
30938
- type: "warmer",
30939
- warmerId,
30940
- index: i,
30941
- concurrency: CONCURRENCY,
30942
- delay: 75
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
- } catch (e) {
30947
- console.error(`failed to warm up #${i}`, e);
30948
- }
30949
- })
30950
- );
30951
- const warmedServerIds = [];
30952
- ret.forEach((r, i) => {
30953
- if (r?.StatusCode !== 200 || !r?.Payload) {
30954
- console.error(`failed to warm up #${i}:`, r?.Payload?.toString());
30955
- return;
30956
- }
30957
- const payloadString = r.Payload.transformToString();
30958
- if (payloadString) {
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