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

feat: add aws-lambda-edge preset with CDK #240

Draft
wants to merge 38 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
bce1771
feat: add aws-lambda-edge preset
WinterYukky May 14, 2022
c87fde6
docs(aws-lambda-edge): add documentation
WinterYukky May 15, 2022
69049af
docs(aws-lambda-edge): fix broken alert
WinterYukky May 15, 2022
81759fc
feat(prest): add deployment code generater
WinterYukky Jul 7, 2022
7e5deb1
fix(preset): fix empty public dir
WinterYukky Jul 7, 2022
12b179a
docs(preset): update to Zero Config Provider
WinterYukky Jul 7, 2022
8dd657a
docs(preset): do not use REGION env
WinterYukky Jul 7, 2022
a7a7af1
fix(preset): add @types/node
WinterYukky Jul 7, 2022
d0264ab
docs(preset): add aws to Zero-Config Providers
WinterYukky Jul 7, 2022
6024e2e
docs(preset): fix message title
WinterYukky Jul 7, 2022
97a89fe
feat(preset): nitro-asset to npm package
WinterYukky Jul 9, 2022
f174d66
docs(preset): update customization example
WinterYukky Jul 9, 2022
ab707c9
docs(preset): update deploy page composition
WinterYukky Jul 9, 2022
79fd8e4
feat(preset): Easier to set specific region
WinterYukky Jul 11, 2022
ba5f8fc
docs(preset): add all option to deploy command
WinterYukky Jul 11, 2022
fd38fab
chore(preset): remove mkdir process
WinterYukky Jul 11, 2022
9684073
docs(preset): update about bootstrap
WinterYukky Jul 11, 2022
53c185a
docs(preset): add new line to bootstrap command
WinterYukky Jul 11, 2022
2ac76e7
chore(preset): remove bootstrap command
WinterYukky Jul 11, 2022
9b8d39a
Merge branch 'main' of https://github.com/WinterYukky/nitro into feat…
WinterYukky Nov 1, 2022
a62fa09
chore(preset): add general properties
WinterYukky Nov 1, 2022
c51917c
docs(preset): update to docus syntax
WinterYukky Nov 1, 2022
08a7340
fix(docs): warning to alert
WinterYukky Nov 1, 2022
ce492b7
docs(preset): set warning type to alert
WinterYukky Nov 1, 2022
af349c0
docs(preset): fix warning position
WinterYukky Nov 1, 2022
a5245ed
Merge branch 'main' into pr/WinterYukky/240
pi0 May 17, 2023
761680c
[autofix.ci] apply automated fixes
autofix-ci[bot] May 17, 2023
3b2d3e7
fix: append query string
pi0 May 17, 2023
35139d6
doc: fix test comment
WinterYukky Nov 4, 2023
4fae105
build: bump to NODEJS_18_X
WinterYukky Nov 4, 2023
f93e91c
fix: remove externals property
WinterYukky Nov 4, 2023
cf7e69f
fix: unnormalized body
WinterYukky Nov 4, 2023
7b387af
feat: support dynamic path resolve
WinterYukky Nov 4, 2023
84555a2
build: use jiti
WinterYukky Nov 4, 2023
52cf689
Merge branch 'main' of https://github.com/WinterYukky/nitro into feat…
WinterYukky Nov 4, 2023
9ea6ed7
chore: apply automated fixes
autofix-ci[bot] Nov 4, 2023
144d35a
test: fix to pass the new tests
WinterYukky Nov 4, 2023
08a5111
doc: add AWS Lambda@Edge
WinterYukky Nov 4, 2023
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
1 change: 1 addition & 0 deletions docs/content/2.deploy/0.index.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ When running Nitro in development mode, Nitro will always use a special preset c

When deploying to the production using CI/CD, Nitro tries to automatically detect the provider environment and set the right one without any additional configuration. Currently, providers below can be auto-detected with zero config.

