Skip to content
This repository was archived by the owner on Jan 28, 2025. It is now read-only.

Commit 05fb0eb

Browse files
feat(lambda-at-edge-compat, lambda-at-edge, nextjs-component): let CloudFront do the Gzipping (#692)
1 parent ef2d596 commit 05fb0eb

File tree

13 files changed

+108
-18
lines changed

13 files changed

+108
-18
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ The fourth cache behaviour handles next API requests `api/*`.
432432
| logLambdaExecutionTimes | `boolean` | `false` | Logs to CloudWatch the default handler performance metrics. |
433433
| minifyHandlers | `boolean` | `false` | Use pre-built minified handlers to reduce code size. Does not minify custom handlers. |
434434
| deploy | `boolean` | `true` | Whether to deploy resources to AWS. Useful if you just need the Lambdas and assets but want to deploy them yourself (available in latest alpha). |
435+
| enableHTTPCompression | `boolean` | `false` | When set to `true` the Lambda@Edge functions for SSR and API requests will use Gzip to compress the response. Note that you shouldn't need to enable this because CloudFront will compress responses for you out of the box. |
435436

436437
Custom inputs can be configured like this:
437438

packages/compat-layers/lambda-at-edge-compat/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,13 @@ module.exports.render = async (event, context) => {
2020
return responsePromise;
2121
};
2222
```
23+
24+
## Options
25+
26+
### Gzip compression
27+
28+
```js
29+
const { req, res, responsePromise } = cloudFrontCompat(event.Records[0].cf, {
30+
enableHTTPCompression: true // false by default
31+
});
32+
```

packages/compat-layers/lambda-at-edge-compat/__tests__/next-aws-cloudfront.response.test.js

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -371,11 +371,10 @@ describe("Response Tests", () => {
371371
});
372372
});
373373

374-
it(`gzips`, () => {
375-
expect.assertions(2);
374+
it("does not gzip by default", () => {
375+
expect.assertions(3);
376376

377377
const gzipSpy = jest.spyOn(zlib, "gzipSync");
378-
gzipSpy.mockReturnValueOnce(Buffer.from("ok-gzipped"));
379378

380379
const { res, responsePromise } = create({
381380
request: {
@@ -393,6 +392,40 @@ describe("Response Tests", () => {
393392

394393
res.end("ok");
395394

395+
return responsePromise.then((response) => {
396+
expect(gzipSpy).not.toBeCalled();
397+
expect(response.headers["content-encoding"]).not.toBeDefined();
398+
expect(response.body).toEqual("b2s=");
399+
});
400+
});
401+
402+
it(`gzips when compression is enabled`, () => {
403+
expect.assertions(2);
404+
405+
const gzipSpy = jest.spyOn(zlib, "gzipSync");
406+
gzipSpy.mockReturnValueOnce(Buffer.from("ok-gzipped"));
407+
408+
const { res, responsePromise } = create(
409+
{
410+
request: {
411+
path: "/",
412+
headers: {
413+
"accept-encoding": [
414+
{
415+
key: "Accept-Encoding",
416+
value: "gzip"
417+
}
418+
]
419+
}
420+
}
421+
},
422+
{
423+
enableHTTPCompression: true
424+
}
425+
);
426+
427+
res.end("ok");
428+
396429
gzipSpy.mockRestore();
397430

398431
return responsePromise.then((response) => {

packages/compat-layers/lambda-at-edge-compat/next-aws-cloudfront.d.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
import { CloudFrontResultResponse, CloudFrontRequest } from "aws-lambda";
22
import { IncomingMessage, ServerResponse } from "http";
33

4-
declare function lambdaAtEdgeCompat(event: {
5-
request: CloudFrontRequest;
6-
}): {
4+
type CompatOptions = {
5+
enableHTTPCompression: boolean;
6+
};
7+
8+
declare function lambdaAtEdgeCompat(
9+
event: {
10+
request: CloudFrontRequest;
11+
},
12+
options: CompatOptions
13+
): {
714
responsePromise: Promise<CloudFrontResultResponse>;
815
req: IncomingMessage;
916
res: ServerResponse;

packages/compat-layers/lambda-at-edge-compat/next-aws-cloudfront.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,11 @@ const isGzipSupported = (headers) => {
144144
return gz;
145145
};
146146

147-
const handler = (event) => {
147+
const defaultOptions = {
148+
enableHTTPCompression: false
149+
};
150+
151+
const handler = (event, { enableHTTPCompression } = defaultOptions) => {
148152
const { request: cfRequest, response: cfResponse = { headers: {} } } = event;
149153

150154
const response = {
@@ -227,7 +231,7 @@ const handler = (event) => {
227231
]);
228232
};
229233

230-
let gz = isGzipSupported(headers);
234+
let shouldGzip = enableHTTPCompression && isGzipSupported(headers);
231235

232236
const responsePromise = new Promise((resolve) => {
233237
res.end = (text) => {
@@ -245,14 +249,14 @@ const handler = (event) => {
245249

246250
if (response.body) {
247251
response.bodyEncoding = "base64";
248-
response.body = gz
252+
response.body = shouldGzip
249253
? zlib.gzipSync(response.body).toString("base64")
250254
: Buffer.from(response.body).toString("base64");
251255
}
252256

253257
response.headers = toCloudFrontHeaders(res.headers, cfResponse.headers);
254258

255-
if (gz) {
259+
if (shouldGzip) {
256260
response.headers["content-encoding"] = [
257261
{ key: "Content-Encoding", value: "gzip" }
258262
];

packages/e2e-tests/next-app/cypress/integration/pages.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ describe("Pages Tests", () => {
1818
(method) => {
1919
it(`allows HTTP method for path ${path}: ${method}`, () => {
2020
cy.request({ url: path, method: method }).then((response) => {
21+
if (method !== "HEAD") {
22+
cy.verifyResponseIsCompressed(response);
23+
}
2124
expect(response.status).to.equal(200);
2225
});
2326
});
@@ -46,6 +49,9 @@ describe("Pages Tests", () => {
4649
(method) => {
4750
it(`allows HTTP method for path ${path}: ${method}`, () => {
4851
cy.request({ url: path, method: method }).then((response) => {
52+
if (method !== "HEAD") {
53+
cy.verifyResponseIsCompressed(response);
54+
}
4955
expect(response.status).to.equal(200);
5056
});
5157
});

packages/e2e-tests/test-utils/cypress/custom-commands.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ declare namespace Cypress {
4444
response: Cypress.Response,
4545
shouldBeCached: boolean
4646
) => Cypress.Chainable<JQuery>;
47+
verifyResponseIsCompressed: (
48+
response: Cypress.Response
49+
) => Cypress.Chainable<JQuery>;
4750
}
4851
}
4952

@@ -140,3 +143,10 @@ Cypress.Commands.add(
140143
}
141144
}
142145
);
146+
147+
Cypress.Commands.add(
148+
"verifyResponseIsCompressed",
149+
(response: Cypress.Response) => {
150+
expect(response.headers["content-encoding"]).to.equal("gzip");
151+
}
152+
);

packages/libs/lambda-at-edge/src/api-handler.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
OriginRequestEvent,
99
RoutesManifest
1010
} from "../types";
11-
import { CloudFrontResultResponse, CloudFrontRequest } from "aws-lambda";
11+
import { CloudFrontResultResponse } from "aws-lambda";
1212
import {
1313
createRedirectResponse,
1414
getDomainRedirectPath,
@@ -103,7 +103,9 @@ export const handler = async (
103103

104104
// eslint-disable-next-line
105105
const page = require(`./${pagePath}`);
106-
const { req, res, responsePromise } = cloudFrontCompat(event.Records[0].cf);
106+
const { req, res, responsePromise } = cloudFrontCompat(event.Records[0].cf, {
107+
enableHTTPCompression: buildManifest.enableHTTPCompression
108+
});
107109

108110
page.default(req, res);
109111

packages/libs/lambda-at-edge/src/build.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type BuildOptions = {
3535
logLambdaExecutionTimes?: boolean;
3636
domainRedirects?: { [key: string]: string };
3737
minifyHandlers?: boolean;
38+
enableHTTPCompression?: boolean;
3839
handler?: string;
3940
};
4041

@@ -46,7 +47,8 @@ const defaultBuildOptions = {
4647
useServerlessTraceTarget: false,
4748
logLambdaExecutionTimes: false,
4849
domainRedirects: {},
49-
minifyHandlers: false
50+
minifyHandlers: false,
51+
enableHTTPCompression: true
5052
};
5153

5254
class Builder {
@@ -382,7 +384,8 @@ class Builder {
382384
);
383385
const {
384386
logLambdaExecutionTimes = false,
385-
domainRedirects = {}
387+
domainRedirects = {},
388+
enableHTTPCompression = false
386389
} = this.buildOptions;
387390

388391
this.normalizeDomainRedirects(domainRedirects);
@@ -402,15 +405,17 @@ class Builder {
402405
},
403406
publicFiles: {},
404407
trailingSlash: false,
405-
domainRedirects: domainRedirects
408+
domainRedirects: domainRedirects,
409+
enableHTTPCompression
406410
};
407411

408412
const apiBuildManifest: OriginRequestApiHandlerManifest = {
409413
apis: {
410414
dynamic: {},
411415
nonDynamic: {}
412416
},
413-
domainRedirects: domainRedirects
417+
domainRedirects: domainRedirects,
418+
enableHTTPCompression
414419
};
415420

416421
const ssrPages = defaultBuildManifest.pages.ssr;

packages/libs/lambda-at-edge/src/default-handler.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,12 @@ const handleOriginRequest = async ({
387387
log("require JS execution time", tBeforePageRequire, tAfterPageRequire);
388388

389389
const tBeforeSSR = now();
390-
const { req, res, responsePromise } = lambdaAtEdgeCompat(event.Records[0].cf);
390+
const { req, res, responsePromise } = lambdaAtEdgeCompat(
391+
event.Records[0].cf,
392+
{
393+
enableHTTPCompression: manifest.enableHTTPCompression
394+
}
395+
);
391396
try {
392397
// If page is _error.js, set status to 404 so _error.js will render a 404 page
393398
if (pagePath === "pages/_error.js") {
@@ -466,7 +471,10 @@ const handleOriginResponse = async ({
466471
// eslint-disable-next-line
467472
const page = require(`./${pagePath}`);
468473
const { req, res, responsePromise } = lambdaAtEdgeCompat(
469-
event.Records[0].cf
474+
event.Records[0].cf,
475+
{
476+
enableHTTPCompression: manifest.enableHTTPCompression
477+
}
470478
);
471479
const isSSG = !!page.getStaticProps;
472480
const { renderOpts, html } = await page.renderReqToHTML(

packages/libs/lambda-at-edge/types.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export type OriginRequestApiHandlerManifest = {
2121
domainRedirects: {
2222
[key: string]: string;
2323
};
24+
enableHTTPCompression: boolean;
2425
};
2526

2627
export type OriginRequestDefaultHandlerManifest = {
@@ -44,6 +45,7 @@ export type OriginRequestDefaultHandlerManifest = {
4445
[key: string]: string;
4546
};
4647
trailingSlash: boolean;
48+
enableHTTPCompression: boolean;
4749
domainRedirects: {
4850
[key: string]: string;
4951
};

packages/serverless-components/nextjs-component/src/component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ class NextjsComponent extends Component {
198198
logLambdaExecutionTimes: inputs.logLambdaExecutionTimes || false,
199199
domainRedirects: inputs.domainRedirects || {},
200200
minifyHandlers: inputs.minifyHandlers || false,
201+
enableHTTPCompression: false,
201202
handler: inputs.handler
202203
? `${inputs.handler.split(".")[0]}.js`
203204
: undefined

packages/serverless-components/nextjs-component/types.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export type ServerlessComponentInputs = {
2323
minifyHandlers?: boolean;
2424
uploadStaticAssetsFromBuild?: boolean;
2525
deploy?: boolean;
26+
enableHTTPCompression?: boolean;
2627
};
2728

2829
type CloudfrontOptions = Record<string, any>;

0 commit comments

Comments
 (0)