Skip to content

Commit 61973db

Browse files
committed
feat: add ssr with lambda@edge
1 parent 7ff2407 commit 61973db

33 files changed

+10873
-14088
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
# dependencies
1111
/node_modules
1212

13+
.serverless
14+
1315
# profiling files
1416
chrome-profiler-events*.json
1517
speed-measure-plugin*.json

angular.json

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"build": {
1818
"builder": "@angular-devkit/build-angular:browser",
1919
"options": {
20-
"outputPath": "dist/angular-lambda-ssr",
20+
"outputPath": "dist/angular-lambda-ssr/browser",
2121
"index": "src/index.html",
2222
"main": "src/main.ts",
2323
"polyfills": "src/polyfills.ts",
@@ -102,7 +102,8 @@
102102
"tsConfig": [
103103
"tsconfig.app.json",
104104
"tsconfig.spec.json",
105-
"e2e/tsconfig.json"
105+
"e2e/tsconfig.json",
106+
"tsconfig.server.json"
106107
],
107108
"exclude": [
108109
"**/node_modules/**"
@@ -120,6 +121,74 @@
120121
"devServerTarget": "angular-lambda-ssr:serve:production"
121122
}
122123
}
124+
},
125+
"serverless": {
126+
"builder": "@angular-devkit/build-angular:server",
127+
"options": {
128+
"outputPath": "dist/angular-lambda-ssr/serverless",
129+
"main": "serverless.ts",
130+
"tsConfig": "tsconfig.serverless.json"
131+
},
132+
"configurations": {
133+
"production": {
134+
"outputHashing": "media",
135+
"fileReplacements": [
136+
{
137+
"replace": "src/environments/environment.ts",
138+
"with": "src/environments/environment.prod.ts"
139+
}
140+
],
141+
"sourceMap": false,
142+
"optimization": true
143+
}
144+
}
145+
},
146+
"server": {
147+
"builder": "@angular-devkit/build-angular:server",
148+
"options": {
149+
"outputPath": "dist/angular-lambda-ssr/server",
150+
"main": "server.ts",
151+
"tsConfig": "tsconfig.server.json"
152+
},
153+
"configurations": {
154+
"production": {
155+
"outputHashing": "media",
156+
"fileReplacements": [
157+
{
158+
"replace": "src/environments/environment.ts",
159+
"with": "src/environments/environment.prod.ts"
160+
}
161+
],
162+
"sourceMap": false,
163+
"optimization": true
164+
}
165+
}
166+
},
167+
"serve-ssr": {
168+
"builder": "@nguniversal/builders:ssr-dev-server",
169+
"options": {
170+
"browserTarget": "angular-lambda-ssr:build",
171+
"serverTarget": "angular-lambda-ssr:server"
172+
},
173+
"configurations": {
174+
"production": {
175+
"browserTarget": "angular-lambda-ssr:build:production",
176+
"serverTarget": "angular-lambda-ssr:server:production"
177+
}
178+
}
179+
},
180+
"prerender": {
181+
"builder": "@nguniversal/builders:prerender",
182+
"options": {
183+
"browserTarget": "angular-lambda-ssr:build:production",
184+
"serverTarget": "angular-lambda-ssr:server:production",
185+
"routes": [
186+
"/"
187+
]
188+
},
189+
"configurations": {
190+
"production": {}
191+
}
123192
}
124193
}
125194
}

