Skip to content

Commit

Permalink
Improve bundling of various lambda functions
Browse files Browse the repository at this point in the history
  • Loading branch information
revmischa committed Oct 18, 2022
1 parent f81cffb commit 2db1593
Show file tree
Hide file tree
Showing 17 changed files with 311 additions and 175 deletions.
7 changes: 6 additions & 1 deletion .eslintrc.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions .projen/deps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 12 additions & 1 deletion .projenrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,15 @@ const project = new awscdk.AwsCdkConstructLibrary({
packageName: 'cdk-nextjs-standalone',
description: 'Deploy a NextJS app to AWS using CDK. Uses standalone build and output tracing.',
keywords: ['nextjs', 'next', 'aws-cdk', 'aws', 'cdk', 'standalone', 'iac', 'infrastructure', 'cloud', 'serverless'],
eslintOptions: { prettier: true, ignorePatterns: ['assets/**/*'] },
eslintOptions: {
prettier: true,
// ignorePatterns: ['assets/**/*']
},
majorVersion: 1,

tsconfig: { compilerOptions: { noUnusedLocals: false }, include: ['assets/**/*.ts'] },
tsconfigDev: { compilerOptions: { noUnusedLocals: false } },

bundledDeps: [
'cross-spawn',
'fs-extra',
Expand All @@ -21,6 +27,7 @@ const project = new awscdk.AwsCdkConstructLibrary({
'@types/cross-spawn',
'@types/fs-extra',
'@types/micromatch',
'@types/aws-lambda',
'esbuild',
'aws-lambda',
'serverless-http',
Expand All @@ -30,4 +37,8 @@ const project = new awscdk.AwsCdkConstructLibrary({
// do not generate sample test files
sampleCode: false,
});
// project.eslint.addOverride({
// rules: {},
// });
// project.tsconfig.addInclude('assets/**/*.ts');
project.synth();
94 changes: 65 additions & 29 deletions API.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ new Nextjs(this, 'Web', {
});
```

If using a **monorepo**, you will [need](https://nextjs.org/docs/advanced-features/output-file-tracing#caveats) to point your `next.config.js` at the project root:

```ts
{
...
experimental: {
outputFileTracingRoot: path.join(__dirname, '..'), // if your nextjs app lives one level deep
},
}
```

## Documentation

Available on [Construct Hub](https://constructs.dev/packages/cdk-nextjs-standalone/).
Expand Down Expand Up @@ -74,7 +85,7 @@ All other required dependencies should be bundled by NextJs [output tracing](htt

This module is largely made up of code from the above projects.

## Questions
## Open questions

- Do we need to manually handle CloudFront invalidation? It looks like `BucketDeployment` takes care of that for us
- How is the `public` dir supposed to be handled? (Right now using an OriginGroup to look in the S3 origin first and if 403/404 then try lambda origin)
Expand Down
1 change: 1 addition & 0 deletions assets/lambda/NextJsHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const config: Options = {
dir: __dirname,
minimalMode: true,
};
console.debug('Environment:', JSON.stringify(process.env, null, 2));

// next request handler
const nextHandler = new NextNodeServer(config).getRequestHandler();
Expand Down
72 changes: 72 additions & 0 deletions assets/lambda/S3StaticEnvRewriter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { CdkCustomResourceEvent, CdkCustomResourceHandler } from 'aws-lambda';
import AWS from 'aws-sdk';

async function tryGetObject(bucket, key, tries) {
const s3 = new AWS.S3();
try {
return await s3.getObject({ Bucket: bucket, Key: key }).promise();
} catch (err) {
console.error('Failed to retrieve object', key, err);
// for now.. skip it. might be a rollback and the file is no longer available.
// // if access denied - wait a few seconds and try again
// if (err.code === 'AccessDenied' && tries < 10) {
// console.info('Retrying for object', key);
// await new Promise((res) => setTimeout(res, 5000));
// return tryGetObject(bucket, key, ++tries);
// } else {
// throw err;
// }
}
}

const doRewrites = async (event: CdkCustomResourceEvent) => {
// rewrite static files
const s3 = new AWS.S3();
const { s3keys, bucket, replacements } = event.ResourceProperties;
if (!s3keys || !bucket || !replacements) {
console.error('Missing required properties');
return;
}
const promises = s3keys.map(async (key) => {
// get file
const params = { Bucket: bucket, Key: key };
console.info('Rewriting', key, 'in bucket', bucket);
const res = await tryGetObject(bucket, key, 0);
if (!res) return;

// get body
const bodyPre = res.Body?.toString('utf-8');
if (!bodyPre) return;
let bodyPost = bodyPre;

// do replacements of tokens
Object.entries(replacements as Record<string, string>).forEach(([token, value]) => {
bodyPost = bodyPost.replace(token, value);
});

// didn't change?
if (bodyPost === bodyPre) return;

// upload
console.info('Rewrote', key, 'in bucket', bucket);
const putParams = {
...params,
Body: bodyPost,
ContentType: res.ContentType,
ContentEncoding: res.ContentEncoding,
CacheControl: res.CacheControl,
};
await s3.putObject(putParams).promise();
});
await Promise.all(promises);
};

// search and replace tokenized values of designated objects in s3
export const handler: CdkCustomResourceHandler = async (event) => {
const requestType = event.RequestType;
if (requestType === 'Create' || requestType === 'Update') {
await doRewrites(event);
}

return event;
};
26 changes: 26 additions & 0 deletions assets/lambda@edge/LambdaOriginRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import url from 'url';
import type { CloudFrontRequestHandler } from 'aws-lambda';

/**
* This fixes the "host" header to be the host of the origin.
* The origin is the lambda server function URL.
* If we don't provide its expected "host", it will not know how to route the request.
*/
export const handler: CloudFrontRequestHandler = (event, _context, callback) => {
const request = event.Records[0].cf.request;
// console.log(JSON.stringify(request, null, 2))

// get origin url from header
const originUrlHeader = request.origin?.custom?.customHeaders['x-origin-url'];
if (!originUrlHeader || !originUrlHeader[0]) {
console.error('Origin header wasn"t set correctly, cannot get origin url');
return callback(null, request);
}
const urlHeader = originUrlHeader[0].value;
const originUrl = url.parse(urlHeader, true);
if (!originUrl.host) throw new Error('Origin url host is missing');

request.headers['x-forwarded-host'] = [{ key: 'x-forwarded-host', value: request.headers.host[0].value }];
request.headers.host = [{ key: 'host', value: originUrl.host }];
callback(null, request);
};
2 changes: 2 additions & 0 deletions package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 2db1593

Please sign in to comment.