- [aws](/deploy/providers/aws)
- [azure](/deploy/providers/azure)
- [cloudflare pages](/deploy/providers/cloudflare#cloudflare-pages)
- [netlify](/deploy/providers/netlify)
Expand Down
272 changes: 270 additions & 2 deletions docs/content/2.deploy/20.providers/aws.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# AWS Lambda
# AWS

Deploy Nitro apps to AWS Lambda.
Deploy Nitro apps to AWS.

## AWS Lambda

**Preset:** `aws_lambda` ([switch to this preset](/deploy/#changing-the-deployment-preset))

Expand Down Expand Up @@ -35,3 +37,269 @@ export default defineNuxtConfig({
})
```
::

## AWS Lambda@Edge

**Preset:** `aws-lambda-edge-cdk` ([switch to this preset](/deploy/#changing-the-deployment-preset))

::alert
**Zero Config Provider**
:br
Integration with this provider is possible with zero configuration. ([Learn More](/deploy/#zero-config-providers))
::

Nitro provides a built-in preset to generate output format compatible with [AWS Lambda@Edge](https://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html).

The output entrypoint in `.output/server/index.mjs` is compatible with [AWS Lambda@Edge format](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html).

::alert{type=warning}
**Bootstrap**
:br
Nitro uses [AWS CDK](https://github.com/aws/aws-cdk) for deploying Lambda@Edge.
If you are using AWS CDK for the first time on a region-by-region basis, you will need to run the following commands.
```bash
npx cdk bootstrap \
aws://<aws-account-id>/us-east-1 \
aws://<aws-account-id>/<aws-region>
```
Lambda@Edge uses us-east-1, so be sure to include it in your bootstrap, even when deploying to a different region.
::

### Deploy from your local machine

To deploy, run the following commands.

```bash
NITRO_PRESET=aws-lambda-edge npm run build
cd .output/cdk
APP_ID=<app-id> npm run deploy --all
```

### Deploy from CI/CD via GitHub Actions

First, settings IAM Role following [these instructions](https://github.com/aws-actions/configure-aws-credentials#assuming-a-role) and add the IAM Role ARN as a secret to your GitHub repository.

Then create the following file as a workflow:

```yml
# .github/workflows/cdk.yml
name: cdk
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
deploy:
env:
APP_ID: <app-id>
runs-on: ubuntu-latest
name: Deploy to AWS
permissions:
id-token: write
contents: read
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v3

- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: ${{ secrets.AWS_IAM_ROLE_ARN_TO_ASSUME }}
aws-region: <your-aws-region>

- name: Install Dependencies
run: yarn

- name: Build
run: yarn build
env:
NITRO_PRESET: aws-lambda-edge

- name: Install CDK Dependencies
working-directory: .output/cdk
run: yarn

- name: Deploy to AWS Lambda@Edge
working-directory: .output/cdk
run: yarn cdk deploy --require-approval never --all
```

### Customize CDK App

<details>
<summary>Expand here to see</summary>
<div>

To customize it, you must create your own AWS CDK application.
Create your AWS CDK application with the following command.

```bash
mkdir nitro-lambda-edge && cd nitro-lambda-edge
npx cdk init app --language typescript
npm i nitro-aws-cdk-lib
```

The following code is an example of deploying a Nuxt3 project using custom domain to CloudFront and Lambda@Edge with AWS CDK. Using this stack, paths under `_nuxt/` (static assets) will get their data from the S3 origin, and all other paths will be resolved by Lambda@Edge.

```ts
// nitro-lambda-edge/lib/nitro-lambda-edge-stack.ts
import {
CfnOutput,
DockerImage,
RemovalPolicy,
Stack,
StackProps,
} from "aws-cdk-lib";
import * as acm from "aws-cdk-lib/aws-certificatemanager";
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
import * as origins from "aws-cdk-lib/aws-cloudfront-origins";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as route53 from "aws-cdk-lib/aws-route53";
import * as targets from "aws-cdk-lib/aws-route53-targets";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as s3deployment from "aws-cdk-lib/aws-s3-deployment";
import { spawnSync } from "child_process";
import { Construct } from "constructs";
import { NitroAsset } from "nitro-aws-cdk-lib";

export interface NitroLambdaEdgeStackProps extends StackProps {
/**
* Your site domain name.
* @example example.com
*/
readonly domainName: string;
/**
* Your site subdomain.
* @example www
* @default - Use domainName as it is.
*/
readonly subdomain?: string;
}

export class NitroLambdaEdgeStack extends Stack {
constructor(scope: Construct, id: string, props: NitroLambdaEdgeStackProps) {
super(scope, id, props);

// Resolve nitro server and public assets
const nitro = new NitroAsset(this, "NitroAsset", {
path: "<path-to-your-nitro-app-project>",

// Uncomment this option if you want to build nitro app from CDK app.
// bundling: {
// workingDirectory: "<path-to-your-nitro-app-project>",
// image: DockerImage.fromRegistry('node:lts'),
// local: {
// tryBundle(outputDir, options) {
// const spawnOptions = {
// stdio: "inherit" as const,
// cwd: options.workingDirectory
// }
// spawnSync('npm', ['ci'], spawnOptions)
// spawnSync('npm', ['run', 'build'], spawnOptions)
// spawnSync('cp', ['-r', '.output', outputDir], spawnOptions)
// return true
// },
// }
// }
});

// Lambda@Edge with working Nitro server code
const edgeFunction = new cloudfront.experimental.EdgeFunction(
this,
"EdgeFunction",
{
runtime: lambda.Runtime.NODEJS_18_X,
handler: "index.handler",
code: nitro.serverHandler,
}
);

// Static assets bucket
const bucket = new s3.Bucket(this, "Bucket", {
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
encryption: s3.BucketEncryption.S3_MANAGED,
removalPolicy: RemovalPolicy.DESTROY,
autoDeleteObjects: true,
});

const hostedZone = route53.HostedZone.fromLookup(this, "HostedZone", {
domainName: props.domainName,
});
const siteDomain = [props.subdomain, props.domainName]
.filter((v) => !!v)
.join(".");
new CfnOutput(this, "URL", {
value: `https://${siteDomain}`,
});

// TLS certificate
const certificate = new acm.DnsValidatedCertificate(this, "Certificate", {
domainName: siteDomain,
hostedZone,
region: "us-east-1", // always must be set us-east-1
});

// CloudFront distribution
const s3Origin = new origins.S3Origin(bucket);
const distribution = new cloudfront.Distribution(this, "Distribution", {
certificate,
domainNames: [siteDomain],
defaultBehavior: {
origin: s3Origin,
edgeLambdas: [
{
functionVersion: edgeFunction.currentVersion,
eventType: cloudfront.LambdaEdgeEventType.ORIGIN_REQUEST,
},
],
},
additionalBehaviors: nitro.staticAsset.resolveCloudFrontBehaviors({
resolve: () => ({
origin: s3Origin,
}),
}),
priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL,
httpVersion: cloudfront.HttpVersion.HTTP3,
});
new route53.ARecord(this, "AliasRecord", {
recordName: siteDomain,
target: route53.RecordTarget.fromAlias(
new targets.CloudFrontTarget(distribution)
),
zone: hostedZone,
});

// Deploy static assets to S3 bucket
new s3deployment.BucketDeployment(this, "Deployment", {
sources: [nitro.staticAsset],
destinationBucket: bucket,
distribution,
});
}
}
```

```ts
// nitro-lambda-edge/bin/nitro-lambda-edge.ts
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { NitroLambdaEdgeStack } from "../lib/nitro-lambda-edge-stack";

const app = new cdk.App();
new NitroLambdaEdgeStack(app, "NitroLambdaEdgeStack", {
domainName: "your-site.com",
subdomain: "www",
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
},
});
```

</div>
</details>
10 changes: 10 additions & 0 deletions src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,16 @@ async function _build(nitro: Nitro, rollupConfig: RollupConfig) {
preview: nitro.options.commands.preview,
deploy: nitro.options.commands.deploy,
},
output: {
serverDir: relative(
nitro.options.output.dir,
nitro.options.output.serverDir
),
publicDir: relative(
nitro.options.output.dir,
nitro.options.output.publicDir
),
},
};
await writeFile(nitroConfigPath, JSON.stringify(buildInfo, null, 2));

Expand Down
Loading