Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CDK Construct #878

Merged
merged 34 commits into from
Feb 16, 2021
Merged

CDK Construct #878

merged 34 commits into from
Feb 16, 2021

Conversation

kirkness
Copy link
Collaborator

@kirkness kirkness commented Jan 18, 2021

This may be wildly out of scope for this project at the moment(⁉️) as per #777. I've been using CDK + Next.js with the sls-next Builder for around a year now on a couple of different projects. To make this possible I've evolved this CDK Construct which is by now fairly well battle-tested; all the while trying to keep it in line with the way that the serverless-component works. I'm quite keen on open-sourcing it and think it makes the most sense to exist in this project. Keen to know whether this is something that is of interest including in the project at the moment?

Todo:

  • Documentation
  • Unit tests
  • Reuse cache logic from serverless component
  • Function names should be more descriptive
  • Fix custom behaviors (should use new format)
  • Fix static image bevaviour
  • Identify why minify handlers returns 503

Future possibilities:

  • Abstract infrastructure synthesis so that logic is shared between serverless and cdk deployments
  • Add built-in optional builder
  • Write using Jsii so that this construct can be used within other CDK languages

Side-note: I'm also keen on getting more involved with the maintenance and support for this project if there still a need (especially helping on the work outlined in #777).

@codecov
Copy link

codecov bot commented Jan 18, 2021

Codecov Report

Merging #878 (5d0a370) into master (9b89a3b) will increase coverage by 0.57%.
The diff coverage is 97.63%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #878      +/-   ##
==========================================
+ Coverage   80.73%   81.31%   +0.57%     
==========================================
  Files          64       68       +4     
  Lines        2403     2488      +85     
  Branches      561      591      +30     
==========================================
+ Hits         1940     2023      +83     
+ Misses        399      398       -1     
- Partials       64       67       +3     
Impacted Files Coverage Δ
...rless-components/nextjs-cdk-construct/src/index.ts 94.54% <94.54%> (ø)
packages/libs/s3-static-assets/src/index.ts 100.00% <100.00%> (ø)
...ibs/s3-static-assets/src/lib/readDirectoryFiles.ts 100.00% <100.00%> (+11.11%) ⬆️
...tjs-cdk-construct/src/utils/readAssetsDirectory.ts 100.00% <100.00%> (ø)
...uct/src/utils/readInvalidationPathsFromManifest.ts 100.00% <100.00%> (ø)
...cdk-construct/src/utils/reduceInvalidationPaths.ts 100.00% <100.00%> (ø)
...s/nextjs-cdk-construct/src/utils/toLambdaOption.ts 100.00% <100.00%> (ø)
... and 2 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9b89a3b...5d0a370. Read the comment docs.

@danielcondemarin
Copy link
Contributor

This is awesome @kirkness 🙌

Actually I think this aligns well with what @dphang outlined in #777. There is the question of CDK vs CDK for Terraform, but I don't see why we shouldn't make a start with plain CDK if you've done all the hard work already.
The most important thing is that CDK solves the infrastructure state problem we've always had with serverless components beta. It also opens up the possibility of supporting serverless components GA via CloudFormation which I've briefly discussed with @dphang. Plus CDK is not going away any time soon as AWS is investing a lot in it.

I will try to test out this branch over the weekend and will feedback.

'm also keen on getting more involved with the maintenance and support for this project if there still a need

We do need more contributors and I can add you to the repo's maintainers if that's something you're interested in 🙂

CC'ing @dphang for any thoughts on this.

