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

Commit f0beeec

Browse files
author
Bender
committed
ref(cdk, images): cdk simplified to single APIGW, image handler bundling fixed and simplified to not include layers
1 parent 5477aa9 commit f0beeec

12 files changed

+3793
-214
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ node_modules/**
22
.env*
33
!.env.example
44
dist/**
5+
.webpack
56

67
# Temporary build folder.
78
nodejs/**

Diff for: .nvmrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
16
1+
16.15

Diff for: README.md

+7-9
Original file line numberDiff line numberDiff line change
@@ -31,24 +31,22 @@ This library uses Cloudfront, S3, ApiGateway and Lambdas to deploy easily in sec
3131
- Run `npx --package @sladg/nextjs-lambda next-utils deploy` (will deploy to AWS).
3232
- Profit 🎉
3333

34-
35-
36-
At this point, advanced features were not tested with this setup. This includes:
34+
---
3735

3836
- [x] Render frontfacing pages in Lambda
3937
- [x] Render API routes in Lambda
4038
- [x] Image optimization
39+
- [x] NextJS headers (next.config.js)
4140
- [x] [GetStaticPaths](https://nextjs.org/docs/basic-features/data-fetching/get-static-paths)
42-
- [ ] next-intl (i18n)
43-
- [ ] [GetServerSideProps](https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props)
44-
- [ ] [GetStaticProps](https://nextjs.org/docs/basic-features/data-fetching/get-static-props)
45-
- [ ] [Middleware](https://nextjs.org/docs/advanced-features/middleware)
41+
- [x] next-intl (i18n)
42+
- [x] [Middleware](https://nextjs.org/docs/advanced-features/middleware)
43+
- [x] [GetServerSideProps](https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props)
44+
- [x] [GetStaticProps](https://nextjs.org/docs/basic-features/data-fetching/get-static-props)
45+
- [x] NextJS rewrites (next.config.js)
4646
- [ ] [ISR and fallbacks](https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration)
4747
- [ ] [Streaming](https://nextjs.org/docs/advanced-features/react-18/streaming)
4848
- [ ] Custom babel configuration
4949

50-
I am looking for advanced projects implementing those features, so we can test them out! Reach out to me!
51-
5250

5351
## Usage
5452

Diff for: cdk/app.ts

+17-29
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ class NextStandaloneStack extends Stack {
2222
super(scope, id, props)
2323

2424
const config = {
25+
apigwServerPath: '/_server',
26+
apigwImagePath: '/_image',
2527
assetsZipPath: path.resolve(commandCwd, './next.out/assetsLayer.zip'),
2628
codeZipPath: path.resolve(commandCwd, './next.out/code.zip'),
2729
dependenciesZipPath: path.resolve(commandCwd, './next.out/dependenciesLayer.zip'),
28-
sharpLayerZipPath: path.resolve(cdkFolder, '../dist/sharp-layer.zip'),
29-
nextLayerZipPath: path.resolve(cdkFolder, '../dist/next-layer.zip'),
3030
imageHandlerZipPath: path.resolve(cdkFolder, '../dist/image-handler.zip'),
3131
customServerHandler: 'handler.handler',
32-
customImageHandler: 'index.handler',
32+
customImageHandler: 'handler.handler',
3333
cfnViewerCertificate: undefined,
3434
...props,
3535
}
@@ -42,28 +42,19 @@ class NextStandaloneStack extends Stack {
4242
description: `${depsPrefix}-deps`,
4343
})
4444

45-
// @TODO: Load Sharp version from source package.json so we respect it.
46-
const sharpLayer = new LayerVersion(this, 'SharpLayer', {
47-
code: Code.fromAsset(config.sharpLayerZipPath, { assetHashType: AssetHashType.CUSTOM, assetHash: depsPrefix }),
48-
description: `${depsPrefix}-sharp`,
49-
})
50-
51-
// @TODO: Load Next version from source package.json so we respect it.
52-
const nextLayer = new LayerVersion(this, 'NextLayer', {
53-
code: Code.fromAsset(config.nextLayerZipPath, { assetHashType: AssetHashType.CUSTOM, assetHash: depsPrefix }),
54-
description: `${depsPrefix}-next`,
55-
})
56-
5745
const serverLambda = new Function(this, 'DefaultNextJs', {
5846
code: Code.fromAsset(config.codeZipPath, {
5947
followSymlinks: SymlinkFollowMode.NEVER,
6048
}),
6149
runtime: Runtime.NODEJS_16_X,
6250
handler: config.customServerHandler,
63-
layers: [depsLayer, sharpLayer, nextLayer],
51+
layers: [depsLayer],
6452
// No need for big memory as image handling is done elsewhere.
6553
memorySize: 512,
6654
timeout: Duration.seconds(15),
55+
environment: {
56+
NEXTJS_LAMBDA_BASE_PATH: config.apigwServerPath,
57+
},
6758
})
6859

6960
const assetsBucket = new Bucket(this, 'NextAssetsBucket', {
@@ -77,7 +68,6 @@ class NextStandaloneStack extends Stack {
7768
code: Code.fromAsset(config.imageHandlerZipPath),
7869
runtime: Runtime.NODEJS_16_X,
7970
handler: config.customImageHandler,
80-
layers: [sharpLayer, nextLayer],
8171
memorySize: 1024,
8272
timeout: Duration.seconds(10),
8373
environment: {
@@ -87,15 +77,12 @@ class NextStandaloneStack extends Stack {
8777

8878
assetsBucket.grantRead(imageLambda)
8979

90-
const serverApigatewayProxy = new HttpApi(this, 'ServerProxy', {
91-
createDefaultStage: true,
92-
defaultIntegration: new HttpLambdaIntegration('LambdaApigwIntegration', serverLambda),
93-
})
80+
const apigatewayProxy = new HttpApi(this, 'ServerProxy')
9481

95-
const imageApigatewayProxy = new HttpApi(this, 'ImagesProxy', {
96-
createDefaultStage: true,
97-
defaultIntegration: new HttpLambdaIntegration('ImagesApigwIntegration', imageLambda),
98-
})
82+
// We could do parameter mapping here and remove prefix from path.
83+
// However passing env var (basePath) is easier to use, understand and integrate to other solutions.
84+
apigatewayProxy.addRoutes({ path: `${config.apigwServerPath}/{proxy+}`, integration: new HttpLambdaIntegration('LambdaApigwIntegration', serverLambda) })
85+
apigatewayProxy.addRoutes({ path: `${config.apigwImagePath}/{proxy+}`, integration: new HttpLambdaIntegration('ImagesApigwIntegration', imageLambda) })
9986

10087
const s3AssetsIdentity = new OriginAccessIdentity(this, 'OAICfnDistroS3', {
10188
comment: 'Allows CloudFront to access S3 bucket with assets',
@@ -123,7 +110,8 @@ class NextStandaloneStack extends Stack {
123110
},
124111
],
125112
customOriginSource: {
126-
domainName: `${serverApigatewayProxy.apiId}.execute-api.${this.region}.amazonaws.com`,
113+
originPath: config.apigwServerPath,
114+
domainName: `${apigatewayProxy.apiId}.execute-api.${this.region}.amazonaws.com`,
127115
},
128116
},
129117
{
@@ -137,7 +125,8 @@ class NextStandaloneStack extends Stack {
137125
},
138126
],
139127
customOriginSource: {
140-
domainName: `${imageApigatewayProxy.apiId}.execute-api.${this.region}.amazonaws.com`,
128+
originPath: config.apigwImagePath,
129+
domainName: `${apigatewayProxy.apiId}.execute-api.${this.region}.amazonaws.com`,
141130
},
142131
},
143132
{
@@ -171,8 +160,7 @@ class NextStandaloneStack extends Stack {
171160

172161
new CfnOutput(this, 'cfnDistroUrl', { value: cfnDistro.distributionDomainName })
173162
new CfnOutput(this, 'cfnDistroId', { value: cfnDistro.distributionId })
174-
new CfnOutput(this, 'defaultApiGwUrl', { value: serverApigatewayProxy.apiEndpoint })
175-
new CfnOutput(this, 'imagesApiGwUrl', { value: imageApigatewayProxy.apiEndpoint })
163+
new CfnOutput(this, 'apiGwUrl', { value: apigatewayProxy.apiEndpoint })
176164
new CfnOutput(this, 'assetsBucketUrl', { value: assetsBucket.bucketDomainName })
177165
}
178166
}

Diff for: lib/cli/pack.ts

+2
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export const packHandler = async ({ handlerPath, outputFolder, publicFolder, sta
8585
ignore: ['**/node_modules/**', '*.zip'],
8686
},
8787
{
88+
// @TODO: use {dot:true} configuration in archiver and remove this.
8889
// Ensure hidden files are included.
8990
isGlob: true,
9091
cwd: standaloneFolder,
@@ -96,6 +97,7 @@ export const packHandler = async ({ handlerPath, outputFolder, publicFolder, sta
9697
name: 'handler.js',
9798
},
9899
{
100+
// @TODO: Verify this as it seems like symlink is not needed when layer is in /opt/nodejs/node_modules
99101
isFile: true,
100102
path: symlinkPath,
101103
name: 'node_modules',

Diff for: lib/standalone/image-handler.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
// ! Make sure this comes before the fist import
2-
process.env.NEXT_SHARP_PATH = require.resolve('sharp')
3-
process.env.NODE_ENV = 'production'
4-
51
import type { APIGatewayProxyEventV2, APIGatewayProxyStructuredResultV2 } from 'aws-lambda'
62
import { defaultConfig, NextConfigComplete } from 'next/dist/server/config-shared'
73
import { imageOptimizer as nextImageOptimizer, ImageOptimizerCache } from 'next/dist/server/image-optimizer'
@@ -24,6 +20,8 @@ const nextConfig = {
2420
},
2521
}
2622

23+
// We don't need serverless-http neither basePath configuration as endpoint works as single route API.
24+
// Images are handled via header and query param information.
2725
const optimizer = async (event: APIGatewayProxyEventV2): Promise<APIGatewayProxyStructuredResultV2> => {
2826
try {
2927
if (!sourceBucket) {

Diff for: lib/standalone/server-handler.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ const server = slsHttp(
3737
{
3838
// We have separate function for handling images. Assets are handled by S3.
3939
binary: false,
40-
basePath: process.env.NEXTJS_LAMBDA_BASE_PATH
40+
provider: 'aws',
41+
basePath: process.env.NEXTJS_LAMBDA_BASE_PATH,
4142
},
4243
)
4344

0 commit comments

Comments
 (0)