Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion examples/aws-router/sst.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ export default $config({
access: "public",
});
const router = new sst.aws.Router("MyRouter", {
// Use the AWS-managed CachingDisabled policy so this distribution is
// compatible with the CloudFront Free Tier and safe for the API route.
cachePolicy: sst.aws.cloudfront.cachePolicy.cachingDisabled,
routes: {
"/api/*": api.url,
"/*": $interpolate`https://${bucket.domain}`,
"/*": { bucket },
},
});

Expand Down
28 changes: 27 additions & 1 deletion platform/src/components/aws/cdn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,12 @@ export interface CdnArgs {
* The default cache behavior for this distribution.
*/
defaultCacheBehavior: cloudfront.DistributionArgs["defaultCacheBehavior"];
/**
* The cache policy to use for the default cache behavior.
*
* When set, this overrides the cache policy configured on `defaultCacheBehavior`.
*/
cachePolicy?: Input<string>;
/**
* An ordered list of cache behaviors for this distribution. Listed in order of precedence. The first cache behavior will have precedence 0.
*/
Expand Down Expand Up @@ -385,7 +391,27 @@ export class Cdn extends Component {
enabled: true,
origins: args.origins,
originGroups: args.originGroups,
defaultCacheBehavior: args.defaultCacheBehavior,
defaultCacheBehavior: all([
args.defaultCacheBehavior,
args.cachePolicy,
]).apply(([behavior, cachePolicy]) => {
const effectiveCachePolicy =
cachePolicy ?? behavior.cachePolicyId;
if (!effectiveCachePolicy) return behavior;

const {
forwardedValues: _forwardedValues,
minTtl: _minTtl,
defaultTtl: _defaultTtl,
maxTtl: _maxTtl,
...rest
} = behavior;

return {
...rest,
cachePolicyId: effectiveCachePolicy,
};
}),
orderedCacheBehaviors: args.orderedCacheBehaviors,
defaultRootObject: args.defaultRootObject,
customErrorResponses: args.customErrorResponses,
Expand Down
19 changes: 19 additions & 0 deletions platform/src/components/aws/cloudfront.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* AWS-managed CloudFront policies and configuration values.
*/
export const cloudfront = {
cachePolicy: {
/**
* Disables caching. This policy is useful for dynamic content and APIs.
*
* @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html
*/
cachingDisabled: "4135ea2d-6df8-44a3-9df3-4b5a84be39ad",
/**
* Optimizes cache efficiency by minimizing values included in the cache key.
*
* @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html
*/
cachingOptimized: "658327ea-f89d-4fab-a63d-7e88639e58f6",
},
} as const;
1 change: 1 addition & 0 deletions platform/src/components/aws/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export * from "./auth.js";
export * from "./bucket.js";
export * from "./bus.js";
export * from "./cluster.js";
export * from "./cloudfront.js";
export * from "./cognito-identity-pool.js";
export * from "./cognito-user-pool.js";
export * from "./cron.js";
Expand Down
19 changes: 16 additions & 3 deletions platform/src/components/aws/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1043,6 +1043,15 @@ export interface RouterArgs {
}
>;

/**
* The CloudFront cache policy to use by default for routes that do not specify
* their own policy.
*
* By default, SST creates a cache policy for server routes and uses CloudFront's
* managed CachingOptimized policy for bucket routes.
*/
cachePolicy?: Input<string>;

/**
* Configure Lambda function URL protection through CloudFront Origin Access Control.
*
Expand Down Expand Up @@ -1874,6 +1883,8 @@ async function handler(event) {
}

function createCachePolicy() {
if (args.cachePolicy) return undefined;

defaultCachePolicy =
defaultCachePolicy ??
new cloudfront.CachePolicy(
Expand Down Expand Up @@ -1962,9 +1973,11 @@ async function handler(event) {
"PUT",
],
cachedMethods: ["GET", "HEAD"],
defaultTtl: 0,
compress: true,
cachePolicyId: route.cachePolicy ?? createCachePolicy().id,
cachePolicyId:
route.cachePolicy ??
args.cachePolicy ??
createCachePolicy()!.id,
// CloudFront's Managed-AllViewerExceptHostHeader policy
originRequestPolicyId:
"b689b0a8-53d0-40ab-baf2-68738e2966ac",
Expand Down Expand Up @@ -2061,7 +2074,7 @@ async function handler(event) {
const kvStoreArn = createRequestKvStore();
const requestFunction = createRequestFunction();
const responseFunction = createResponseFunction();
const cachePolicyId = createCachePolicy().id;
const cachePolicyId = args.cachePolicy ?? createCachePolicy()!.id;
const edgeFunction = createLambdaEdgeFunction();
const distribution = createDistribution();

Expand Down
9 changes: 8 additions & 1 deletion platform/src/components/aws/static-site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,12 @@ export interface StaticSiteArgs extends BaseStaticSiteArgs {
* ```
*/
domain?: CdnArgs["domain"];
/**
* The CloudFront cache policy to use for the default cache behavior.
*
* By default, CloudFront's managed CachingOptimized policy is used.
*/
cachePolicy?: Input<string>;
/**
* @deprecated The `router` prop is now the recommended way to serve your site
* through a `Router` component.
Expand Down Expand Up @@ -1199,7 +1205,8 @@ async function handler(event) {
cachedMethods: ["GET", "HEAD"],
compress: true,
// CloudFront's managed CachingOptimized policy
cachePolicyId: "658327ea-f89d-4fab-a63d-7e88639e58f6",
cachePolicyId:
args.cachePolicy ?? "658327ea-f89d-4fab-a63d-7e88639e58f6",
functionAssociations: all([
createRequestFunction(),
createResponseFunction(),
Expand Down
87 changes: 87 additions & 0 deletions platform/test/components/cdn.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { describe, expect, it, vi } from "vitest";
import * as pulumi from "@pulumi/pulumi";

vi.mock(
"../../src/components/aws/providers/distribution-deployment-waiter.js",
() => ({
DistributionDeploymentWaiter: class {
isDone = pulumi.output(true);
},
}),
);

// @ts-ignore
global.$app = { name: "app", stage: "test" };
global.$util = pulumi;

pulumi.runtime.setMocks(
{
newResource: (args: pulumi.runtime.MockResourceArgs) => ({
id: `${args.inputs.name}_id`,
state: {
...args.inputs,
domainName: `${args.inputs.name}.cloudfront.net`,
hostedZoneId: "Z2FDTNDATAQYW2",
aliases: args.inputs.aliases ?? [],
etag: "etag",
},
}),
call: (args: pulumi.runtime.MockCallArgs) => args.inputs,
},
"project",
"stack",
false,
);

describe("Cdn", () => {
it("overrides the default behavior cache policy", async () => {
const { Cdn } = await import("../../src/components/aws/cdn");
const cdn = new Cdn("TestCdn", {
origins: [
{
originId: "default",
domainName: "example.com",
customOriginConfig: {
httpPort: 80,
httpsPort: 443,
originProtocolPolicy: "https-only",
originSslProtocols: ["TLSv1.2"],
},
},
],
cachePolicy: "managed-policy",
defaultCacheBehavior: {
targetOriginId: "default",
viewerProtocolPolicy: "redirect-to-https",
allowedMethods: ["GET", "HEAD"],
cachedMethods: ["GET", "HEAD"],
forwardedValues: {
queryString: true,
cookies: {
forward: "none",
},
},
defaultTtl: 60,
cachePolicyId: "sst-policy",
},
});

await new Promise<void>((resolve, reject) => {
cdn.nodes.distribution.apply((distribution) => {
pulumi.output(distribution.defaultCacheBehavior).apply((behavior) => {
try {
expect(behavior).toMatchObject({
targetOriginId: "default",
cachePolicyId: "managed-policy",
});
expect(behavior.forwardedValues).toBeUndefined();
expect(behavior.defaultTtl).toBeUndefined();
resolve();
} catch (error) {
reject(error);
}
});
});
});
});
});