event.json

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
{
2+
"Records": [
3+
{
4+
"cf": {
5+
"config": {
6+
"distributionDomainName": "d123.cloudfront.net",
7+
"distributionId": "EDFDVBD6EXAMPLE",
8+
"eventType": "viewer-request",
9+
"requestId": "MRVMF7KydIvxMWfJIglgwHQwZsbG2IhRJ07sn9AkKUFSHS9EXAMPLE=="
10+
},
11+
"request": {
12+
"body": {
13+
"action": "read-only",
14+
"data": "eyJ1c2VybmFtZSI6IkxhbWJkYUBFZGdlIiwiY29tbWVudCI6IlRoaXMgaXMgcmVxdWVzdCBib2R5In0=",
15+
"encoding": "base64",
16+
"inputTruncated": false
17+
},
18+
"clientIp": "2001:0db8:85a3:0:0:8a2e:0370:7334",
19+
"querystring": "size=large",
20+
"uri": "/",
21+
"method": "GET",
22+
"headers": {
23+
"host": [
24+
{
25+
"key": "Host",
26+
"value": "d111111abcdef8.cloudfront.net"
27+
}
28+
],
29+
"user-agent": [
30+
{
31+
"key": "User-Agent",
32+
"value": "curl/7.51.0"
33+
}
34+
]
35+
},
36+
"origin": {
37+
"custom": {
38+
"customHeaders": {
39+
"my-origin-custom-header": [
40+
{
41+
"key": "My-Origin-Custom-Header",
42+
"value": "Test"
43+
}
44+
]
45+
},
46+
"domainName": "example.com",
47+
"keepaliveTimeout": 5,
48+
"path": "/custom_path",
49+
"port": 443,
50+
"protocol": "https",
51+
"readTimeout": 5,
52+
"sslProtocols": [
53+
"TLSv1",
54+
"TLSv1.1"
55+
]
56+
}
57+
}
58+
}
59+
}
60+
}
61+
]
62+
}

instructions.txt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
- prerequisites:
3+
- aws account
4+
- aws cli configure
5+
- serverless framework installed
6+
7+
- install ng add @nguniversal/express-engine
8+
- add serverless.ts (to keep things clean)
9+
- add serverless in angular.json (to keep things clean)
10+
- add ServerTransferStateModule in app.server.module.ts
11+
- add TransferHttpCacheModule in app.module.ts
12+
- add lambda.js file
13+
- run yarn add html-minifier
14+
- add in package.json:
15+
"build:sls": "ng build --prod && ng run angular-lambda-ssr:serverless:production"
16+
- deploy serverless distribution:
17+
serverless deploy --config serverless-distribution.yml
18+
- replace the url in search.servicee with the cloudfront endpoint
19+
- build:
20+
yarn build:sls
21+
- upload dist to s3:
22+
aws s3 sync . s3://eelayoubi-ssr-test --profile A4L-MASTER
23+
- deploy lambda stack
24+
serverless deploy
25+
- attach lambda to cloudfront origin request
26+
27+
# cleanup
28+
- empty the bucket that contains the dist (if not empty, cannot be deleted)
29+
- delete distribution stack:
30+
serverless remove --config serverless-distribution.yml
31+
- wait for a while then delete lambda stack
32+
serverless remove
33+

lambda.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
2+
const path = require('path')
3+
const serverless = require('serverless-http');
4+
const minify = require('html-minifier').minify;
5+
6+
const { app } = require("./dist/angular-lambda-ssr/serverless/main");
7+
8+
const handle = serverless(app, {
9+
provider: 'aws',
10+
type: 'lambda-edge-origin-request'
11+
});
12+
13+
const handler = async (event, context, callback) => {
14+
const request = event.Records[0].cf.request;
15+
16+
17+
if ((!path.extname(request.uri)) || (request.uri === '/index.html')) {
18+
const response = await handle(event, context);
19+
let minified = minify(response.body, {
20+
caseSensitive: true,
21+
collapseWhitespace: true,
22+
preserveLineBreaks: true,
23+
removeAttributeQuotes: true,
24+
removeComments: true
25+
});
26+
console.log('response: ', response)
27+
callback(null, {
28+
status: response.status,
29+
statusDescription: response.statusDescription,
30+
headers: {
31+
...response.headers,
32+
},
33+
body: minified,
34+
bodyEncoding: response.bodyEncoding
35+
});
36+
} else {
37+
console.log(`${request.uri} directly served from S3`)
38+
return request;
39+
}
40+
41+
}
42+
43+
exports.handler = handler;

0 commit comments

Comments
 (0)