sst 2.3.7 → 2.4.1
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/cli/commands/bind.js +61 -48
- package/constructs/EdgeFunction.js +13 -1
- package/constructs/NextjsSite.d.ts +12 -5
- package/constructs/NextjsSite.js +149 -102
- package/constructs/SsrSite.d.ts +7 -8
- package/constructs/SsrSite.js +13 -14
- package/node/actor/index.d.ts +29 -0
- package/node/actor/index.js +17 -0
- package/node/util/index.js +12 -1
- package/package.json +1 -1
- package/sst.mjs +61 -50
- package/support/bootstrap-metadata-function/index.mjs +238 -238
- package/support/custom-resources/index.mjs +238 -238
- package/util/error.d.ts +3 -0
- package/util/error.js +6 -0
package/cli/commands/bind.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import { VisibleError } from "../../error.js";
|
|
3
|
-
const
|
|
3
|
+
const SSR_SITE_CONFIG = {
|
|
4
4
|
NextjsSite: "next.config",
|
|
5
5
|
AstroSite: "astro.config",
|
|
6
6
|
RemixSite: "remix.config",
|
|
7
7
|
SolidStartSite: "vite.config",
|
|
8
|
-
StaticSite: "vite.config",
|
|
9
8
|
SlsNextjsSite: "next.config",
|
|
10
9
|
};
|
|
11
10
|
export const bind = (program) => program
|
|
@@ -18,6 +17,7 @@ export const bind = (program) => program
|
|
|
18
17
|
const { useBus } = await import("../../bus.js");
|
|
19
18
|
const { useIOT } = await import("../../iot.js");
|
|
20
19
|
const { Colors } = await import("../colors.js");
|
|
20
|
+
const { Logger } = await import("../../logger.js");
|
|
21
21
|
if (args._[0] === "env") {
|
|
22
22
|
Colors.line(Colors.warning(`Warning: ${Colors.bold(`sst env`)} has been renamed to ${Colors.bold(`sst bind`)}`));
|
|
23
23
|
}
|
|
@@ -25,17 +25,18 @@ export const bind = (program) => program
|
|
|
25
25
|
const bus = useBus();
|
|
26
26
|
const project = useProject();
|
|
27
27
|
const command = args.command?.join(" ");
|
|
28
|
-
const
|
|
28
|
+
const isSsrSite = await isRunningInSsrSite();
|
|
29
29
|
let p;
|
|
30
30
|
let timer;
|
|
31
|
-
let
|
|
31
|
+
let siteConfigCache;
|
|
32
32
|
// Handle missing command
|
|
33
33
|
if (!command) {
|
|
34
|
-
throw new VisibleError(`Command is required, e.g. sst bind ${
|
|
34
|
+
throw new VisibleError(`Command is required, e.g. sst bind ${isSsrSite ? "next dev" : "env"}`);
|
|
35
35
|
}
|
|
36
36
|
// Bind script
|
|
37
37
|
const initialMetadata = await getSiteMetadata();
|
|
38
|
-
if (!initialMetadata) {
|
|
38
|
+
if (!initialMetadata && !isSsrSite) {
|
|
39
|
+
Logger.debug("Running in script mode.");
|
|
39
40
|
return await bindScript();
|
|
40
41
|
}
|
|
41
42
|
// Bind site
|
|
@@ -44,33 +45,44 @@ export const bind = (program) => program
|
|
|
44
45
|
bus.subscribe("stacks.metadata.deleted", () => bindSite("metadata_updated"));
|
|
45
46
|
bus.subscribe("config.secret.updated", (payload) => {
|
|
46
47
|
const secretName = payload.properties.name;
|
|
47
|
-
if (
|
|
48
|
+
if (siteConfigCache?.secrets === undefined)
|
|
48
49
|
return;
|
|
49
|
-
if (!
|
|
50
|
+
if (!siteConfigCache.secrets.includes(secretName))
|
|
50
51
|
return;
|
|
51
52
|
Colors.line(`\n`, `SST secrets have been updated. Restarting \`${command}\`...`);
|
|
52
53
|
bindSite("secrets_updated");
|
|
53
54
|
});
|
|
54
|
-
async function
|
|
55
|
+
async function isRunningInSsrSite() {
|
|
55
56
|
const { existsAsync } = await import("../../util/fs.js");
|
|
56
|
-
const
|
|
57
|
-
|
|
57
|
+
const { readFile } = await import("fs/promises");
|
|
58
|
+
const results = await Promise.all(Object.values(SSR_SITE_CONFIG)
|
|
59
|
+
.map((config) => [".js", ".cjs", ".mjs", ".ts"].map(async (ext) => {
|
|
60
|
+
const exists = await existsAsync(`${config}${ext}`);
|
|
61
|
+
if (exists && config === "vite.config") {
|
|
62
|
+
const content = await readFile(`${config}${ext}`);
|
|
63
|
+
return content.includes("solid-start");
|
|
64
|
+
}
|
|
65
|
+
return exists;
|
|
66
|
+
}))
|
|
58
67
|
.flat());
|
|
59
68
|
return results.some(Boolean);
|
|
60
69
|
}
|
|
61
70
|
async function bindSite(reason) {
|
|
62
71
|
// Get metadata
|
|
63
|
-
const
|
|
72
|
+
const siteMetadata = (reason === "init"
|
|
73
|
+
? initialMetadata
|
|
74
|
+
: await getSiteMetadataUntilAvailable());
|
|
75
|
+
const siteConfig = await parseSiteConfig(siteMetadata);
|
|
64
76
|
// Handle rebind due to metadata updated
|
|
65
77
|
if (reason === "metadata_updated") {
|
|
66
|
-
if (areEnvsSame(
|
|
78
|
+
if (areEnvsSame(siteConfig.envs, siteConfigCache?.envs || {}))
|
|
67
79
|
return;
|
|
68
80
|
Colors.line(`\n`, `SST resources have been updated. Restarting \`${command}\`...`);
|
|
69
81
|
}
|
|
70
|
-
|
|
82
|
+
siteConfigCache = siteConfig;
|
|
71
83
|
// Assume function's role credentials
|
|
72
|
-
if (
|
|
73
|
-
const credentials = await assumeSsrRole(
|
|
84
|
+
if (siteConfig.role) {
|
|
85
|
+
const credentials = await assumeSsrRole(siteConfig.role);
|
|
74
86
|
if (credentials) {
|
|
75
87
|
// refresh crecentials 1 minute before expiration
|
|
76
88
|
const expireAt = credentials.Expiration.getTime() - 60000;
|
|
@@ -80,7 +92,7 @@ export const bind = (program) => program
|
|
|
80
92
|
bindSite("iam_expired");
|
|
81
93
|
}, expireAt - Date.now());
|
|
82
94
|
runCommand({
|
|
83
|
-
...
|
|
95
|
+
...siteConfig.envs,
|
|
84
96
|
AWS_ACCESS_KEY_ID: credentials.AccessKeyId,
|
|
85
97
|
AWS_SECRET_ACCESS_KEY: credentials.SecretAccessKey,
|
|
86
98
|
AWS_SESSION_TOKEN: credentials.SessionToken,
|
|
@@ -90,7 +102,7 @@ export const bind = (program) => program
|
|
|
90
102
|
}
|
|
91
103
|
// Fallback to use local IAM credentials
|
|
92
104
|
runCommand({
|
|
93
|
-
...
|
|
105
|
+
...siteConfig.envs,
|
|
94
106
|
...(await localIamCredentials()),
|
|
95
107
|
});
|
|
96
108
|
}
|
|
@@ -101,24 +113,30 @@ export const bind = (program) => program
|
|
|
101
113
|
...(await localIamCredentials()),
|
|
102
114
|
});
|
|
103
115
|
}
|
|
104
|
-
async function
|
|
105
|
-
const { metadata } = await import("../../stacks/metadata.js");
|
|
106
|
-
const { createSpinner } = await import("../spinner.js");
|
|
116
|
+
async function parseSiteConfig(metadata) {
|
|
107
117
|
const { LambdaClient, GetFunctionCommand } = await import("@aws-sdk/client-lambda");
|
|
108
118
|
const { useAWSClient } = await import("../../credentials.js");
|
|
119
|
+
const isBindSupported = metadata.type !== "StaticSite" && metadata.type !== "SlsNextjsSite";
|
|
120
|
+
// Handle StaticSite
|
|
121
|
+
if (!isBindSupported) {
|
|
122
|
+
return { envs: metadata.data.environment };
|
|
123
|
+
}
|
|
124
|
+
// Get function details
|
|
125
|
+
const lambda = useAWSClient(LambdaClient);
|
|
126
|
+
const { Configuration: functionConfig } = await lambda.send(new GetFunctionCommand({
|
|
127
|
+
FunctionName: metadata.data.server,
|
|
128
|
+
}));
|
|
129
|
+
return {
|
|
130
|
+
role: functionConfig?.Role,
|
|
131
|
+
envs: functionConfig?.Environment?.Variables || {},
|
|
132
|
+
secrets: metadata.data.secrets,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
async function getSiteMetadataUntilAvailable() {
|
|
136
|
+
const { createSpinner } = await import("../spinner.js");
|
|
109
137
|
const spinner = createSpinner({});
|
|
110
138
|
while (true) {
|
|
111
|
-
const
|
|
112
|
-
const data = Object.values(metadataData)
|
|
113
|
-
.flat()
|
|
114
|
-
.filter((c) => Boolean(c))
|
|
115
|
-
.filter((c) => Boolean(SITE_CONFIG[c.type]))
|
|
116
|
-
.find((c) => path.resolve(project.paths.root, c.data.path) ===
|
|
117
|
-
process.cwd());
|
|
118
|
-
// Do not retry if not running in frontend
|
|
119
|
-
if (!data && !isFrontend) {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
139
|
+
const data = await getSiteMetadata();
|
|
122
140
|
// Handle site metadata not found
|
|
123
141
|
if (!data) {
|
|
124
142
|
spinner.start(
|
|
@@ -137,25 +155,20 @@ export const bind = (program) => program
|
|
|
137
155
|
continue;
|
|
138
156
|
}
|
|
139
157
|
spinner.isSpinning && spinner.stop().clear();
|
|
140
|
-
|
|
141
|
-
if (!isBindSupported) {
|
|
142
|
-
return { envs: data.data.environment };
|
|
143
|
-
}
|
|
144
|
-
// Get function details
|
|
145
|
-
const lambda = useAWSClient(LambdaClient);
|
|
146
|
-
const { Configuration: functionConfig } = await lambda.send(new GetFunctionCommand({
|
|
147
|
-
FunctionName: data.data.server,
|
|
148
|
-
}));
|
|
149
|
-
return {
|
|
150
|
-
role: functionConfig?.Role,
|
|
151
|
-
envs: functionConfig?.Environment?.Variables || {},
|
|
152
|
-
secrets: data.data.secrets,
|
|
153
|
-
};
|
|
158
|
+
return data;
|
|
154
159
|
}
|
|
155
160
|
}
|
|
161
|
+
async function getSiteMetadata() {
|
|
162
|
+
const { metadata } = await import("../../stacks/metadata.js");
|
|
163
|
+
const metadataData = await metadata();
|
|
164
|
+
return Object.values(metadataData)
|
|
165
|
+
.flat()
|
|
166
|
+
.filter((c) => Boolean(c))
|
|
167
|
+
.filter((c) => c.type === "StaticSite" || Boolean(SSR_SITE_CONFIG[c.type]))
|
|
168
|
+
.find((c) => path.resolve(project.paths.root, c.data.path) === process.cwd());
|
|
169
|
+
}
|
|
156
170
|
async function assumeSsrRole(roleArn) {
|
|
157
171
|
const { STSClient, AssumeRoleCommand } = await import("@aws-sdk/client-sts");
|
|
158
|
-
const { Logger } = await import("../../logger.js");
|
|
159
172
|
const { useAWSClient } = await import("../../credentials.js");
|
|
160
173
|
const sts = useAWSClient(STSClient);
|
|
161
174
|
const assumeRole = async (duration) => {
|
|
@@ -184,7 +197,7 @@ export const bind = (program) => program
|
|
|
184
197
|
err = e;
|
|
185
198
|
}
|
|
186
199
|
}
|
|
187
|
-
Colors.line(
|
|
200
|
+
Colors.line("Using local IAM credentials since `sst dev` is not running.");
|
|
188
201
|
Logger.debug(`Failed to assume ${roleArn}.`, err);
|
|
189
202
|
}
|
|
190
203
|
async function localIamCredentials() {
|
|
@@ -2,6 +2,7 @@ import fs from "fs";
|
|
|
2
2
|
import url from "url";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import crypto from "crypto";
|
|
5
|
+
import spawn from "cross-spawn";
|
|
5
6
|
import { Construct } from "constructs";
|
|
6
7
|
import { Effect, Role, Policy, PolicyStatement, CompositePrincipal, ServicePrincipal, ManagedPolicy, } from "aws-cdk-lib/aws-iam";
|
|
7
8
|
import { Version, Code, Runtime, Function as CdkFunction, } from "aws-cdk-lib/aws-lambda";
|
|
@@ -135,9 +136,20 @@ export class EdgeFunction extends Construct {
|
|
|
135
136
|
const filePath = path.join(bundle, handlerFilename);
|
|
136
137
|
const fileData = fs.readFileSync(filePath, "utf8");
|
|
137
138
|
fs.writeFileSync(filePath, `process.env = { ...process.env, ..."{{ _SST_FUNCTION_ENVIRONMENT_ }}" };\n${fileData}`);
|
|
139
|
+
// Note: cannot point the bundle to the `.open-next/server-function`
|
|
140
|
+
// b/c the folder contains node_modules. And pnpm node_modules
|
|
141
|
+
// contains symlinks. CDK cannot zip symlinks correctly.
|
|
142
|
+
// https://github.com/aws/aws-cdk/issues/9251
|
|
143
|
+
// We will zip the folder ourselves.
|
|
144
|
+
const outputPath = path.resolve(useProject().paths.artifacts, `SsrFunction-${this.node.id}-${this.node.addr}`);
|
|
145
|
+
const script = path.resolve(__dirname, "../support/ssr-site-function-archiver.mjs");
|
|
146
|
+
const result = spawn.sync("node", [script, path.join(bundle), path.join(outputPath, "server-function.zip")], { stdio: "inherit" });
|
|
147
|
+
if (result.status !== 0) {
|
|
148
|
+
throw new Error(`There was a problem generating the assets package.`);
|
|
149
|
+
}
|
|
138
150
|
// Create asset
|
|
139
151
|
const asset = new Asset(this.scope, `FunctionAsset`, {
|
|
140
|
-
path:
|
|
152
|
+
path: path.join(outputPath, "server-function.zip"),
|
|
141
153
|
});
|
|
142
154
|
return { handlerFilename, asset };
|
|
143
155
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Construct } from "constructs";
|
|
2
2
|
import { Function as CdkFunction } from "aws-cdk-lib/aws-lambda";
|
|
3
|
-
import { Distribution
|
|
3
|
+
import { Distribution } from "aws-cdk-lib/aws-cloudfront";
|
|
4
|
+
import { EdgeFunction } from "./EdgeFunction.js";
|
|
4
5
|
import { SsrSite, SsrSiteProps } from "./SsrSite.js";
|
|
5
6
|
import { Size } from "./util/size.js";
|
|
6
|
-
export interface NextjsSiteProps extends Omit<SsrSiteProps, "
|
|
7
|
+
export interface NextjsSiteProps extends Omit<SsrSiteProps, "nodejs"> {
|
|
7
8
|
imageOptimization?: {
|
|
8
9
|
/**
|
|
9
10
|
* The amount of memory in MB allocated for image optimization function.
|
|
@@ -43,9 +44,15 @@ export declare class NextjsSite extends SsrSite {
|
|
|
43
44
|
clientBuildVersionedSubDir: string;
|
|
44
45
|
};
|
|
45
46
|
protected createFunctionForRegional(): CdkFunction;
|
|
46
|
-
|
|
47
|
-
private
|
|
47
|
+
protected createFunctionForEdge(): EdgeFunction;
|
|
48
|
+
private createImageOptimizationFunction;
|
|
48
49
|
protected createCloudFrontDistributionForRegional(): Distribution;
|
|
49
|
-
protected
|
|
50
|
+
protected createCloudFrontDistributionForEdge(): Distribution;
|
|
51
|
+
private buildServerBehaviorForRegional;
|
|
52
|
+
private buildServerBehaviorForEdge;
|
|
53
|
+
private buildImageBehavior;
|
|
54
|
+
private buildStaticFileBehavior;
|
|
55
|
+
private buildDefaultNextjsBehaviorForRegional;
|
|
56
|
+
private buildDefaultNextjsBehaviorForEdge;
|
|
50
57
|
protected generateBuildId(): string;
|
|
51
58
|
}
|
package/constructs/NextjsSite.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from "path";
|
|
|
4
4
|
import { Fn, Duration as CdkDuration, RemovalPolicy } from "aws-cdk-lib";
|
|
5
5
|
import { RetentionDays } from "aws-cdk-lib/aws-logs";
|
|
6
6
|
import { Function as CdkFunction, Code, Runtime, Architecture, FunctionUrlAuthType, } from "aws-cdk-lib/aws-lambda";
|
|
7
|
-
import { Distribution, ViewerProtocolPolicy, AllowedMethods, LambdaEdgeEventType, CachedMethods, CachePolicy,
|
|
7
|
+
import { Distribution, ViewerProtocolPolicy, AllowedMethods, LambdaEdgeEventType, CachedMethods, CachePolicy, } from "aws-cdk-lib/aws-cloudfront";
|
|
8
8
|
import { S3Origin, HttpOrigin, OriginGroup, } from "aws-cdk-lib/aws-cloudfront-origins";
|
|
9
9
|
import { SsrFunction } from "./SsrFunction.js";
|
|
10
10
|
import { EdgeFunction } from "./EdgeFunction.js";
|
|
@@ -26,7 +26,7 @@ const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
|
|
|
26
26
|
export class NextjsSite extends SsrSite {
|
|
27
27
|
constructor(scope, id, props) {
|
|
28
28
|
super(scope, id, {
|
|
29
|
-
buildCommand: "npx --yes open-next
|
|
29
|
+
buildCommand: "npx --yes open-next@^0.8.0 build",
|
|
30
30
|
...props,
|
|
31
31
|
});
|
|
32
32
|
}
|
|
@@ -54,7 +54,20 @@ export class NextjsSite extends SsrSite {
|
|
|
54
54
|
});
|
|
55
55
|
return ssrFn.function;
|
|
56
56
|
}
|
|
57
|
-
|
|
57
|
+
createFunctionForEdge() {
|
|
58
|
+
const { runtime, timeout, memorySize, bind, permissions, environment } = this.props;
|
|
59
|
+
return new EdgeFunction(this, "ServerFunction", {
|
|
60
|
+
bundle: path.join(this.props.path, ".open-next", "server-function"),
|
|
61
|
+
handler: "index.handler",
|
|
62
|
+
runtime,
|
|
63
|
+
timeout,
|
|
64
|
+
memorySize,
|
|
65
|
+
bind,
|
|
66
|
+
permissions,
|
|
67
|
+
environment,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
createImageOptimizationFunction() {
|
|
58
71
|
const { imageOptimization, path: sitePath } = this.props;
|
|
59
72
|
return new CdkFunction(this, `ImageFunction`, {
|
|
60
73
|
description: "Image optimization handler for Next.js",
|
|
@@ -83,86 +96,7 @@ export class NextjsSite extends SsrSite {
|
|
|
83
96
|
],
|
|
84
97
|
});
|
|
85
98
|
}
|
|
86
|
-
createMiddlewareEdgeFunctionForRegional() {
|
|
87
|
-
const { permissions, environment, path: sitePath } = this.props;
|
|
88
|
-
const middlewarePath = path.resolve(sitePath, ".open-next/middleware-function");
|
|
89
|
-
if (fs.existsSync(middlewarePath)) {
|
|
90
|
-
return new EdgeFunction(this, "Middleware", {
|
|
91
|
-
bundle: middlewarePath,
|
|
92
|
-
handler: "index.handler",
|
|
93
|
-
runtime: "nodejs18.x",
|
|
94
|
-
timeout: 5,
|
|
95
|
-
memorySize: 128,
|
|
96
|
-
permissions,
|
|
97
|
-
environment,
|
|
98
|
-
nodejs: {
|
|
99
|
-
format: "esm",
|
|
100
|
-
},
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
99
|
createCloudFrontDistributionForRegional() {
|
|
105
|
-
const { cdk } = this.props;
|
|
106
|
-
const cfDistributionProps = cdk?.distribution || {};
|
|
107
|
-
const s3Origin = new S3Origin(this.cdk.bucket);
|
|
108
|
-
// Create server behavior
|
|
109
|
-
const middlewareFn = this.createMiddlewareEdgeFunctionForRegional();
|
|
110
|
-
const serverFnUrl = this.serverLambdaForRegional.addFunctionUrl({
|
|
111
|
-
authType: FunctionUrlAuthType.NONE,
|
|
112
|
-
});
|
|
113
|
-
const serverBehavior = {
|
|
114
|
-
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
115
|
-
origin: new HttpOrigin(Fn.parseDomainName(serverFnUrl.url)),
|
|
116
|
-
allowedMethods: AllowedMethods.ALLOW_ALL,
|
|
117
|
-
cachedMethods: CachedMethods.CACHE_GET_HEAD_OPTIONS,
|
|
118
|
-
compress: true,
|
|
119
|
-
cachePolicy: cdk?.serverCachePolicy ?? this.createCloudFrontServerCachePolicy(),
|
|
120
|
-
edgeLambdas: middlewareFn && [
|
|
121
|
-
{
|
|
122
|
-
eventType: LambdaEdgeEventType.VIEWER_REQUEST,
|
|
123
|
-
functionVersion: middlewareFn.currentVersion,
|
|
124
|
-
},
|
|
125
|
-
],
|
|
126
|
-
};
|
|
127
|
-
// Create image optimization behavior
|
|
128
|
-
const imageFn = this.createImageOptimizationFunctionForRegional();
|
|
129
|
-
const imageFnUrl = imageFn.addFunctionUrl({
|
|
130
|
-
authType: FunctionUrlAuthType.NONE,
|
|
131
|
-
});
|
|
132
|
-
const imageBehavior = {
|
|
133
|
-
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
134
|
-
origin: new HttpOrigin(Fn.parseDomainName(imageFnUrl.url)),
|
|
135
|
-
allowedMethods: AllowedMethods.ALLOW_ALL,
|
|
136
|
-
cachedMethods: CachedMethods.CACHE_GET_HEAD_OPTIONS,
|
|
137
|
-
compress: true,
|
|
138
|
-
cachePolicy: serverBehavior.cachePolicy,
|
|
139
|
-
};
|
|
140
|
-
// Create statics behavior
|
|
141
|
-
const staticFileBehaviour = {
|
|
142
|
-
origin: s3Origin,
|
|
143
|
-
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
144
|
-
allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,
|
|
145
|
-
cachedMethods: CachedMethods.CACHE_GET_HEAD_OPTIONS,
|
|
146
|
-
compress: true,
|
|
147
|
-
cachePolicy: CachePolicy.CACHING_OPTIMIZED,
|
|
148
|
-
};
|
|
149
|
-
// Create default behavior
|
|
150
|
-
// default handler for requests that don't match any other path:
|
|
151
|
-
// - try lambda handler first
|
|
152
|
-
// - if failed, fall back to S3
|
|
153
|
-
const fallbackOriginGroup = new OriginGroup({
|
|
154
|
-
primaryOrigin: serverBehavior.origin,
|
|
155
|
-
fallbackOrigin: s3Origin,
|
|
156
|
-
fallbackStatusCodes: [404],
|
|
157
|
-
});
|
|
158
|
-
const defaultBehavior = {
|
|
159
|
-
origin: fallbackOriginGroup,
|
|
160
|
-
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
161
|
-
compress: true,
|
|
162
|
-
cachePolicy: serverBehavior.cachePolicy,
|
|
163
|
-
edgeLambdas: serverBehavior.edgeLambdas,
|
|
164
|
-
...(cfDistributionProps.defaultBehavior || {}),
|
|
165
|
-
};
|
|
166
100
|
/**
|
|
167
101
|
* Next.js requests
|
|
168
102
|
*
|
|
@@ -212,6 +146,42 @@ export class NextjsSite extends SsrSite {
|
|
|
212
146
|
* - Cache-Control: public, max-age=0, must-revalidate
|
|
213
147
|
* - x-vercel-cache: MISS
|
|
214
148
|
*/
|
|
149
|
+
const { cdk } = this.props;
|
|
150
|
+
const cfDistributionProps = cdk?.distribution || {};
|
|
151
|
+
const s3Origin = new S3Origin(this.cdk.bucket);
|
|
152
|
+
const serverFnUrl = this.serverLambdaForRegional.addFunctionUrl({
|
|
153
|
+
authType: FunctionUrlAuthType.NONE,
|
|
154
|
+
});
|
|
155
|
+
const serverOrigin = new HttpOrigin(Fn.parseDomainName(serverFnUrl.url));
|
|
156
|
+
const cachePolicy = cdk?.serverCachePolicy ?? this.buildServerCachePolicy();
|
|
157
|
+
const originRequestPolicy = this.buildServerOriginRequestPolicy();
|
|
158
|
+
const serverBehavior = this.buildServerBehaviorForRegional(serverOrigin, cachePolicy, originRequestPolicy);
|
|
159
|
+
return new Distribution(this, "Distribution", {
|
|
160
|
+
// these values can be overwritten by cfDistributionProps
|
|
161
|
+
defaultRootObject: "",
|
|
162
|
+
// Override props.
|
|
163
|
+
...cfDistributionProps,
|
|
164
|
+
// these values can NOT be overwritten by cfDistributionProps
|
|
165
|
+
domainNames: this.buildDistributionDomainNames(),
|
|
166
|
+
certificate: this.cdk.certificate,
|
|
167
|
+
defaultBehavior: this.buildDefaultNextjsBehaviorForRegional(serverOrigin, s3Origin, cachePolicy, originRequestPolicy),
|
|
168
|
+
additionalBehaviors: {
|
|
169
|
+
"api/*": serverBehavior,
|
|
170
|
+
"_next/data/*": serverBehavior,
|
|
171
|
+
"_next/image*": this.buildImageBehavior(cachePolicy),
|
|
172
|
+
"_next/*": this.buildStaticFileBehavior(s3Origin),
|
|
173
|
+
...(cfDistributionProps.additionalBehaviors || {}),
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
createCloudFrontDistributionForEdge() {
|
|
178
|
+
const { cdk } = this.props;
|
|
179
|
+
const cfDistributionProps = cdk?.distribution || {};
|
|
180
|
+
const s3Origin = new S3Origin(this.cdk.bucket);
|
|
181
|
+
const cachePolicy = cdk?.serverCachePolicy ?? this.buildServerCachePolicy();
|
|
182
|
+
const originRequestPolicy = this.buildServerOriginRequestPolicy();
|
|
183
|
+
const functionVersion = this.serverLambdaForEdge.currentVersion;
|
|
184
|
+
const serverBehavior = this.buildServerBehaviorForEdge(functionVersion, s3Origin, cachePolicy, originRequestPolicy);
|
|
215
185
|
return new Distribution(this, "Distribution", {
|
|
216
186
|
// these values can be overwritten by cfDistributionProps
|
|
217
187
|
defaultRootObject: "",
|
|
@@ -220,34 +190,111 @@ export class NextjsSite extends SsrSite {
|
|
|
220
190
|
// these values can NOT be overwritten by cfDistributionProps
|
|
221
191
|
domainNames: this.buildDistributionDomainNames(),
|
|
222
192
|
certificate: this.cdk.certificate,
|
|
223
|
-
defaultBehavior:
|
|
193
|
+
defaultBehavior: this.buildDefaultNextjsBehaviorForEdge(functionVersion, s3Origin, cachePolicy, originRequestPolicy),
|
|
224
194
|
additionalBehaviors: {
|
|
225
195
|
"api/*": serverBehavior,
|
|
226
196
|
"_next/data/*": serverBehavior,
|
|
227
|
-
"_next/image*":
|
|
228
|
-
"_next/*":
|
|
197
|
+
"_next/image*": this.buildImageBehavior(cachePolicy),
|
|
198
|
+
"_next/*": this.buildStaticFileBehavior(s3Origin),
|
|
229
199
|
...(cfDistributionProps.additionalBehaviors || {}),
|
|
230
200
|
},
|
|
231
201
|
});
|
|
232
202
|
}
|
|
233
|
-
|
|
234
|
-
return
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
203
|
+
buildServerBehaviorForRegional(serverOrigin, cachePolicy, originRequestPolicy) {
|
|
204
|
+
return {
|
|
205
|
+
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
206
|
+
origin: serverOrigin,
|
|
207
|
+
allowedMethods: AllowedMethods.ALLOW_ALL,
|
|
208
|
+
cachedMethods: CachedMethods.CACHE_GET_HEAD_OPTIONS,
|
|
209
|
+
compress: true,
|
|
210
|
+
cachePolicy,
|
|
211
|
+
originRequestPolicy,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
buildServerBehaviorForEdge(functionVersion, s3Origin, cachePolicy, originRequestPolicy) {
|
|
215
|
+
return {
|
|
216
|
+
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
217
|
+
origin: s3Origin,
|
|
218
|
+
allowedMethods: AllowedMethods.ALLOW_ALL,
|
|
219
|
+
cachedMethods: CachedMethods.CACHE_GET_HEAD_OPTIONS,
|
|
220
|
+
compress: true,
|
|
221
|
+
cachePolicy,
|
|
222
|
+
originRequestPolicy,
|
|
223
|
+
edgeLambdas: [
|
|
224
|
+
{
|
|
225
|
+
includeBody: true,
|
|
226
|
+
eventType: LambdaEdgeEventType.ORIGIN_REQUEST,
|
|
227
|
+
functionVersion,
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
buildImageBehavior(cachePolicy) {
|
|
233
|
+
const imageFn = this.createImageOptimizationFunction();
|
|
234
|
+
const imageFnUrl = imageFn.addFunctionUrl({
|
|
235
|
+
authType: FunctionUrlAuthType.NONE,
|
|
250
236
|
});
|
|
237
|
+
return {
|
|
238
|
+
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
239
|
+
origin: new HttpOrigin(Fn.parseDomainName(imageFnUrl.url)),
|
|
240
|
+
allowedMethods: AllowedMethods.ALLOW_ALL,
|
|
241
|
+
cachedMethods: CachedMethods.CACHE_GET_HEAD_OPTIONS,
|
|
242
|
+
compress: true,
|
|
243
|
+
cachePolicy,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
buildStaticFileBehavior(s3Origin) {
|
|
247
|
+
return {
|
|
248
|
+
origin: s3Origin,
|
|
249
|
+
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
250
|
+
allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,
|
|
251
|
+
cachedMethods: CachedMethods.CACHE_GET_HEAD_OPTIONS,
|
|
252
|
+
compress: true,
|
|
253
|
+
cachePolicy: CachePolicy.CACHING_OPTIMIZED,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
buildDefaultNextjsBehaviorForRegional(serverOrigin, s3Origin, cachePolicy, originRequestPolicy) {
|
|
257
|
+
// Create default behavior
|
|
258
|
+
// default handler for requests that don't match any other path:
|
|
259
|
+
// - try lambda handler first
|
|
260
|
+
// - if failed, fall back to S3
|
|
261
|
+
const { cdk } = this.props;
|
|
262
|
+
const cfDistributionProps = cdk?.distribution || {};
|
|
263
|
+
const fallbackOriginGroup = new OriginGroup({
|
|
264
|
+
primaryOrigin: serverOrigin,
|
|
265
|
+
fallbackOrigin: s3Origin,
|
|
266
|
+
fallbackStatusCodes: [404],
|
|
267
|
+
});
|
|
268
|
+
return {
|
|
269
|
+
origin: fallbackOriginGroup,
|
|
270
|
+
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
271
|
+
compress: true,
|
|
272
|
+
cachePolicy,
|
|
273
|
+
originRequestPolicy,
|
|
274
|
+
...(cfDistributionProps.defaultBehavior || {}),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
buildDefaultNextjsBehaviorForEdge(functionVersion, s3Origin, cachePolicy, originRequestPolicy) {
|
|
278
|
+
const { cdk } = this.props;
|
|
279
|
+
const cfDistributionProps = cdk?.distribution || {};
|
|
280
|
+
return {
|
|
281
|
+
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
282
|
+
origin: s3Origin,
|
|
283
|
+
allowedMethods: AllowedMethods.ALLOW_ALL,
|
|
284
|
+
cachedMethods: CachedMethods.CACHE_GET_HEAD_OPTIONS,
|
|
285
|
+
compress: true,
|
|
286
|
+
cachePolicy,
|
|
287
|
+
originRequestPolicy,
|
|
288
|
+
...(cfDistributionProps.defaultBehavior || {}),
|
|
289
|
+
edgeLambdas: [
|
|
290
|
+
{
|
|
291
|
+
includeBody: true,
|
|
292
|
+
eventType: LambdaEdgeEventType.ORIGIN_REQUEST,
|
|
293
|
+
functionVersion,
|
|
294
|
+
},
|
|
295
|
+
...(cfDistributionProps.defaultBehavior?.edgeLambdas || []),
|
|
296
|
+
],
|
|
297
|
+
};
|
|
251
298
|
}
|
|
252
299
|
generateBuildId() {
|
|
253
300
|
const filePath = path.join(this.props.path, ".next/BUILD_ID");
|
package/constructs/SsrSite.d.ts
CHANGED
|
@@ -4,7 +4,6 @@ import { Function as CdkFunction, FunctionProps } from "aws-cdk-lib/aws-lambda";
|
|
|
4
4
|
import { IHostedZone } from "aws-cdk-lib/aws-route53";
|
|
5
5
|
import { Distribution, ICachePolicy, BehaviorOptions, CachePolicy } from "aws-cdk-lib/aws-cloudfront";
|
|
6
6
|
import { ICertificate } from "aws-cdk-lib/aws-certificatemanager";
|
|
7
|
-
import { S3Origin } from "aws-cdk-lib/aws-cloudfront-origins";
|
|
8
7
|
import { SSTConstruct } from "./Construct.js";
|
|
9
8
|
import { NodeJSProps, Function } from "./Function.js";
|
|
10
9
|
import { EdgeFunction } from "./EdgeFunction.js";
|
|
@@ -198,7 +197,7 @@ export declare class SsrSite extends Construct implements SSTConstruct {
|
|
|
198
197
|
protected props: SsrSiteNormalizedProps;
|
|
199
198
|
private doNotDeploy;
|
|
200
199
|
protected buildConfig: SsrBuildConfig;
|
|
201
|
-
|
|
200
|
+
protected serverLambdaForEdge?: EdgeFunction;
|
|
202
201
|
protected serverLambdaForRegional?: CdkFunction;
|
|
203
202
|
private serverLambdaForDev?;
|
|
204
203
|
private bucket;
|
|
@@ -264,14 +263,14 @@ export declare class SsrSite extends Construct implements SSTConstruct {
|
|
|
264
263
|
private createFunctionPermissionsForEdge;
|
|
265
264
|
private validateCloudFrontDistributionSettings;
|
|
266
265
|
protected createCloudFrontDistributionForRegional(): Distribution;
|
|
267
|
-
|
|
266
|
+
protected createCloudFrontDistributionForEdge(): Distribution;
|
|
268
267
|
protected buildDistributionDomainNames(): string[];
|
|
269
|
-
|
|
270
|
-
private
|
|
268
|
+
protected buildDefaultBehaviorForRegional(): BehaviorOptions;
|
|
269
|
+
private buildDefaultBehaviorForEdge;
|
|
271
270
|
private buildBehaviorFunctionAssociations;
|
|
272
|
-
|
|
273
|
-
protected
|
|
274
|
-
protected
|
|
271
|
+
private buildStaticFileBehaviors;
|
|
272
|
+
protected buildServerCachePolicy(): CachePolicy;
|
|
273
|
+
protected buildServerOriginRequestPolicy(): import("aws-cdk-lib/aws-cloudfront").IOriginRequestPolicy;
|
|
275
274
|
private createCloudFrontInvalidation;
|
|
276
275
|
protected validateCustomDomainSettings(): void;
|
|
277
276
|
protected lookupHostedZone(): IHostedZone | undefined;
|