destinationKeyPrefix: staticAsset,
distributionPaths: ["/*"],
sources: [s3Deploy.Source.asset(assetPath)],
cacheControl: [
Copy link
Collaborator

@dphang dphang Jan 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Static Pages would need different cache control here: public, max-age=0, s-maxage=2678400, must-revalidate.

And also in the latest 1.19 alpha, we are now versioning pages so only the last two deployments are kept to save S3 space.

See the code here:

const deleteOldStaticAssets = async (
. Would be good to have this as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will have a look into how static pages are being versioned, but I presume this means no invalidation is required for these either which is nice! This is something I wanted to revisit as it's currently creating an invalidation on all resources every deploy which is unnecessary given everything is versioned?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And also in the latest 1.19 alpha, we are now versioning pages so only the last two deployments are kept to save S3 space.

I believe this is actually handled by CDK itself as it automatically 'syncs' source assets, deleting ones that no longer exist. I'm assuming the reason you are keeping the last 2 deployments is for safety and thus this behavior is not needed for the construct?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dphang - update on this here. I've refactored the uploadStaticAssetsFromBuild which was determining the cache-control for files so that it can be used in the CDK construct.

@@ -0,0 +1 @@
exports.handler = async () => ({ statusCode: 200 });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For better readability of all these added JS/JSON files, could you please format these test files using prettier?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in ea6cc16

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although, prettier has not updated __tests__/fixtures/next-boilerplate/default-lambda/index.js...

@dphang
Copy link
Collaborator

dphang commented Jan 19, 2021

@kirkness thanks a lot for this! Yea, we have been talking about adding CDK for a while now but just haven't had resources. So this is a good first step! We are also trying to upgrade to Serverless Components GA, if you are interested as mentioned it might go hand-in-hand with this as we are trying to make deployments more seamless in the near future (+ there is significant financial sponsorship (like $1k+) from Serverless Framework for doing so, which we would be happy to split based on resourcing). Do let @danielcondemarin know and we can add you to the Slack too.

@kirkness
Copy link
Collaborator Author

Great news, would be awesome to join you on Slack to kick this off! My email is just my first name (Henry) + my company domain (planes.studio).

In the meantime, I'll revert this PR to a draft and flesh out a plan of attack with some of the additional work that's needed to go into it.

@kirkness kirkness marked this pull request as draft January 19, 2021 11:37
bucketName,
credentials: credentials
});
const getAssetDirectoryFileCachePolicies = (
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've taken all the logic from uploadStaticAssetsFromBuild that determines the cache-control for each file in /assets and refactored it to be agnostic to the deployment mechanism. This file is imported into the CDK construct now, whilst also used below in uploadStaticAssetsFromBuild. I think we could do with a grander refactor of this form that removes the deployment code from the synthesis.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This didn't make sense to use in the end as the data structure required to work with the constructs didn't fit this output enough to make it worth it. Happy to revert the changes to this module if you think its needed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be ok to keep it.

@@ -1,3 +0,0 @@
import { Item } from "klaw";

export default (fileItem: Item): boolean => !fileItem.stats.isDirectory();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed anymore as we get fast-glob to only read files.

@iDVB
Copy link

iDVB commented Jan 25, 2021

Thanks for working on this @kirkness!
Our company may be interested in the plain construct and helping out if we can.

One thing to think about is, have you considered the potential order of events that may be needed before the frontend assets are deployed? One think I've come up against when using CDK to deploy frontend assets is that there is currently no way that I saw to have "backend" infra deploy first and to pass API endpoints, outputs, into the frontend construct so they can be bundled into the frontend assets. Have you found away around this? Is there anything I might be able to help out with in these efforts?

@kirkness
Copy link
Collaborator Author

One thing to think about is, have you considered the potential order of events that may be needed before the frontend assets are deployed? One think I've come up against when using CDK to deploy frontend assets is that there is currently no way that I saw to have "backend" infra deploy first and to pass API endpoints, outputs, into the frontend construct so they can be bundled into the frontend assets. Have you found away around this? Is there anything I might be able to help out with in these efforts?

👍, sure I think I know what you mean, as you might:

  1. Build the next app (this only point in time in which you can set Next.js env variables)
  2. Synth + Deploy your app via CDK (this is the point in time in which you have the values that you might use for your env vars, e.g. API endpoints, S3 bucket names...)

I think at the very least, if the bundling was done within the construct, then you'd be able to pass in non-tokenized values as env vars, all inline with your CDK code.

@iDVB
Copy link

iDVB commented Jan 25, 2021

I think at the very least, if the bundling was done within the construct, then you'd be able to pass in non-tokenized values as env vars, all inline with your CDK code.

This would be SUPER ideal. Any work towards this working now?

@kirkness
Copy link
Collaborator Author

kirkness commented Jan 25, 2021

I think at the very least, if the bundling was done within the construct, then you'd be able to pass in non-tokenized values as env vars, all inline with your CDK code.

This would be SUPER ideal. Any work towards this working now?

I think all that would be needed is to set up Asset Bundeling, which would allow running the Builder in a Docker container... By the looks of it you can pass environment into the Docker container. If this accepts tokenized values, it solves the entire problem, if not, then still pretty neat.

Might have a look at a POC now. Watch this space. 👍

@kirkness
Copy link
Collaborator Author

Might have a look at a POC now. Watch this space. 👍

Parking this now, the CDK asset bundling process starts after the CDK constructs are created. At the point when we create the Next.js construct we need the next.js app to be bundled, this is because the infrastructure bases itself on the shape of the bundled output. So similar in part to the issue you outlined with env variables (that this was trying to resolve). On top of this, the env vars passed into the asset bundling process are not resolved, and so you end up with tokenized values.

More research needed - an easy way would be just to bundle without the use of the asset bundling construct, although keen to find a best-practice approach!

@kirkness
Copy link
Collaborator Author

I think LGTM in general, just a few minor comments and not sure if it's working with i18n subpaths (that was a recent change I made)

Cheers @dphang - just ran through those changes. Is i18n a blocker/substantial change? Will put in follow up PR if all good with you?

@dphang
Copy link
Collaborator

dphang commented Feb 11, 2021

I think LGTM in general, just a few minor comments and not sure if it's working with i18n subpaths (that was a recent change I made)

Cheers @dphang - just ran through those changes. Is i18n a blocker/substantial change? Will put in follow up PR if all good with you?

I think it should probably work as is since it was just modifying the output HTML/JSON files to .serverless_nextjs to add the locale files, and also I updated the default Lambda handler. As long as you are uploading those, it should work. Yea, you can follow up in another PR.

Also, looks like some of the new tests in the CI build is failing now.

Thanks for this!

@kirkness
Copy link
Collaborator Author

I think LGTM in general, just a few minor comments and not sure if it's working with i18n subpaths (that was a recent change I made)

Cheers @dphang - just ran through those changes. Is i18n a blocker/substantial change? Will put in follow up PR if all good with you?

I think it should probably work as is since it was just modifying the output HTML/JSON files to .serverless_nextjs to add the locale files, and also I updated the default Lambda handler. As long as you are uploading those, it should work. Yea, you can follow up in another PR.

Also, looks like some of the new tests in the CI build is failing now.

Thanks for this!

Nice, sounds like it'll work then as each directory of the output is uploaded to s3/lambda as the sls component does. Ive updated the test snapshots so that they pass.

@dphang
Copy link
Collaborator

dphang commented Feb 14, 2021

I think LGTM in general, just a few minor comments and not sure if it's working with i18n subpaths (that was a recent change I made)

Cheers @dphang - just ran through those changes. Is i18n a blocker/substantial change? Will put in follow up PR if all good with you?

I think it should probably work as is since it was just modifying the output HTML/JSON files to .serverless_nextjs to add the locale files, and also I updated the default Lambda handler. As long as you are uploading those, it should work. Yea, you can follow up in another PR.
Also, looks like some of the new tests in the CI build is failing now.
Thanks for this!

Nice, sounds like it'll work then as each directory of the output is uploaded to s3/lambda as the sls component does. Ive updated the test snapshots so that they pass.

Thanks, I ran e2e tests here: https://github.com/serverless-nextjs/serverless-next.js/actions/runs/565470296

@dphang
Copy link
Collaborator

dphang commented Feb 16, 2021

@kirkness looks like everything is good. LMK if ready to merge?

@kirkness
Copy link
Collaborator Author

@kirkness looks like everything is good. LMK if ready to merge?

Cheers @dphang, good to merge from my perspective!

@dphang dphang merged commit d38e2aa into serverless-nextjs:master Feb 16, 2021
@ibrahimcesar
Copy link

Great work! I am looking towards this for some time. There are some example repo for me take a look? Tried to add to a codebase and even started from scratch but cdk deploy only generates a new config, cdk.out remains empty and the command stalled. When I added the builder debug flag true it just showme the log for the application starting.

Just a barebones example besides the code provided so far in the docs would be great, Because of my limits I was not able to use it 😓

Thanks in advance!

@kirkness
Copy link
Collaborator Author

Hey @ibrahimcesar - sounds odd, I don't suppose you could push your example up to a repo (just the one you did from scratch)?

@ibrahimcesar
Copy link

Sure @kirkness !
Please note I maybe missing something crucial, just trying to understand how to use. I think is a really great work. This is my PoC repo: https://github.com/ibrahimcesar/nextjs-starter-cdk-constructor

Thanks!

@kirkness
Copy link
Collaborator Author

Sure @kirkness !
Please note I maybe missing something crucial, just trying to understand how to use. I think is a really great work. This is my PoC repo: https://github.com/ibrahimcesar/nextjs-starter-cdk-constructor

Thanks!

Cheers @ibrahimcesar, I've updated the docs in this PR, it looks like you may have a couple of other unrelated issues in that repo, but hopefully they'll be obvious when it actually starts to build.

@ibrahimcesar
Copy link

Thanks @kirkness ,

Yes, there are some configs missing, but "life finds a way"
I was able to deploy as expected, thanks a lot for the help! 👍🏼

One hookie mistake I made, and l'll leave here as reference, is that I changed my next.config.js to

module.exports = {
  distDir: 'build',
}

Since I saw it was accessing the files at "build", but just leave the default build, or will fail because it will lookup in .next.

Updated my simple repo with a working version! Thanks a lot!

@iDVB
Copy link

iDVB commented Feb 28, 2021

Wow, greet work @kirkness !

Now, my specific interest is in the CDK construct alone.
What is the current state of that now since this merge?

  • does it work on its own?
  • Did you overcome the issue we spoke about in regards to baking stack values into the frontend build?
  • Does it have feature parody with hosting on Vercel? What’s missing?

@donaldpipowitch
Copy link

donaldpipowitch commented Mar 1, 2021

(FYI for others: Not sure if it's my fault, but I tried to play around with this CDK Construct and always hit a s3:PutBucketPolicy Access Denied error. As far as I can tell I should have this permission (also tried with {Allow:*, Resource:*}). There are some other Access Denied errors reported recently, but they seem to be a different problem. I tried to downgrade several libs - either from @sls-next or @aws-cdk and tried different regions (us and eu), but no luck. Just wanted to share this in case others have this problem as well.

Update: So apparently it is a config problem on my side, but not related to role permissions, but the account wide "Block Public Access settings" in S3. It looks like those settings would overrule anything I'd configure on the bucket, so I need to change them.)

@iDVB
Copy link

iDVB commented Mar 7, 2021

@kirkness When using the CDK construct outside of serverless components. what is the best way to organize your
CDK stack files, package.json etc, vs all the files as part of the nextjs src/build files.

I thought it would just be as simple as building in completely separate directory and just copying in the build files, but the CDK build process needed all the next apps deps and now its saying it needs the src pages directory too.

Not sure if its possible or the best way to keep some separation between the CDK stack files and the nextjs app its deploying?

I'm trying to run things in separate directories and leverage the Builder's config to point things around... and its running now, and starting to build the .next dir of files...
image

However it err's our with...
pages-manifest not found. Check if next.config.js target is set to 'serverless'

next.config.js

module.exports = {
  target: "serverless",
}

/bin/cdk-nextjs.ts

...
const nextConfigDir = path.join(__dirname, '../dvb-next');
const outputDir = path.join(nextConfigDir, ".serverless_nextjs");
const builder = new Builder(nextConfigDir, outputDir,
  {
    cmd: './node_modules/.bin/next',
    cwd: '../dvb-next',
    env: {},
    args: ['build']
  });
...

UPDATE -
Figured out my issue was with my directories. this works

...
const nextConfigDir = path.join(__dirname, '../../dvb-next');
const outputDir = path.join(nextConfigDir, ".serverless_nextjs");
const builder = new Builder(nextConfigDir, outputDir,
  {
    cmd: './node_modules/.bin/next',
    cwd: nextConfigDir,
    env: {},
    args: ['build']
  });
...

@kirkness
Copy link
Collaborator Author

kirkness commented Mar 7, 2021

Glad you got it working @iDVB, feel free to create issues if you have any other problems!

@revmischa
Copy link

Curious, is there a supported/recommended way to deploy a NextJS app with CDK but without Serverless?

@ibrahimcesar
Copy link

@revmischa What do you mean by supported? Like a package? Because you only need to build your own constructor but there's a lot for React applications as whole in community. CDK is a general purpose tool so you can create an EC2 Instance and run NextJS inside or use ECS if your application is on Docker or even Fargate, but this service is considered serverless. But CDK gives you access to create any kind of infrastructure on AWS so you can do as you want.

@revmischa
Copy link

@revmischa What do you mean by supported? Like a package? Because you only need to build your own constructor but there's a lot for React applications as whole in community. CDK is a general purpose tool so you can create an EC2 Instance and run NextJS inside or use ECS if your application is on Docker or even Fargate, but this service is considered serverless. But CDK gives you access to create any kind of infrastructure on AWS so you can do as you want.

Yeah ideally deploying it using the https://docs.aws.amazon.com/cdk/api/latest/docs/aws-lambda-nodejs-readme.html construct. Just not sure on what to put in my handler.

@ibrahimcesar
Copy link

@revmischa I don't think you gonna achieve what you want with this constructor in particular. It is serverless and is a Lambda. The architecture is a bit different, I don't think this is the right forum. You can ask me directly and I can help you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants