├── .eslintignore ├── .eslintrc ├── .gitignore ├── BENCHMARKS.md ├── CHANGELOG.md ├── LICENSE ├── README.md ├── benchmark ├── configs │ ├── base.js │ ├── with-compression.js │ ├── with-etag.js │ └── with-static.js ├── results.json └── run.js ├── bin ├── nuxt-lambda.js └── test-lambda.js ├── example ├── assets │ └── nuxt-square.svg ├── layouts │ └── default.vue ├── nuxt.config.js ├── pages │ ├── about.vue │ ├── handle-internal-server-error.vue │ ├── index.vue │ ├── redirect.vue │ └── with-error.vue ├── plugins │ └── vue-uuid.js └── static │ └── nuxt-icon.png ├── lib ├── build.js ├── commands │ ├── build-lambda.js │ ├── build-nuxt.js │ ├── config.js │ └── zip.js ├── stub │ ├── class.js │ ├── consola.js │ ├── fs.js │ └── index.js ├── test.js └── utils │ ├── build-options.js │ ├── nuxt.js │ └── options.js ├── netlify.toml ├── package.json ├── scripts └── build.sh ├── src ├── entries │ ├── event.js │ └── http.js ├── handlers │ ├── connect.js │ ├── full.js │ └── minimal.js ├── middleware │ ├── compression.js │ ├── error.js │ ├── nuxt.js │ └── serve-static.js ├── renderer.js └── utils │ ├── binaryMimeTypes.js │ ├── compression.js │ ├── config.js │ ├── debug.js │ ├── index.js │ ├── request.js │ ├── response.js │ └── url.js └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | .nuxt 2 | dist 3 | example/dist* 4 | example/lambda 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parserOptions": { 4 | "parser": "@babel/eslint-parser", 5 | "sourceType": "module", 6 | "requireConfigFile": false 7 | }, 8 | "extends": [ 9 | "@nuxtjs" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | jspm_packages 4 | 5 | # Only keep yarn.lock in the root 6 | package-lock.json 7 | */**/yarn.lock 8 | 9 | # Logs 10 | *.log 11 | 12 | # Packages 13 | packages/*/LICENSE 14 | 15 | # Distributions 16 | distributions/*/LICENSE 17 | 18 | # Other 19 | .nuxt* 20 | .lambda* 21 | !.nuxtignore 22 | .cache 23 | 24 | # Dist folders 25 | dist* 26 | 27 | # Junit reports 28 | reports 29 | 30 | # Coverage reports 31 | coverage 32 | *.lcov 33 | .nyc_output 34 | 35 | # VSCode 36 | .vscode 37 | 38 | # Intellij idea 39 | *.iml 40 | .idea 41 | 42 | # OSX 43 | .DS_Store 44 | .AppleDouble 45 | .LSOverride 46 | 47 | # Files that might appear in the root of a volume 48 | .DocumentRevisions-V100 49 | .fseventsd 50 | .Spotlight-V100 51 | .TemporaryItems 52 | .Trashes 53 | .VolumeIcon.icns 54 | .com.apple.timemachine.donotpresent 55 | 56 | # Directories potentially created on remote AFP share 57 | .AppleDB 58 | .AppleDesktop 59 | Network Trash Folder 60 | Temporary Items 61 | .apdisk 62 | 63 | # Local Netlify folder 64 | .netlify 65 | -------------------------------------------------------------------------------- /BENCHMARKS.md: -------------------------------------------------------------------------------- 1 | 2 | # Benchmarks 3 | 4 | > :warning: The total times are measured without downloading & unzipping what normally occures on the AWS platform. Therefore these benchmarks only list _load/parse times_ and not _coldboot times_ 5 | 6 | - Times are in `ms` 7 | - Times/memory usage are the averages of 3 runs 8 | 9 | Check the benchmark folder for details how these benchmarks are created 10 | 11 | ## Route `/` 12 | ### Config _base_ 13 | |handler|load time|exec time|total time|memory usage|chksum| 14 | |---|---|---|---|---|---| 15 | |_full_|124.5|82.8|207.3|113 MB|jamPhISHsbmGThJHGys0vw==| 16 | |_connect_|34.5|43.5|78.1|69.3 MB|ZsSu8hwr2WO0vlrDekvhBQ==| 17 | |_minimal_|27.9|42.5|70.4|68.9 MB|Vv9a0Em8JRqCFhoUmKtZZg==| 18 |
Response for full 19 | 20 | ```js 21 | { 22 | "statusCode": 200, 23 | "headers": { 24 | "content-type": "text/html; charset=utf-8", 25 | "accept-ranges": "none", 26 | "content-length": "1385" 27 | }, 28 | "isBase64Encoded": true, 29 | "body": "PCFkb2N0eXBlIGh0bWw+CjxodG1sIGRhdGEtbi1oZWFkLXNzcj4KICA8aGVhZCA+CiAgICA8bGluayByZWw9InByZWxvYWQiIGhyZWY9Ii9fbnV4dC80NTZlZTE3YWU1MTE5NzdkMDRlZC5qcyIgYXM9InNjcmlwdCI+PGxpbmsgcmVsPSJwcmVsb2FkIiBocmVmPSIvX251eHQvZTgzOGQ1NmJlZDU1ZDA2NWJiMzAuanMiIGFzPSJzY3JpcHQiPjxsaW5rIHJlbD0icHJlbG9hZCIgaHJlZj0iL19udXh0LzY3ZDE3NGNlMmExODdhY2UwMjI2LmpzIiBhcz0ic2NyaXB0Ij48bGluayByZWw9InByZWxvYWQiIGhyZWY9Ii9fbnV4dC9kNzFjMWY3YmU0ODM1ZjdkZDc3Yi5qcyIgYXM9InNjcmlwdCI+PHN0eWxlIGRhdGEtdnVlLXNzci1pZD0iMTI0ZTI4OGM6MCI+Lm51eHQtcHJvZ3Jlc3N7cG9zaXRpb246Zml4ZWQ7dG9wOjA7bGVmdDowO3JpZ2h0OjA7aGVpZ2h0OjJweDt3aWR0aDowO29wYWNpdHk6MTt0cmFuc2l0aW9uOndpZHRoIC4xcyxvcGFjaXR5IC40cztiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7ei1pbmRleDo5OTk5OTl9Lm51eHQtcHJvZ3Jlc3MubnV4dC1wcm9ncmVzcy1ub3RyYW5zaXRpb257dHJhbnNpdGlvbjpub25lfS5udXh0LXByb2dyZXNzLWZhaWxlZHtiYWNrZ3JvdW5kLWNvbG9yOnJlZH08L3N0eWxlPgogIDwvaGVhZD4KICA8Ym9keSA+CiAgICA8ZGl2IGRhdGEtc2VydmVyLXJlbmRlcmVkPSJ0cnVlIiBpZD0iX19udXh0Ij48IS0tLS0+PGRpdiBpZD0iX19sYXlvdXQiPjxkaXY+PGRpdj48aDE+SGkgZnJvbSBzZXJ2ZXIhPC9oMT4gPGZpZ3VyZT48aW1nIHNyYz0iL251eHQtaWNvbi5wbmciIHN0eWxlPSJoZWlnaHQ6IDUwcHgiPiA8ZmlnY2FwdGlvbj48YSBocmVmPSIvYWJvdXQiPkFib3V0PC9hPjwvZmlnY2FwdGlvbj48L2ZpZ3VyZT4gPHA+PHNtYWxsPnV1aWQ6IDJkOWIwOWQwLTU5ODEtMTFlYS1iYTk0LTk1ZDk4OTU1M2Y2Yzwvc21hbGw+PC9wPjwvZGl2PiA8Zm9vdGVyPjxzbWFsbD5TZXJ2ZWQgYnkgYSBudXh0LWxhbWJkYTwvc21hbGw+PC9mb290ZXI+PC9kaXY+PC9kaXY+PC9kaXY+PHNjcmlwdD53aW5kb3cuX19OVVhUX189e2xheW91dDoiZGVmYXVsdCIsZGF0YTpbe25hbWU6InNlcnZlciJ9XSxlcnJvcjpudWxsLHNlcnZlclJlbmRlcmVkOnRydWV9Ozwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvNDU2ZWUxN2FlNTExOTc3ZDA0ZWQuanMiIGRlZmVyPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvZDcxYzFmN2JlNDgzNWY3ZGQ3N2IuanMiIGRlZmVyPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvZTgzOGQ1NmJlZDU1ZDA2NWJiMzAuanMiIGRlZmVyPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvNjdkMTc0Y2UyYTE4N2FjZTAyMjYuanMiIGRlZmVyPjwvc2NyaXB0PgogIDwvYm9keT4KPC9odG1sPgo=" 30 | } 31 | ``` 32 |
33 | 34 |
Response for connect 35 | 36 | ```js 37 | { 38 | "statusCode": 200, 39 | "headers": { 40 | "content-type": "text/html; charset=utf-8", 41 | "accept-ranges": "none", 42 | "content-length": "1385" 43 | }, 44 | "isBase64Encoded": true, 45 | "body": "PCFkb2N0eXBlIGh0bWw+CjxodG1sIGRhdGEtbi1oZWFkLXNzcj4KICA8aGVhZCA+CiAgICA8bGluayByZWw9InByZWxvYWQiIGhyZWY9Ii9fbnV4dC80NTZlZTE3YWU1MTE5NzdkMDRlZC5qcyIgYXM9InNjcmlwdCI+PGxpbmsgcmVsPSJwcmVsb2FkIiBocmVmPSIvX251eHQvZTgzOGQ1NmJlZDU1ZDA2NWJiMzAuanMiIGFzPSJzY3JpcHQiPjxsaW5rIHJlbD0icHJlbG9hZCIgaHJlZj0iL19udXh0LzY3ZDE3NGNlMmExODdhY2UwMjI2LmpzIiBhcz0ic2NyaXB0Ij48bGluayByZWw9InByZWxvYWQiIGhyZWY9Ii9fbnV4dC9kNzFjMWY3YmU0ODM1ZjdkZDc3Yi5qcyIgYXM9InNjcmlwdCI+PHN0eWxlIGRhdGEtdnVlLXNzci1pZD0iMTI0ZTI4OGM6MCI+Lm51eHQtcHJvZ3Jlc3N7cG9zaXRpb246Zml4ZWQ7dG9wOjA7bGVmdDowO3JpZ2h0OjA7aGVpZ2h0OjJweDt3aWR0aDowO29wYWNpdHk6MTt0cmFuc2l0aW9uOndpZHRoIC4xcyxvcGFjaXR5IC40cztiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7ei1pbmRleDo5OTk5OTl9Lm51eHQtcHJvZ3Jlc3MubnV4dC1wcm9ncmVzcy1ub3RyYW5zaXRpb257dHJhbnNpdGlvbjpub25lfS5udXh0LXByb2dyZXNzLWZhaWxlZHtiYWNrZ3JvdW5kLWNvbG9yOnJlZH08L3N0eWxlPgogIDwvaGVhZD4KICA8Ym9keSA+CiAgICA8ZGl2IGRhdGEtc2VydmVyLXJlbmRlcmVkPSJ0cnVlIiBpZD0iX19udXh0Ij48IS0tLS0+PGRpdiBpZD0iX19sYXlvdXQiPjxkaXY+PGRpdj48aDE+SGkgZnJvbSBzZXJ2ZXIhPC9oMT4gPGZpZ3VyZT48aW1nIHNyYz0iL251eHQtaWNvbi5wbmciIHN0eWxlPSJoZWlnaHQ6IDUwcHgiPiA8ZmlnY2FwdGlvbj48YSBocmVmPSIvYWJvdXQiPkFib3V0PC9hPjwvZmlnY2FwdGlvbj48L2ZpZ3VyZT4gPHA+PHNtYWxsPnV1aWQ6IDM0ZjljOWEwLTU5ODEtMTFlYS1hY2U5LTRkYWRjMmFlNGNiYjwvc21hbGw+PC9wPjwvZGl2PiA8Zm9vdGVyPjxzbWFsbD5TZXJ2ZWQgYnkgYSBudXh0LWxhbWJkYTwvc21hbGw+PC9mb290ZXI+PC9kaXY+PC9kaXY+PC9kaXY+PHNjcmlwdD53aW5kb3cuX19OVVhUX189e2xheW91dDoiZGVmYXVsdCIsZGF0YTpbe25hbWU6InNlcnZlciJ9XSxlcnJvcjpudWxsLHNlcnZlclJlbmRlcmVkOnRydWV9Ozwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvNDU2ZWUxN2FlNTExOTc3ZDA0ZWQuanMiIGRlZmVyPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvZDcxYzFmN2JlNDgzNWY3ZGQ3N2IuanMiIGRlZmVyPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvZTgzOGQ1NmJlZDU1ZDA2NWJiMzAuanMiIGRlZmVyPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvNjdkMTc0Y2UyYTE4N2FjZTAyMjYuanMiIGRlZmVyPjwvc2NyaXB0PgogIDwvYm9keT4KPC9odG1sPgo=" 46 | } 47 | ``` 48 |
49 | 50 |
Response for minimal 51 | 52 | ```js 53 | { 54 | "statusCode": 200, 55 | "headers": { 56 | "content-type": "text/html; charset=utf-8", 57 | "accept-ranges": "none", 58 | "content-length": 1385 59 | }, 60 | "isBase64Encoded": false, 61 | "body": "\n\n \n \n \n \n

Hi from server!

About

uuid: 38d04d10-5981-11ea-ac51-915f7fc1d524

Served by a nuxt-lambda
\n \n\n" 62 | } 63 | ``` 64 |
65 | 66 | ### Config _with-compression_ 67 | |handler|load time|exec time|total time|memory usage|chksum| 68 | |---|---|---|---|---|---| 69 | |_full_|124.3|96.1|220.4|110 MB|i/yEKdZWv6afjnPTO22VtQ==| 70 | |_connect_|41.8|45.3|87.1|72.9 MB|9zTstWdYdM5zhieECdaNWg==| 71 | |_minimal_|30.2|43.7|73.9|69.6 MB|7PFqml3ZDRDeBJTnsPZRUg==| 72 |
Response for full 73 | 74 | ```js 75 | { 76 | "statusCode": 200, 77 | "headers": { 78 | "content-type": "text/html; charset=utf-8", 79 | "accept-ranges": "none", 80 | "vary": "Accept-Encoding", 81 | "content-encoding": "gzip" 82 | }, 83 | "isBase64Encoded": true, 84 | "body": "H4sIAAAAAAAAA5VUy46bMBTdz1d43G0c7AQCIQSpu6666EMaqaoigy+JOwQjY2aSRvn32oZoXprODAu/z32ce7jZtVClObaAdmZf51eZm5DghpOG7IAL0nU6v0IocxvkVnZdy+YWaajXuLWj4gKjnYZqjYNN0x9MEEYLABZziBhbxrGgIYjpnw4j3q1xV2rZGpy/aQWSeSKiRQEiigRdREUxpx+3sogFi8MSZpwlMS+BzmaLj1sRMStZFRcQJvOoioWI4+Kllc4caxjYu+vBUUekWGM2C2GWJGVKcT515kir1VZD151a1UkjVZNW8gBiZVSb0lUNlbGTltudm3fgF7P2sLqXwuzskWp5Kc0xZSujeTOa8JdoyrrJeI2mYbcqeHm71apvBClVrXT6iVK6+ktkI+CQLv13fhrV0x1p1IOT0yN/jWrgGZJUXNYgTi+cahDnLPD8eDUFTk5+VShxvOhKyLuBvQ70HWiiwQZpoWtsdA8YOTI3viCW7Gtiv9xjhvOaH1XvbuzROOxY/kWiSqs9GkxeW88sR1klt72GPJP7Lep0aevs85ClaqZts8XIh7rGI/coou0BD7iSty79POMXifDC+/3spizgeRY8fhaMvlDWWoXseV3nfS9FikJWguBzSqJlwghjwElS0YRwtoxCKmhMk7nlzCOywIIDl5ONQSkD+mLru8tLoOKIOPI51HxfCP4AvDz36CfjoNv83kpB3U83m68/b35sNuvTQGSKBVS8rw2euJqkv04N30OKByLx+fcEtLalbfq6ngyH38Z6pa5c55WNYfAwehqZfqNHWK8+3P9gX/0b34F9tau8A/tqL3mG9Qp3wrYNNRga6z9KzMWIaQUAAA==" 85 | } 86 | ``` 87 |
88 | 89 |
Response for connect 90 | 91 | ```js 92 | { 93 | "statusCode": 200, 94 | "headers": { 95 | "content-type": "text/html; charset=utf-8", 96 | "accept-ranges": "none", 97 | "vary": "Accept-Encoding", 98 | "content-encoding": "gzip" 99 | }, 100 | "isBase64Encoded": true, 101 | "body": "H4sIAAAAAAAAA5VUy46bMBTdz1d43G0ccAIhAYLUXVdd9CGNVFWRwZfEHQcjY2aSRvn32oZoXprOjBd+cu7j3MPNr7mqzLEFtDN7WVzlbkGcGUYasgPGSdfp4gqh3B2Q29m9FM0t0iDXuLWzYhyjnYZ6jYNN0x9MEMULAJowiCldJQkPI+DTPx1GrFvjrtKiNbh40wos50seL0rgcczDRVyW8/DjVhYJp0lUwYzRZcIqCGezxcet8IRWtE5KiJbzuE44T5LypZXOHCUM7N314Kgjgq8xnUUwWy6rNMTF1JkjrVZbDV13alUnjFBNWosD8MyoNg0zCbWxixbbnVt34Dez9pDdC2529kq1rBLmmNLMaNaMJvwjmtJuMj6jadRlJatut1r1DSeVkkqnn8IwzP4S0XA4pCs/zk+jenoijXpwcnrkr1ENPEOSmgkJ/PTCqQZ+zgPPj1dT4OTkd6Xix4uuuLgb2OtA34EmGmyQFrrGRveAkSNz4wtiyb4mdhQeM9xLdlS9e7FX47SjxReBaq32aDB5bT3TAuW12PYailzst6jTla2zz0NUqpm2zRYjH+oaj9yjOGwPeMBVrHXpFzm7SISV3u9nt+QBK/Lg8WfB6AvlrVXInklZ9L3gKYpW9ueIq5DEqyUllAIjbFGuCJuzebICGvL53HLmEXlgwYHLycaglAF9sfXd5cVReUQM+Rwk25ecPQAvn3v0k3nQbXFvpaDup5vN1583Pzab9WkgMsUcatZLgyeuJumvU8P2kOKBSHz+PQGtbWmbXsrJcPltrFfqynXObAyDh9HTyPQbPcJ69eH+B/vq3/gO7Ktd5R3YV3vJM6xXuBO2bajB0Fj/ATzkBKlpBQAA" 102 | } 103 | ``` 104 |
105 | 106 |
Response for minimal 107 | 108 | ```js 109 | { 110 | "statusCode": 200, 111 | "headers": { 112 | "content-type": "text/html; charset=utf-8", 113 | "accept-ranges": "none", 114 | "vary": "Accept-Encoding", 115 | "content-encoding": "gzip" 116 | }, 117 | "isBase64Encoded": true, 118 | "body": "H4sIAAAAAAAAA5VUy46bMBTdz1d43G0cMIGQAEHqrqsu+pBGqqrI4Etwh2BkzEzSKP9e2xDNS9OZYeH3uY9zDze75rLUxw5QrfdNfpXZCXGmGWlJDYyTvlf5FUKZ3SC7MutGtLdIQbPBnRkl4xjVCqoN9rbtcNBeGC0BaMwgonQdx9wPgc//9BixfoP7UolO4/xNK7BarHi0LIBHEfeXUVEs/I9bWcacxmEJAaOrmJXgB8Hy41Z4TEtaxQWEq0VUxZzHcfHSSq+PDYzs3Q1gqSOCbzANQghWqzLxcT635kin5E5B35862QstZJtU4gA81bJL/LSBSptJiV1t5xrcIugO6b3gujZHsmOl0MeEplqxdjLhLtGc9rPpGs3DPi1YebtTcmg5KWUjVfLJ9/30LxEth0Oydt/5aVRPd6SVD05Oj/y1soVnSFIx0QA/vXCqgJ8zz/Hj1ORZOblVIfnxoisu7kb2elB3oIgCE6SBbrBWA2Bkydy6ghiyr4n5cocZzxt2lIO9MUfTUNP8i0CVkns0mrw2nmmOskrsBgV5JvY71KvS1NnlIUrZzrt2h5ELdYMn7lHkdwc84krW2fTzjF0kwgrn97OdMo/lmff4mTf5QllnFLJnTZMPg+AJMqJcQBH6JFqvKKEUGGFFFJFlHHCgax6UERjOHCLzDNizOZkYpNSgLra+27w4Ko6IIZdDw/YFZw/Ay3OHfjKOus3vjRTk/Xy7/frz5sd2uzmNRCaYQ8WGRuOZrUny69SyPSR4JBKff89AKVPadmia2Xj4bapXYst1Tk0Mo4fJ08T0Gz3CeHXh/gf76t/4DuyrXeUd2Fd7yTOsU7gVtmmo3thY/wHGyla/aQUAAA==" 119 | } 120 | ``` 121 |
122 | 123 | ### Config _with-etag_ 124 | |handler|load time|exec time|total time|memory usage|chksum| 125 | |---|---|---|---|---|---| 126 | |_full_|122.4|83.1|205.5|106 MB|Toqsh1+YkkzCOXdSyM0Dlg==| 127 | |_connect_|33.3|43.7|77|70 MB|BndH/0F1zmvFNRMSaiB3Kg==| 128 | |_minimal_|29.7|41.5|71.2|68 MB|8Qp9XdeFKxmgo++gQtjyeg==| 129 |
Response for full 130 | 131 | ```js 132 | { 133 | "statusCode": 200, 134 | "headers": { 135 | "etag": "\"569-P0BbQLm5Wplmhd1dyA3lOKpa7Uk\"", 136 | "content-type": "text/html; charset=utf-8", 137 | "accept-ranges": "none", 138 | "content-length": "1385" 139 | }, 140 | "isBase64Encoded": true, 141 | "body": "PCFkb2N0eXBlIGh0bWw+CjxodG1sIGRhdGEtbi1oZWFkLXNzcj4KICA8aGVhZCA+CiAgICA8bGluayByZWw9InByZWxvYWQiIGhyZWY9Ii9fbnV4dC80NTZlZTE3YWU1MTE5NzdkMDRlZC5qcyIgYXM9InNjcmlwdCI+PGxpbmsgcmVsPSJwcmVsb2FkIiBocmVmPSIvX251eHQvZTgzOGQ1NmJlZDU1ZDA2NWJiMzAuanMiIGFzPSJzY3JpcHQiPjxsaW5rIHJlbD0icHJlbG9hZCIgaHJlZj0iL19udXh0LzY3ZDE3NGNlMmExODdhY2UwMjI2LmpzIiBhcz0ic2NyaXB0Ij48bGluayByZWw9InByZWxvYWQiIGhyZWY9Ii9fbnV4dC9kNzFjMWY3YmU0ODM1ZjdkZDc3Yi5qcyIgYXM9InNjcmlwdCI+PHN0eWxlIGRhdGEtdnVlLXNzci1pZD0iMTI0ZTI4OGM6MCI+Lm51eHQtcHJvZ3Jlc3N7cG9zaXRpb246Zml4ZWQ7dG9wOjA7bGVmdDowO3JpZ2h0OjA7aGVpZ2h0OjJweDt3aWR0aDowO29wYWNpdHk6MTt0cmFuc2l0aW9uOndpZHRoIC4xcyxvcGFjaXR5IC40cztiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7ei1pbmRleDo5OTk5OTl9Lm51eHQtcHJvZ3Jlc3MubnV4dC1wcm9ncmVzcy1ub3RyYW5zaXRpb257dHJhbnNpdGlvbjpub25lfS5udXh0LXByb2dyZXNzLWZhaWxlZHtiYWNrZ3JvdW5kLWNvbG9yOnJlZH08L3N0eWxlPgogIDwvaGVhZD4KICA8Ym9keSA+CiAgICA8ZGl2IGRhdGEtc2VydmVyLXJlbmRlcmVkPSJ0cnVlIiBpZD0iX19udXh0Ij48IS0tLS0+PGRpdiBpZD0iX19sYXlvdXQiPjxkaXY+PGRpdj48aDE+SGkgZnJvbSBzZXJ2ZXIhPC9oMT4gPGZpZ3VyZT48aW1nIHNyYz0iL251eHQtaWNvbi5wbmciIHN0eWxlPSJoZWlnaHQ6IDUwcHgiPiA8ZmlnY2FwdGlvbj48YSBocmVmPSIvYWJvdXQiPkFib3V0PC9hPjwvZmlnY2FwdGlvbj48L2ZpZ3VyZT4gPHA+PHNtYWxsPnV1aWQ6IDU1ODNkZGEwLTU5ODEtMTFlYS1iYWNjLTg1NTcwYWZkYjI0ODwvc21hbGw+PC9wPjwvZGl2PiA8Zm9vdGVyPjxzbWFsbD5TZXJ2ZWQgYnkgYSBudXh0LWxhbWJkYTwvc21hbGw+PC9mb290ZXI+PC9kaXY+PC9kaXY+PC9kaXY+PHNjcmlwdD53aW5kb3cuX19OVVhUX189e2xheW91dDoiZGVmYXVsdCIsZGF0YTpbe25hbWU6InNlcnZlciJ9XSxlcnJvcjpudWxsLHNlcnZlclJlbmRlcmVkOnRydWV9Ozwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvNDU2ZWUxN2FlNTExOTc3ZDA0ZWQuanMiIGRlZmVyPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvZDcxYzFmN2JlNDgzNWY3ZGQ3N2IuanMiIGRlZmVyPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvZTgzOGQ1NmJlZDU1ZDA2NWJiMzAuanMiIGRlZmVyPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvNjdkMTc0Y2UyYTE4N2FjZTAyMjYuanMiIGRlZmVyPjwvc2NyaXB0PgogIDwvYm9keT4KPC9odG1sPgo=" 142 | } 143 | ``` 144 |
145 | 146 |
Response for connect 147 | 148 | ```js 149 | { 150 | "statusCode": 200, 151 | "headers": { 152 | "etag": "\"569-IQjIlEirG0AntouF07nHCGX6DgY\"", 153 | "content-type": "text/html; charset=utf-8", 154 | "accept-ranges": "none", 155 | "content-length": "1385" 156 | }, 157 | "isBase64Encoded": true, 158 | "body": "PCFkb2N0eXBlIGh0bWw+CjxodG1sIGRhdGEtbi1oZWFkLXNzcj4KICA8aGVhZCA+CiAgICA8bGluayByZWw9InByZWxvYWQiIGhyZWY9Ii9fbnV4dC80NTZlZTE3YWU1MTE5NzdkMDRlZC5qcyIgYXM9InNjcmlwdCI+PGxpbmsgcmVsPSJwcmVsb2FkIiBocmVmPSIvX251eHQvZTgzOGQ1NmJlZDU1ZDA2NWJiMzAuanMiIGFzPSJzY3JpcHQiPjxsaW5rIHJlbD0icHJlbG9hZCIgaHJlZj0iL19udXh0LzY3ZDE3NGNlMmExODdhY2UwMjI2LmpzIiBhcz0ic2NyaXB0Ij48bGluayByZWw9InByZWxvYWQiIGhyZWY9Ii9fbnV4dC9kNzFjMWY3YmU0ODM1ZjdkZDc3Yi5qcyIgYXM9InNjcmlwdCI+PHN0eWxlIGRhdGEtdnVlLXNzci1pZD0iMTI0ZTI4OGM6MCI+Lm51eHQtcHJvZ3Jlc3N7cG9zaXRpb246Zml4ZWQ7dG9wOjA7bGVmdDowO3JpZ2h0OjA7aGVpZ2h0OjJweDt3aWR0aDowO29wYWNpdHk6MTt0cmFuc2l0aW9uOndpZHRoIC4xcyxvcGFjaXR5IC40cztiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7ei1pbmRleDo5OTk5OTl9Lm51eHQtcHJvZ3Jlc3MubnV4dC1wcm9ncmVzcy1ub3RyYW5zaXRpb257dHJhbnNpdGlvbjpub25lfS5udXh0LXByb2dyZXNzLWZhaWxlZHtiYWNrZ3JvdW5kLWNvbG9yOnJlZH08L3N0eWxlPgogIDwvaGVhZD4KICA8Ym9keSA+CiAgICA8ZGl2IGRhdGEtc2VydmVyLXJlbmRlcmVkPSJ0cnVlIiBpZD0iX19udXh0Ij48IS0tLS0+PGRpdiBpZD0iX19sYXlvdXQiPjxkaXY+PGRpdj48aDE+SGkgZnJvbSBzZXJ2ZXIhPC9oMT4gPGZpZ3VyZT48aW1nIHNyYz0iL251eHQtaWNvbi5wbmciIHN0eWxlPSJoZWlnaHQ6IDUwcHgiPiA8ZmlnY2FwdGlvbj48YSBocmVmPSIvYWJvdXQiPkFib3V0PC9hPjwvZmlnY2FwdGlvbj48L2ZpZ3VyZT4gPHA+PHNtYWxsPnV1aWQ6IDVjYjNjNTQwLTU5ODEtMTFlYS1hNGRiLWY5NGNiZTk2MDBkNzwvc21hbGw+PC9wPjwvZGl2PiA8Zm9vdGVyPjxzbWFsbD5TZXJ2ZWQgYnkgYSBudXh0LWxhbWJkYTwvc21hbGw+PC9mb290ZXI+PC9kaXY+PC9kaXY+PC9kaXY+PHNjcmlwdD53aW5kb3cuX19OVVhUX189e2xheW91dDoiZGVmYXVsdCIsZGF0YTpbe25hbWU6InNlcnZlciJ9XSxlcnJvcjpudWxsLHNlcnZlclJlbmRlcmVkOnRydWV9Ozwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvNDU2ZWUxN2FlNTExOTc3ZDA0ZWQuanMiIGRlZmVyPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvZDcxYzFmN2JlNDgzNWY3ZGQ3N2IuanMiIGRlZmVyPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvZTgzOGQ1NmJlZDU1ZDA2NWJiMzAuanMiIGRlZmVyPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvNjdkMTc0Y2UyYTE4N2FjZTAyMjYuanMiIGRlZmVyPjwvc2NyaXB0PgogIDwvYm9keT4KPC9odG1sPgo=" 159 | } 160 | ``` 161 |
162 | 163 |
Response for minimal 164 | 165 | ```js 166 | { 167 | "statusCode": 200, 168 | "headers": { 169 | "etag": "\"569-ByX5FJmEasai68yzAk2OdB0hVcs\"", 170 | "content-type": "text/html; charset=utf-8", 171 | "accept-ranges": "none", 172 | "content-length": 1385 173 | }, 174 | "isBase64Encoded": false, 175 | "body": "\n\n \n \n \n \n

Hi from server!

About

uuid: 60b0e380-5981-11ea-8b68-bdad45a81041

Served by a nuxt-lambda
\n \n\n" 176 | } 177 | ``` 178 |
179 | 180 | ### Config _with-static_ 181 | |handler|load time|exec time|total time|memory usage|chksum| 182 | |---|---|---|---|---|---| 183 | |_full_|122.1|84.1|206.2|112 MB|e/N6jWt0sa24Y0pk5hKRww==| 184 | |_connect_|38.8|45.9|84.7|71.7 MB|t2sIc32tCAaNtPIzHutdhg==| 185 | |_minimal_|29|41.2|70.2|69.2 MB|vUsDeFIkpln0svAEjeDoUQ==| 186 |
Response for full 187 | 188 | ```js 189 | { 190 | "statusCode": 200, 191 | "headers": { 192 | "content-type": "text/html; charset=utf-8", 193 | "accept-ranges": "none", 194 | "content-length": "1385" 195 | }, 196 | "isBase64Encoded": true, 197 | "body": "PCFkb2N0eXBlIGh0bWw+CjxodG1sIGRhdGEtbi1oZWFkLXNzcj4KICA8aGVhZCA+CiAgICA8bGluayByZWw9InByZWxvYWQiIGhyZWY9Ii9fbnV4dC80NTZlZTE3YWU1MTE5NzdkMDRlZC5qcyIgYXM9InNjcmlwdCI+PGxpbmsgcmVsPSJwcmVsb2FkIiBocmVmPSIvX251eHQvZTgzOGQ1NmJlZDU1ZDA2NWJiMzAuanMiIGFzPSJzY3JpcHQiPjxsaW5rIHJlbD0icHJlbG9hZCIgaHJlZj0iL19udXh0LzY3ZDE3NGNlMmExODdhY2UwMjI2LmpzIiBhcz0ic2NyaXB0Ij48bGluayByZWw9InByZWxvYWQiIGhyZWY9Ii9fbnV4dC9kNzFjMWY3YmU0ODM1ZjdkZDc3Yi5qcyIgYXM9InNjcmlwdCI+PHN0eWxlIGRhdGEtdnVlLXNzci1pZD0iMTI0ZTI4OGM6MCI+Lm51eHQtcHJvZ3Jlc3N7cG9zaXRpb246Zml4ZWQ7dG9wOjA7bGVmdDowO3JpZ2h0OjA7aGVpZ2h0OjJweDt3aWR0aDowO29wYWNpdHk6MTt0cmFuc2l0aW9uOndpZHRoIC4xcyxvcGFjaXR5IC40cztiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7ei1pbmRleDo5OTk5OTl9Lm51eHQtcHJvZ3Jlc3MubnV4dC1wcm9ncmVzcy1ub3RyYW5zaXRpb257dHJhbnNpdGlvbjpub25lfS5udXh0LXByb2dyZXNzLWZhaWxlZHtiYWNrZ3JvdW5kLWNvbG9yOnJlZH08L3N0eWxlPgogIDwvaGVhZD4KICA8Ym9keSA+CiAgICA8ZGl2IGRhdGEtc2VydmVyLXJlbmRlcmVkPSJ0cnVlIiBpZD0iX19udXh0Ij48IS0tLS0+PGRpdiBpZD0iX19sYXlvdXQiPjxkaXY+PGRpdj48aDE+SGkgZnJvbSBzZXJ2ZXIhPC9oMT4gPGZpZ3VyZT48aW1nIHNyYz0iL251eHQtaWNvbi5wbmciIHN0eWxlPSJoZWlnaHQ6IDUwcHgiPiA8ZmlnY2FwdGlvbj48YSBocmVmPSIvYWJvdXQiPkFib3V0PC9hPjwvZmlnY2FwdGlvbj48L2ZpZ3VyZT4gPHA+PHNtYWxsPnV1aWQ6IDY5YzM5NGUwLTU5ODEtMTFlYS1hZDQwLTczY2IwMGZkMzk1MDwvc21hbGw+PC9wPjwvZGl2PiA8Zm9vdGVyPjxzbWFsbD5TZXJ2ZWQgYnkgYSBudXh0LWxhbWJkYTwvc21hbGw+PC9mb290ZXI+PC9kaXY+PC9kaXY+PC9kaXY+PHNjcmlwdD53aW5kb3cuX19OVVhUX189e2xheW91dDoiZGVmYXVsdCIsZGF0YTpbe25hbWU6InNlcnZlciJ9XSxlcnJvcjpudWxsLHNlcnZlclJlbmRlcmVkOnRydWV9Ozwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvNDU2ZWUxN2FlNTExOTc3ZDA0ZWQuanMiIGRlZmVyPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvZDcxYzFmN2JlNDgzNWY3ZGQ3N2IuanMiIGRlZmVyPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvZTgzOGQ1NmJlZDU1ZDA2NWJiMzAuanMiIGRlZmVyPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvNjdkMTc0Y2UyYTE4N2FjZTAyMjYuanMiIGRlZmVyPjwvc2NyaXB0PgogIDwvYm9keT4KPC9odG1sPgo=" 198 | } 199 | ``` 200 |
201 | 202 |
Response for connect 203 | 204 | ```js 205 | { 206 | "statusCode": 200, 207 | "headers": { 208 | "content-type": "text/html; charset=utf-8", 209 | "accept-ranges": "none", 210 | "content-length": "1385" 211 | }, 212 | "isBase64Encoded": true, 213 | "body": "PCFkb2N0eXBlIGh0bWw+CjxodG1sIGRhdGEtbi1oZWFkLXNzcj4KICA8aGVhZCA+CiAgICA8bGluayByZWw9InByZWxvYWQiIGhyZWY9Ii9fbnV4dC80NTZlZTE3YWU1MTE5NzdkMDRlZC5qcyIgYXM9InNjcmlwdCI+PGxpbmsgcmVsPSJwcmVsb2FkIiBocmVmPSIvX251eHQvZTgzOGQ1NmJlZDU1ZDA2NWJiMzAuanMiIGFzPSJzY3JpcHQiPjxsaW5rIHJlbD0icHJlbG9hZCIgaHJlZj0iL19udXh0LzY3ZDE3NGNlMmExODdhY2UwMjI2LmpzIiBhcz0ic2NyaXB0Ij48bGluayByZWw9InByZWxvYWQiIGhyZWY9Ii9fbnV4dC9kNzFjMWY3YmU0ODM1ZjdkZDc3Yi5qcyIgYXM9InNjcmlwdCI+PHN0eWxlIGRhdGEtdnVlLXNzci1pZD0iMTI0ZTI4OGM6MCI+Lm51eHQtcHJvZ3Jlc3N7cG9zaXRpb246Zml4ZWQ7dG9wOjA7bGVmdDowO3JpZ2h0OjA7aGVpZ2h0OjJweDt3aWR0aDowO29wYWNpdHk6MTt0cmFuc2l0aW9uOndpZHRoIC4xcyxvcGFjaXR5IC40cztiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7ei1pbmRleDo5OTk5OTl9Lm51eHQtcHJvZ3Jlc3MubnV4dC1wcm9ncmVzcy1ub3RyYW5zaXRpb257dHJhbnNpdGlvbjpub25lfS5udXh0LXByb2dyZXNzLWZhaWxlZHtiYWNrZ3JvdW5kLWNvbG9yOnJlZH08L3N0eWxlPgogIDwvaGVhZD4KICA8Ym9keSA+CiAgICA8ZGl2IGRhdGEtc2VydmVyLXJlbmRlcmVkPSJ0cnVlIiBpZD0iX19udXh0Ij48IS0tLS0+PGRpdiBpZD0iX19sYXlvdXQiPjxkaXY+PGRpdj48aDE+SGkgZnJvbSBzZXJ2ZXIhPC9oMT4gPGZpZ3VyZT48aW1nIHNyYz0iL251eHQtaWNvbi5wbmciIHN0eWxlPSJoZWlnaHQ6IDUwcHgiPiA8ZmlnY2FwdGlvbj48YSBocmVmPSIvYWJvdXQiPkFib3V0PC9hPjwvZmlnY2FwdGlvbj48L2ZpZ3VyZT4gPHA+PHNtYWxsPnV1aWQ6IDcxMGVhNWEwLTU5ODEtMTFlYS05ZTY3LWY1MjE3NDgwZjNjMTwvc21hbGw+PC9wPjwvZGl2PiA8Zm9vdGVyPjxzbWFsbD5TZXJ2ZWQgYnkgYSBudXh0LWxhbWJkYTwvc21hbGw+PC9mb290ZXI+PC9kaXY+PC9kaXY+PC9kaXY+PHNjcmlwdD53aW5kb3cuX19OVVhUX189e2xheW91dDoiZGVmYXVsdCIsZGF0YTpbe25hbWU6InNlcnZlciJ9XSxlcnJvcjpudWxsLHNlcnZlclJlbmRlcmVkOnRydWV9Ozwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvNDU2ZWUxN2FlNTExOTc3ZDA0ZWQuanMiIGRlZmVyPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvZDcxYzFmN2JlNDgzNWY3ZGQ3N2IuanMiIGRlZmVyPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvZTgzOGQ1NmJlZDU1ZDA2NWJiMzAuanMiIGRlZmVyPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvNjdkMTc0Y2UyYTE4N2FjZTAyMjYuanMiIGRlZmVyPjwvc2NyaXB0PgogIDwvYm9keT4KPC9odG1sPgo=" 214 | } 215 | ``` 216 |
217 | 218 |
Response for minimal 219 | 220 | ```js 221 | { 222 | "statusCode": 200, 223 | "headers": { 224 | "content-type": "text/html; charset=utf-8", 225 | "accept-ranges": "none", 226 | "content-length": 1385 227 | }, 228 | "isBase64Encoded": false, 229 | "body": "\n\n \n \n \n \n

Hi from server!

About

uuid: 74cbada0-5981-11ea-b4bb-e51e4e5c259e

Served by a nuxt-lambda
\n \n\n" 230 | } 231 | ``` 232 |
233 | 234 | ## Route `/about` 235 | ### Config _base_ 236 | |handler|load time|exec time|total time|memory usage|chksum| 237 | |---|---|---|---|---|---| 238 | |_full_|123.8|181.9|305.8|115 MB|DPgDl85zFYfiBnWM6glBNw==| 239 | |_connect_|33.8|154.7|188.5|71.7 MB|DPgDl85zFYfiBnWM6glBNw==| 240 | |_minimal_|29.3|142.4|171.7|69.8 MB|tJd3/XQ6XFswKbzA3XSqQw==| 241 |
Response for full 242 | 243 | ```js 244 | blob_with_size_4006 245 | ``` 246 |
247 | 248 |
Response for connect 249 | 250 | ```js 251 | blob_with_size_4006 252 | ``` 253 |
254 | 255 |
Response for minimal 256 | 257 | ```js 258 | { 259 | "statusCode": 200, 260 | "headers": { 261 | "content-type": "text/html; charset=utf-8", 262 | "accept-ranges": "none", 263 | "content-length": 2860 264 | }, 265 | "isBase64Encoded": false, 266 | "body": "\n\n \n \n \n \n

About from server

  • \n sunt aut facere repellat provident occaecati excepturi optio reprehenderit\n
  • \n qui est esse\n
  • \n ea molestias quasi exercitationem repellat qui ipsa sit aut\n
  • \n eum et est occaecati\n
  • \n nesciunt quas odio\n

\n Back to home page\n

Served by a nuxt-lambda
\n \n\n" 267 | } 268 | ``` 269 |
270 | 271 | ### Config _with-compression_ 272 | |handler|load time|exec time|total time|memory usage|chksum| 273 | |---|---|---|---|---|---| 274 | |_full_|121.2|193.5|314.7|115 MB|58FxPFjLIbcBIK9eerUorg==| 275 | |_connect_|41.8|139.3|181.1|74.9 MB|58FxPFjLIbcBIK9eerUorg==| 276 | |_minimal_|29.4|137.5|166.9|71.6 MB|58FxPFjLIbcBIK9eerUorg==| 277 |
Response for full 278 | 279 | ```js 280 | { 281 | "statusCode": 200, 282 | "headers": { 283 | "content-type": "text/html; charset=utf-8", 284 | "accept-ranges": "none", 285 | "vary": "Accept-Encoding", 286 | "content-encoding": "gzip" 287 | }, 288 | "isBase64Encoded": true, 289 | "body": "H4sIAAAAAAAAA61WS28bNxC+51ew20sCSNbDkiWvZAHtrZce+gAKVIExS44sNlxyzYdi19B/7wxXK0uO0yRoBWjFJef5zTcjLr9TTsbHBsU21mb1Zsk/QkGEvu1vEVQ/BL96I8SSXwSvaG20/SA8mpuioacDVYitx81NMbi16SEOJtMrxNEMcDoaXc9majhBdfFXKASEmyJIr5tYrL5oBeeXczW9qlBNp2p4Na2qy+G3W7maqdFsInEMo/kMJA7H46tvtzK6nsxHw2o4nwwBqqm6nl1uPrUS4qPBFr1dQoaur9VNMRpPcDyfy3JYrC7YXL/x7s5jCE+NCzpqZ8uNfkC1iK4phwuDm0g/Xt9t+XeLeTFuHhYftYpb2nINSB0fy9EierAHE/lQXIxC73AsLiZhUYH8cOddsqovnXG+/H44HC7+7mur8KG8zp/9eVTnb33rnp08nfizzuILzf4GtEH19IlTj2q/HGR8MpsGTKe8qpx67Hil9K5FL6Dfoe97pCBJ9aaIPmEhGMzbXBAC+7s+fVZZp9038OgSn9BWuy8NBKqPdDaCtujpbDta/VCRmNh4V4vWD0UzWollMsyDNhQhQrJRAAsSaTwSORo0BqKgXHdaIZ06KQElRC3wQWITk9fCNYQNC3vc5uh1bHMbkOkT8/eJtEKkb8BXBRBE7QyJaAgkDYG9oKe6AqOP9XNEbEs3AQQVhkN+3V6qBcbs8xj3q4IWg9ScPDsVTml3IjYgkMSyWS2h64+iQzkTgfuoDzLqHRadwR+JDCI6sXU1igbuunyBzJGlAZdLLDfORfTUQzUYs/qVC6NE9ShAtIahrhQQhfLxctCJZ+2zZ9uNq49EcPfx4vb259//+O329ubtJlnJyL2Fd08eqVhWPLWUKQuFG0gmFj1mX/nnk4Uay6JlR9GjJo2BdhNt/KRK6Gl+RB0NC/1vPCl63AtlQeUELlVIVIhGx7XtViQvUwCrAAWROuB9Iu/Jk90GFTGD1WSq1/bM8JFIKCjOdKhsdBFI0lJyPjGb+Mn0oGSIXTkvTwvcodVkF7zc6ogyumLfO8Ni3GFxSuouG35vbZPVxhFCOwoU1zl6TZQlN1ZvtTkHQyieG6JCKghyM+R3DETP+0Tam3SnCeTKEBo0jnQQO2dSQ81BHHPeO7FDczB8lr5HLTU50WFtOQBoyB3UgqaZUFiRrUAGQtB1CrmzskOyRA1oE1X2ZfaXXfb/oWOPYEWhU6BeCUT+++TozVOyx9TqEy65mmISaPQdZ5MpCOpMNkOmK8qDsQCZuRM1lYL8U5ZAiRN71vYZoBY6pCBy5zOfDFRcNVqRlUO4LyCYHCF4Zch0uTF2+TQAwXBSB3ESMijiOZF9bTMyNXZ0pBGTahKHk75qaSWTaSDXMoitltQYdU2xP88xJnFEyy1/YFHNFWX86wPAWTkz/iSW1rw2xrWZv8x62mX96cTscqaCJ6XbhuU2AoYe0ENsvVGZ1xYMEyaPkJbUB1JwExINCMxzBuS6HynKvXaMOhebk1nbQyefZ96Sudi/37/vIVXal0zpXjvofjn825b8Z7vfvx29e7eggduO08NYFcHLL1/zaJzm2fwvup+9UH2F7mcvhl+h+9nr4AvdfEnhItKdeNDejf8BCV7FLCwLAAA=" 290 | } 291 | ``` 292 |
293 | 294 |
Response for connect 295 | 296 | ```js 297 | { 298 | "statusCode": 200, 299 | "headers": { 300 | "content-type": "text/html; charset=utf-8", 301 | "accept-ranges": "none", 302 | "vary": "Accept-Encoding", 303 | "content-encoding": "gzip" 304 | }, 305 | "isBase64Encoded": true, 306 | "body": "H4sIAAAAAAAAA61WS28bNxC+51ew20sCSNbDkiWvZAHtrZce+gAKVIExS44sNlxyzYdi19B/7wxXK0uO0yRoBWjFJef5zTcjLr9TTsbHBsU21mb1Zsk/QkGEvu1vEVQ/BL96I8SSXwSvaG20/SA8mpuioacDVYitx81NMbi16SEOJtMrxNEMcDoaXc9majhBdfFXKASEmyJIr5tYrL5oBeeXczW9qlBNp2p4Na2qy+G3W7maqdFsInEMo/kMJA7H46tvtzK6nsxHw2o4nwwBqqm6nl1uPrUS4qPBFr1dQoaur9VNMRpPcDyfy3JYrC7YXL/x7s5jCE+NCzpqZ8uNfkC1iK4phwuDm0g/Xt9t+XeLeTFuHhYftYpb2nINSB0fy9EierAHE/lQXIxC73AsLiZhUYH8cOddsqovnXG+/H44HC7+7mur8KG8zp/9eVTnb33rnp08nfizzuILzf4GtEH19IlTj2q/HGR8MpsGTKe8qpx67Hil9K5FL6Dfoe97pCBJ9aaIPmEhGMzbXBAC+7s+fVZZp9038OgSn9BWuy8NBKqPdDaCtujpbDta/VCRmNh4V4vWD0UzWollMsyDNhQhQrJRAAsSaTwSORo0BqKgXHdaIZ06KQElRC3wQWITk9fCNYQNC3vc5uh1bHMbkOkT8/eJtEKkb8BXBRBE7QyJaAgkDYG9oKe6AqOP9XNEbEs3AQQVhkN+3V6qBcbs8xj3q4IWg9ScPDsVTml3IjYgkMSyWS2h64+iQzkTgfuoDzLqHRadwR+JDCI6sXU1igbuunyBzJGlAZdLLDfORfTUQzUYs/qVC6NE9ShAtIahrhQQhfLxctCJZ+2zZ9uNq49EcPfx4vb259//+O329ubtJlnJyL2Fd08eqVhWPLWUKQuFG0gmFj1mX/nnk4Uay6JlR9GjJo2BdhNt/KRK6Gl+RB0NC/1vPCl63AtlQeUELlVIVIhGx7XtViQvUwCrAAWROuB9Iu/Jk90GFTGD1WSq1/bM8JFIKCjOdKhsdBFI0lJyPjGb+Mn0oGSIXTkvTwvcodVkF7zc6ogyumLfO8Ni3GFxSuouG35vbZPVxhFCOwoU1zl6TZQlN1ZvtTkHQyieG6JCKghyM+R3DETP+0Tam3SnCeTKEBo0jnQQO2dSQ81BHHPeO7FDczB8lr5HLTU50WFtOQBoyB3UgqaZUFiRrUAGQtB1CrmzskOyRA1oE1X2ZfaXXfb/oWOPYEWhU6BeCUT+++TozVOyx9TqEy65mmISaPQdZ5MpCOpMNkOmK8qDsQCZuRM1lYL8U5ZAiRN71vYZoBY6pCBy5zOfDFRcNVqRlUO4LyCYHCF4Zch0uTF2+TQAwXBSB3ESMijiOZF9bTMyNXZ0pBGTahKHk75qaSWTaSDXMoitltQYdU2xP88xJnFEyy1/YFHNFWX86wPAWTkz/iSW1rw2xrWZv8x62mX96cTscqaCJ6XbhuU2AoYe0ENsvVGZ1xYMEyaPkJbUB1JwExINCMxzBuS6HynKvXaMOhebk1nbQyefZ96Sudi/37/vIVXal0zpXjvofjn825b8Z7vfvx29e7eggduO08NYFcHLL1/zaJzm2fwvup+9UH2F7mcvhl+h+9nr4AvdfEnhItKdeNDejf8BCV7FLCwLAAA=" 307 | } 308 | ``` 309 |
310 | 311 |
Response for minimal 312 | 313 | ```js 314 | { 315 | "statusCode": 200, 316 | "headers": { 317 | "content-type": "text/html; charset=utf-8", 318 | "accept-ranges": "none", 319 | "vary": "Accept-Encoding", 320 | "content-encoding": "gzip" 321 | }, 322 | "isBase64Encoded": true, 323 | "body": "H4sIAAAAAAAAA61WS28bNxC+51ew20sCSNbDkiWvZAHtrZce+gAKVIExS44sNlxyzYdi19B/7wxXK0uO0yRoBWjFJef5zTcjLr9TTsbHBsU21mb1Zsk/QkGEvu1vEVQ/BL96I8SSXwSvaG20/SA8mpuioacDVYitx81NMbi16SEOJtMrxNEMcDoaXc9majhBdfFXKASEmyJIr5tYrL5oBeeXczW9qlBNp2p4Na2qy+G3W7maqdFsInEMo/kMJA7H46tvtzK6nsxHw2o4nwwBqqm6nl1uPrUS4qPBFr1dQoaur9VNMRpPcDyfy3JYrC7YXL/x7s5jCE+NCzpqZ8uNfkC1iK4phwuDm0g/Xt9t+XeLeTFuHhYftYpb2nINSB0fy9EierAHE/lQXIxC73AsLiZhUYH8cOddsqovnXG+/H44HC7+7mur8KG8zp/9eVTnb33rnp08nfizzuILzf4GtEH19IlTj2q/HGR8MpsGTKe8qpx67Hil9K5FL6Dfoe97pCBJ9aaIPmEhGMzbXBAC+7s+fVZZp9038OgSn9BWuy8NBKqPdDaCtujpbDta/VCRmNh4V4vWD0UzWollMsyDNhQhQrJRAAsSaTwSORo0BqKgXHdaIZ06KQElRC3wQWITk9fCNYQNC3vc5uh1bHMbkOkT8/eJtEKkb8BXBRBE7QyJaAgkDYG9oKe6AqOP9XNEbEs3AQQVhkN+3V6qBcbs8xj3q4IWg9ScPDsVTml3IjYgkMSyWS2h64+iQzkTgfuoDzLqHRadwR+JDCI6sXU1igbuunyBzJGlAZdLLDfORfTUQzUYs/qVC6NE9ShAtIahrhQQhfLxctCJZ+2zZ9uNq49EcPfx4vb259//+O329ubtJlnJyL2Fd08eqVhWPLWUKQuFG0gmFj1mX/nnk4Uay6JlR9GjJo2BdhNt/KRK6Gl+RB0NC/1vPCl63AtlQeUELlVIVIhGx7XtViQvUwCrAAWROuB9Iu/Jk90GFTGD1WSq1/bM8JFIKCjOdKhsdBFI0lJyPjGb+Mn0oGSIXTkvTwvcodVkF7zc6ogyumLfO8Ni3GFxSuouG35vbZPVxhFCOwoU1zl6TZQlN1ZvtTkHQyieG6JCKghyM+R3DETP+0Tam3SnCeTKEBo0jnQQO2dSQ81BHHPeO7FDczB8lr5HLTU50WFtOQBoyB3UgqaZUFiRrUAGQtB1CrmzskOyRA1oE1X2ZfaXXfb/oWOPYEWhU6BeCUT+++TozVOyx9TqEy65mmISaPQdZ5MpCOpMNkOmK8qDsQCZuRM1lYL8U5ZAiRN71vYZoBY6pCBy5zOfDFRcNVqRlUO4LyCYHCF4Zch0uTF2+TQAwXBSB3ESMijiOZF9bTMyNXZ0pBGTahKHk75qaSWTaSDXMoitltQYdU2xP88xJnFEyy1/YFHNFWX86wPAWTkz/iSW1rw2xrWZv8x62mX96cTscqaCJ6XbhuU2AoYe0ENsvVGZ1xYMEyaPkJbUB1JwExINCMxzBuS6HynKvXaMOhebk1nbQyefZ96Sudi/37/vIVXal0zpXjvofjn825b8Z7vfvx29e7eggduO08NYFcHLL1/zaJzm2fwvup+9UH2F7mcvhl+h+9nr4AvdfEnhItKdeNDejf8BCV7FLCwLAAA=" 324 | } 325 | ``` 326 |
327 | 328 | ### Config _with-etag_ 329 | |handler|load time|exec time|total time|memory usage|chksum| 330 | |---|---|---|---|---|---| 331 | |_full_|119.7|191.1|310.7|116 MB|YyOsCcyDXWnp4IUl+rF/8A==| 332 | |_connect_|33.6|133|166.7|72.7 MB|YyOsCcyDXWnp4IUl+rF/8A==| 333 | |_minimal_|28.8|132.7|161.5|72.9 MB|/0naPgKEVILCd4EgZ9umsQ==| 334 |
Response for full 335 | 336 | ```js 337 | blob_with_size_4057 338 | ``` 339 |
340 | 341 |
Response for connect 342 | 343 | ```js 344 | blob_with_size_4057 345 | ``` 346 |
347 | 348 |
Response for minimal 349 | 350 | ```js 351 | { 352 | "statusCode": 200, 353 | "headers": { 354 | "etag": "\"b2c-+4BzmtOUQYVrYTP+Rq9V0+WHIdM\"", 355 | "content-type": "text/html; charset=utf-8", 356 | "accept-ranges": "none", 357 | "content-length": 2860 358 | }, 359 | "isBase64Encoded": false, 360 | "body": "\n\n \n \n \n \n

About from server

  • \n sunt aut facere repellat provident occaecati excepturi optio reprehenderit\n
  • \n qui est esse\n
  • \n ea molestias quasi exercitationem repellat qui ipsa sit aut\n
  • \n eum et est occaecati\n
  • \n nesciunt quas odio\n

\n Back to home page\n

Served by a nuxt-lambda
\n \n\n" 361 | } 362 | ``` 363 |
364 | 365 | ### Config _with-static_ 366 | |handler|load time|exec time|total time|memory usage|chksum| 367 | |---|---|---|---|---|---| 368 | |_full_|123.4|180.1|303.5|110 MB|DPgDl85zFYfiBnWM6glBNw==| 369 | |_connect_|40.1|134.6|174.8|74.5 MB|DPgDl85zFYfiBnWM6glBNw==| 370 | |_minimal_|28.8|134.9|163.6|71.2 MB|tJd3/XQ6XFswKbzA3XSqQw==| 371 |
Response for full 372 | 373 | ```js 374 | blob_with_size_4006 375 | ``` 376 |
377 | 378 |
Response for connect 379 | 380 | ```js 381 | blob_with_size_4006 382 | ``` 383 |
384 | 385 |
Response for minimal 386 | 387 | ```js 388 | { 389 | "statusCode": 200, 390 | "headers": { 391 | "content-type": "text/html; charset=utf-8", 392 | "accept-ranges": "none", 393 | "content-length": 2860 394 | }, 395 | "isBase64Encoded": false, 396 | "body": "\n\n \n \n \n \n

About from server

  • \n sunt aut facere repellat provident occaecati excepturi optio reprehenderit\n
  • \n qui est esse\n
  • \n ea molestias quasi exercitationem repellat qui ipsa sit aut\n
  • \n eum et est occaecati\n
  • \n nesciunt quas odio\n

\n Back to home page\n

Served by a nuxt-lambda
\n \n\n" 397 | } 398 | ``` 399 |
400 | 401 | ## Route `/redirect` 402 | ### Config _base_ 403 | |handler|load time|exec time|total time|memory usage|chksum| 404 | |---|---|---|---|---|---| 405 | |_full_|119.6|74.3|193.9|113 MB|Oj9UlW05muQ2LHwz+7+a1w==| 406 | |_connect_|35.3|37.3|72.6|69.3 MB|Oj9UlW05muQ2LHwz+7+a1w==| 407 | |_minimal_|30.5|31.3|61.8|67.6 MB|atSW0WUyEvi3qig5Uosd7A==| 408 |
Response for full 409 | 410 | ```js 411 | { 412 | "statusCode": 302, 413 | "headers": {}, 414 | "isBase64Encoded": false, 415 | "body": "" 416 | } 417 | ``` 418 |
419 | 420 |
Response for connect 421 | 422 | ```js 423 | { 424 | "statusCode": 302, 425 | "headers": {}, 426 | "isBase64Encoded": false, 427 | "body": "" 428 | } 429 | ``` 430 |
431 | 432 |
Response for minimal 433 | 434 | ```js 435 | { 436 | "statusCode": 302, 437 | "headers": { 438 | "location": "/about" 439 | }, 440 | "isBase64Encoded": false, 441 | "body": "" 442 | } 443 | ``` 444 |
445 | 446 | ### Config _with-compression_ 447 | |handler|load time|exec time|total time|memory usage|chksum| 448 | |---|---|---|---|---|---| 449 | |_full_|121.9|84.3|206.2|112 MB|atSW0WUyEvi3qig5Uosd7A==| 450 | |_connect_|41.4|36.2|77.6|72.2 MB|atSW0WUyEvi3qig5Uosd7A==| 451 | |_minimal_|29.4|30|59.4|68.9 MB|atSW0WUyEvi3qig5Uosd7A==| 452 |
Response for full 453 | 454 | ```js 455 | { 456 | "statusCode": 302, 457 | "headers": { 458 | "location": "/about" 459 | }, 460 | "isBase64Encoded": false, 461 | "body": "" 462 | } 463 | ``` 464 |
465 | 466 |
Response for connect 467 | 468 | ```js 469 | { 470 | "statusCode": 302, 471 | "headers": { 472 | "location": "/about" 473 | }, 474 | "isBase64Encoded": false, 475 | "body": "" 476 | } 477 | ``` 478 |
479 | 480 |
Response for minimal 481 | 482 | ```js 483 | { 484 | "statusCode": 302, 485 | "headers": { 486 | "location": "/about" 487 | }, 488 | "isBase64Encoded": false, 489 | "body": "" 490 | } 491 | ``` 492 |
493 | 494 | ### Config _with-etag_ 495 | |handler|load time|exec time|total time|memory usage|chksum| 496 | |---|---|---|---|---|---| 497 | |_full_|123|75.5|198.5|108 MB|Oj9UlW05muQ2LHwz+7+a1w==| 498 | |_connect_|34.1|35.7|69.9|68.1 MB|Oj9UlW05muQ2LHwz+7+a1w==| 499 | |_minimal_|29.2|30.4|59.5|68.3 MB|atSW0WUyEvi3qig5Uosd7A==| 500 |
Response for full 501 | 502 | ```js 503 | { 504 | "statusCode": 302, 505 | "headers": {}, 506 | "isBase64Encoded": false, 507 | "body": "" 508 | } 509 | ``` 510 |
511 | 512 |
Response for connect 513 | 514 | ```js 515 | { 516 | "statusCode": 302, 517 | "headers": {}, 518 | "isBase64Encoded": false, 519 | "body": "" 520 | } 521 | ``` 522 |
523 | 524 |
Response for minimal 525 | 526 | ```js 527 | { 528 | "statusCode": 302, 529 | "headers": { 530 | "location": "/about" 531 | }, 532 | "isBase64Encoded": false, 533 | "body": "" 534 | } 535 | ``` 536 |
537 | 538 | ### Config _with-static_ 539 | |handler|load time|exec time|total time|memory usage|chksum| 540 | |---|---|---|---|---|---| 541 | |_full_|123.1|74.9|198|107 MB|Oj9UlW05muQ2LHwz+7+a1w==| 542 | |_connect_|40|38.5|78.5|71 MB|Oj9UlW05muQ2LHwz+7+a1w==| 543 | |_minimal_|28.3|30.4|58.7|68.5 MB|atSW0WUyEvi3qig5Uosd7A==| 544 |
Response for full 545 | 546 | ```js 547 | { 548 | "statusCode": 302, 549 | "headers": {}, 550 | "isBase64Encoded": false, 551 | "body": "" 552 | } 553 | ``` 554 |
555 | 556 |
Response for connect 557 | 558 | ```js 559 | { 560 | "statusCode": 302, 561 | "headers": {}, 562 | "isBase64Encoded": false, 563 | "body": "" 564 | } 565 | ``` 566 |
567 | 568 |
Response for minimal 569 | 570 | ```js 571 | { 572 | "statusCode": 302, 573 | "headers": { 574 | "location": "/about" 575 | }, 576 | "isBase64Encoded": false, 577 | "body": "" 578 | } 579 | ``` 580 |
581 | 582 | ## Route `/nuxt-icon.png` 583 | ### Config _base_ 584 | |handler|load time|exec time|total time|memory usage|chksum| 585 | |---|---|---|---|---|---| 586 | |_full_|125.3|48|173.3|102 MB|oCShUmxG+SfF2/HJadRk6w==| 587 | |_connect_|34|43.1|77.1|69.1 MB|NdQL9Cs2MAVKCcGgGcc22A==| 588 | |_minimal_|28.8|40|68.7|69.2 MB|WOlebsjuEP8seruYOzY0Kw==| 589 |
Response for full 590 | 591 | ```js 592 | { 593 | "statusCode": 404, 594 | "headers": { 595 | "content-type": "image/gif", 596 | "cache-control": "no-cache, no-store, must-revalidate", 597 | "pragma": "no-cache", 598 | "expires": "0" 599 | }, 600 | "isBase64Encoded": false, 601 | "body": "GIF89a\u0001\u0000\u0001\u0000�\u0001\u0000���\u0000\u0000\u0000!�\u0004\u0001\n\u0000\u0001\u0000,\u0000\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0002\u0002L\u0001\u0000;" 602 | } 603 | ``` 604 |
605 | 606 |
Response for connect 607 | 608 | ```js 609 | { 610 | "statusCode": 404, 611 | "headers": { 612 | "content-type": "text/html; charset=utf-8", 613 | "accept-ranges": "none", 614 | "content-length": "2630" 615 | }, 616 | "isBase64Encoded": true, 617 | "body": "PCFkb2N0eXBlIGh0bWw+CjxodG1sIGRhdGEtbi1oZWFkLXNzcj4KICA8aGVhZCA+CiAgICA8dGl0bGU+VGhpcyBwYWdlIGNvdWxkIG5vdCBiZSBmb3VuZDwvdGl0bGU+PG1ldGEgZGF0YS1uLWhlYWQ9InNzciIgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCxpbml0aWFsLXNjYWxlPTEuMCxtaW5pbXVtLXNjYWxlPTEuMCxtYXhpbXVtLXNjYWxlPTEuMCx1c2VyLXNjYWxhYmxlPW5vIj48bGluayByZWw9InByZWxvYWQiIGhyZWY9Ii9fbnV4dC80NTZlZTE3YWU1MTE5NzdkMDRlZC5qcyIgYXM9InNjcmlwdCI+PGxpbmsgcmVsPSJwcmVsb2FkIiBocmVmPSIvX251eHQvZTgzOGQ1NmJlZDU1ZDA2NWJiMzAuanMiIGFzPSJzY3JpcHQiPjxsaW5rIHJlbD0icHJlbG9hZCIgaHJlZj0iL19udXh0LzY3ZDE3NGNlMmExODdhY2UwMjI2LmpzIiBhcz0ic2NyaXB0Ij48c3R5bGUgZGF0YS12dWUtc3NyLWlkPSIxMjRlMjg4YzowIj4ubnV4dC1wcm9ncmVzc3twb3NpdGlvbjpmaXhlZDt0b3A6MDtsZWZ0OjA7cmlnaHQ6MDtoZWlnaHQ6MnB4O3dpZHRoOjA7b3BhY2l0eToxO3RyYW5zaXRpb246d2lkdGggLjFzLG9wYWNpdHkgLjRzO2JhY2tncm91bmQtY29sb3I6IzAwMDt6LWluZGV4Ojk5OTk5OX0ubnV4dC1wcm9ncmVzcy5udXh0LXByb2dyZXNzLW5vdHJhbnNpdGlvbnt0cmFuc2l0aW9uOm5vbmV9Lm51eHQtcHJvZ3Jlc3MtZmFpbGVke2JhY2tncm91bmQtY29sb3I6cmVkfTwvc3R5bGU+PHN0eWxlIGRhdGEtdnVlLXNzci1pZD0iN2RiOWI3OGM6MCI+Ll9fbnV4dC1lcnJvci1wYWdle3BhZGRpbmc6MXJlbTtiYWNrZ3JvdW5kOiNmN2Y4ZmI7Y29sb3I6IzQ3NDk0ZTt0ZXh0LWFsaWduOmNlbnRlcjtkaXNwbGF5OmZsZXg7anVzdGlmeS1jb250ZW50OmNlbnRlcjthbGlnbi1pdGVtczpjZW50ZXI7ZmxleC1kaXJlY3Rpb246Y29sdW1uO2ZvbnQtZmFtaWx5OnNhbnMtc2VyaWY7Zm9udC13ZWlnaHQ6MTAwIWltcG9ydGFudDstbXMtdGV4dC1zaXplLWFkanVzdDoxMDAlOy13ZWJraXQtdGV4dC1zaXplLWFkanVzdDoxMDAlOy13ZWJraXQtZm9udC1zbW9vdGhpbmc6YW50aWFsaWFzZWQ7cG9zaXRpb246YWJzb2x1dGU7dG9wOjA7bGVmdDowO3JpZ2h0OjA7Ym90dG9tOjB9Ll9fbnV4dC1lcnJvci1wYWdlIC5lcnJvcnttYXgtd2lkdGg6NDUwcHh9Ll9fbnV4dC1lcnJvci1wYWdlIC50aXRsZXtmb250LXNpemU6MS41cmVtO21hcmdpbi10b3A6MTVweDtjb2xvcjojNDc0OTRlO21hcmdpbi1ib3R0b206OHB4fS5fX251eHQtZXJyb3ItcGFnZSAuZGVzY3JpcHRpb257Y29sb3I6IzdmODI4YjtsaW5lLWhlaWdodDoyMXB4O21hcmdpbi1ib3R0b206MTBweH0uX19udXh0LWVycm9yLXBhZ2UgYXtjb2xvcjojN2Y4MjhiIWltcG9ydGFudDt0ZXh0LWRlY29yYXRpb246bm9uZX0uX19udXh0LWVycm9yLXBhZ2UgLmxvZ297cG9zaXRpb246Zml4ZWQ7bGVmdDoxMnB4O2JvdHRvbToxMnB4fTwvc3R5bGU+CiAgPC9oZWFkPgogIDxib2R5ID4KICAgIDxkaXYgZGF0YS1zZXJ2ZXItcmVuZGVyZWQ9InRydWUiIGlkPSJfX251eHQiPjwhLS0tLT48ZGl2IGlkPSJfX2xheW91dCI+PGRpdj48ZGl2IGNsYXNzPSJfX251eHQtZXJyb3ItcGFnZSI+PGRpdiBjbGFzcz0iZXJyb3IiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iOTAiIGhlaWdodD0iOTAiIGZpbGw9IiNEQkUxRUMiIHZpZXdCb3g9IjAgMCA0OCA0OCI+PHBhdGggZD0iTTIyIDMwaDR2NGgtNHptMC0xNmg0djEyaC00em0xLjk5LTEwQzEyLjk0IDQgNCAxMi45NSA0IDI0czguOTQgMjAgMTkuOTkgMjBTNDQgMzUuMDUgNDQgMjQgMzUuMDQgNCAyMy45OSA0ek0yNCA0MGMtOC44NCAwLTE2LTcuMTYtMTYtMTZTMTUuMTYgOCAyNCA4czE2IDcuMTYgMTYgMTYtNy4xNiAxNi0xNiAxNnoiPjwvcGF0aD48L3N2Zz4gPGRpdiBjbGFzcz0idGl0bGUiPlRoaXMgcGFnZSBjb3VsZCBub3QgYmUgZm91bmQ8L2Rpdj4gPHAgY2xhc3M9ImRlc2NyaXB0aW9uIj48YSBocmVmPSIvIiBjbGFzcz0iZXJyb3ItbGluayBudXh0LWxpbmstYWN0aXZlIj5CYWNrIHRvIHRoZSBob21lIHBhZ2U8L2E+PC9wPiA8ZGl2IGNsYXNzPSJsb2dvIj48YSBocmVmPSJodHRwczovL251eHRqcy5vcmciIHRhcmdldD0iX2JsYW5rIiByZWw9Im5vb3BlbmVyIj5OdXh0LmpzPC9hPjwvZGl2PjwvZGl2PjwvZGl2PiA8Zm9vdGVyPjxzbWFsbD5TZXJ2ZWQgYnkgYSBudXh0LWxhbWJkYTwvc21hbGw+PC9mb290ZXI+PC9kaXY+PC9kaXY+PC9kaXY+PHNjcmlwdD53aW5kb3cuX19OVVhUX189e2xheW91dDoiZGVmYXVsdCIsZGF0YTpbXSxlcnJvcjp7c3RhdHVzQ29kZTo0MDQscGF0aDoiXHUwMDJGbnV4dC1pY29uLnBuZyIsbWVzc2FnZToiVGhpcyBwYWdlIGNvdWxkIG5vdCBiZSBmb3VuZCJ9LHNlcnZlclJlbmRlcmVkOnRydWV9Ozwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvNDU2ZWUxN2FlNTExOTc3ZDA0ZWQuanMiIGRlZmVyPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvZTgzOGQ1NmJlZDU1ZDA2NWJiMzAuanMiIGRlZmVyPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvNjdkMTc0Y2UyYTE4N2FjZTAyMjYuanMiIGRlZmVyPjwvc2NyaXB0PgogIDwvYm9keT4KPC9odG1sPgo=" 618 | } 619 | ``` 620 |
621 | 622 |
Response for minimal 623 | 624 | ```js 625 | { 626 | "statusCode": 200, 627 | "headers": { 628 | "content-type": "text/html; charset=utf-8", 629 | "accept-ranges": "none", 630 | "content-length": 2630 631 | }, 632 | "isBase64Encoded": false, 633 | "body": "\n\n \n This page could not be found\n \n \n
This page could not be found

Back to the home page

Served by a nuxt-lambda
\n \n\n" 634 | } 635 | ``` 636 |
637 | 638 | ### Config _with-compression_ 639 | |handler|load time|exec time|total time|memory usage|chksum| 640 | |---|---|---|---|---|---| 641 | |_full_|122.7|57.6|180.3|109 MB|oCShUmxG+SfF2/HJadRk6w==| 642 | |_connect_|41.8|43.4|85.2|71.9 MB|bb96X595NQ+9Ky1Yfxn0oQ==| 643 | |_minimal_|29.6|41.5|71|68.8 MB|kPG6GRYEwn0+8RGJUMHVaw==| 644 |
Response for full 645 | 646 | ```js 647 | { 648 | "statusCode": 404, 649 | "headers": { 650 | "content-type": "image/gif", 651 | "cache-control": "no-cache, no-store, must-revalidate", 652 | "pragma": "no-cache", 653 | "expires": "0" 654 | }, 655 | "isBase64Encoded": false, 656 | "body": "GIF89a\u0001\u0000\u0001\u0000�\u0001\u0000���\u0000\u0000\u0000!�\u0004\u0001\n\u0000\u0001\u0000,\u0000\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0002\u0002L\u0001\u0000;" 657 | } 658 | ``` 659 |
660 | 661 |
Response for connect 662 | 663 | ```js 664 | { 665 | "statusCode": 404, 666 | "headers": { 667 | "content-type": "text/html; charset=utf-8", 668 | "accept-ranges": "none", 669 | "vary": "Accept-Encoding", 670 | "content-encoding": "gzip" 671 | }, 672 | "isBase64Encoded": true, 673 | "body": "H4sIAAAAAAAAA5VWS4/bOAy+91eoKvYW+TXOxLGTHNrt3tpD2wUW2F0MZItO1LEtQ5LzmGD++1JyUiQznbabGDZFihRFfqS0eC1UZQ89kI1tm9WrhfsQwS1nHdsAF8wYvXpFyMINiKOQttI2sPqykYb0fA2kUkMjSKcsKYHUaujEIhznLFqw/NLekqJBSjrewpJuJex6pS1FC52Fzi7pTgq7WQrYygqYH0xkJ63kDTMVb2AZB9GkRVY7tJccvn/CGQxoP+QlcjpFV4tGdvdEQ7OkPb4VF5RsNNRLGt51w96G6fQWIJ5xmMbxfDYTUQoi+Goo4QbdrrTs7S9YgewmE9PbEsR0KqLbaVneRP/fyu1MxLO0goTH2YxXECXJ7XMrxh4aGMO7HcDlikkMcZykkGRZlUd0FThzrNdqrcGYY68MRlN1eS33IAqr+jwqGqgtfrRcb9x3A55I+n3hM4As1fNK2kMeF1bz7mTCC0kQm8lJTILUFCWv7tfagYBVqlE6fxNFUfHAZCdgn8/97/Haq+sRQyB9W+R4sV6nOniiyWouGxDHZ4tqEI+L0MfnxTDNRDkvZ6cw3fm4M9BaaeZQfey5ELJb57GG9mJX+Zt6Vmd1WZx2l87SeQqFBdTmjVx3eYVIBl0IafqGH/K6gX3xdTBW1gd2Avp5jldg0kJrziw3nQmpofKbxlWGtitq1MPNtrI55AYjwhDesh7ZuzFfcRS9lq2rJ97ZgrWGeZ+MfADGhXPATfmtwPnlvbQ/kXrLplXKblwQ0CTWoOQGQfMNQ7w06J2F78OoVNaqNo8en8eWBJ4+YtmORZ6n06jff3embyTH0R10No+DqUtIy/VadsytHE8RqdfZOElPLmQvmBYwVpLD2UkfU5tkZYHlCexcBzGavzYYv+AsvzZzkQ4fbAGV0vwCy9/xqVFr9bRKfWBjV4/n9ZH+Bm/XnUPXWz1VKnE492khtyPqESxbbIcasAaxMpbU6gEocUUwuoC95DXD38rrjHzErhqcBFkjv2q4MWeVC6/pldjzXXParsm+bTpkbazt8zDc7XbB7iZQeh0m2BRCnEHJ2PHpPMIO6AM+0rVssDm++f3t+/j9O0rcUfFW7Zc0IhFJM3xwhZ5j/0FfPyQJuYk26TbdsPShjVh8i4M48aM4mM9ZHL2Lk2CeEvd31BS/SWoyx0siEs9xFhKf05TcTIMIxU7uaaeS3Dh5+vABeWlUsSzIUuLWYbMAX/75HE+RJpnTywxSTkT8w04k8+8HdD10vuMHQ7Ail9HzcKc/OVxdRsiiP+tc4BhN8/NJQq8ywvyR4zPnKMaxv2xxpbfY2YhVxG7wEqBa8KsuQu58vHbNQfPCvkuqwaw6k1+NyyolFssEMIN3ZcO7ezqecJ1SPXSAmPiIU/EUG617WF28yaLGbgMakdPypll9dqAVpDwQfnKbt6XgGDMvXoTn6c8sLcZwrHZ45qgdVtnHP//6cne3PI6QzjFgNR8aSyeuOvK//534COVHY7kdzDslIE+jdOJylNN/hihK/vAeSGzfQd+t6aTFwwfjlNMfJYo+TsbK+3QqvNzV3WOBWxgdPDlKjK5+fgtBp/1uf6D74t3jF3RfvHE80fXtxnUZvC2G463xP9Kayp5GCgAA" 674 | } 675 | ``` 676 |
677 | 678 |
Response for minimal 679 | 680 | ```js 681 | { 682 | "statusCode": 200, 683 | "headers": { 684 | "content-type": "text/html; charset=utf-8", 685 | "accept-ranges": "none", 686 | "vary": "Accept-Encoding", 687 | "content-encoding": "gzip" 688 | }, 689 | "isBase64Encoded": true, 690 | "body": "H4sIAAAAAAAAA5VWS4/bOAy+91eoKvYW+TXOxLGTHNrt3tpD2wUW2F0MZItO1LEtQ5LzmGD++1JyUiQznbabGDZFihRFfqS0eC1UZQ89kI1tm9WrhfsQwS1nHdsAF8wYvXpFyMINiKOQttI2sPqykYb0fA2kUkMjSKcsKYHUaujEIhznLFqw/NLekqJBSjrewpJuJex6pS1FC52Fzi7pTgq7WQrYygqYH0xkJ63kDTMVb2AZB9GkRVY7tJccvn/CGQxoP+QlcjpFV4tGdvdEQ7OkPb4VF5RsNNRLGt51w96G6fQWIJ5xmMbxfDYTUQoi+Goo4QbdrrTs7S9YgewmE9PbEsR0KqLbaVneRP/fyu1MxLO0goTH2YxXECXJ7XMrxh4aGMO7HcDlikkMcZykkGRZlUd0FThzrNdqrcGYY68MRlN1eS33IAqr+jwqGqgtfrRcb9x3A55I+n3hM4As1fNK2kMeF1bz7mTCC0kQm8lJTILUFCWv7tfagYBVqlE6fxNFUfHAZCdgn8/97/Haq+sRQyB9W+R4sV6nOniiyWouGxDHZ4tqEI+L0MfnxTDNRDkvZ6cw3fm4M9BaaeZQfey5ELJb57GG9mJX+Zt6Vmd1WZx2l87SeQqFBdTmjVx3eYVIBl0IafqGH/K6gX3xdTBW1gd2Avp5jldg0kJrziw3nQmpofKbxlWGtitq1MPNtrI55AYjwhDesh7ZuzFfcRS9lq2rJ97ZgrWGeZ+MfADGhXPATfmtwPnlvbQ/kXrLplXKblwQ0CTWoOQGQfMNQ7w06J2F78OoVNaqNo8en8eWBJ4+YtmORZ6n06jff3embyTH0R10No+DqUtIy/VadsytHE8RqdfZOElPLmQvmBYwVpLD2UkfU5tkZYHlCexcBzGavzYYv+AsvzZzkQ4fbAGV0vwCy9/xqVFr9bRKfWBjV4/n9ZH+Bm/XnUPXWz1VKnE492khtyPqESxbbIcasAaxMpbU6gEocUUwuoC95DXD38rrjHzErhqcBFkjv2q4MWeVC6/pldjzXXParsm+bTpkbazt8zDc7XbB7iZQeh0m2BRCnEHJ2PHpPMIO6AM+0rVssDm++f3t+/j9O0rcUfFW7Zc0IhFJM3xwhZ5j/0FfPyQJuYk26TbdsPShjVh8i4M48aM4mM9ZHL2Lk2CeEvd31BS/SWoyx0siEs9xFhKf05TcTIMIxU7uaaeS3Dh5+vABeWlUsSzIUuLWYbMAX/75HE+RJpnTywxSTkT8w04k8+8HdD10vuMHQ7Ail9HzcKc/OVxdRsiiP+tc4BhN8/NJQq8ywvyR4zPnKMaxv2xxpbfY2YhVxG7wEqBa8KsuQu58vHbNQfPCvkuqwaw6k1+NyyolFssEMIN3ZcO7ezqecJ1SPXSAmPiIU/EUG617WF28yaLGbgMakdPypll9dqAVpDwQfnKbt6XgGDMvXoTn6c8sLcZwrHZ45qgdVtnHP//6cne3PI6QzjFgNR8aSyeuOvK//534COVHY7kdzDslIE+jdOJylNN/hihK/vAeSGzfQd+t6aTFwwfjlNMfJYo+TsbK+3QqvNzV3WOBWxgdPDlKjK5+fgtBp/1uf6D74t3jF3RfvHE80fXtxnUZvC2G463xP9Kayp5GCgAA" 691 | } 692 | ``` 693 |
694 | 695 | ### Config _with-etag_ 696 | |handler|load time|exec time|total time|memory usage|chksum| 697 | |---|---|---|---|---|---| 698 | |_full_|122.1|48.4|170.4|108 MB|oCShUmxG+SfF2/HJadRk6w==| 699 | |_connect_|34.2|41.9|76.1|69.3 MB|NdQL9Cs2MAVKCcGgGcc22A==| 700 | |_minimal_|29|39.5|68.5|68.7 MB|WOlebsjuEP8seruYOzY0Kw==| 701 |
Response for full 702 | 703 | ```js 704 | { 705 | "statusCode": 404, 706 | "headers": { 707 | "content-type": "image/gif", 708 | "cache-control": "no-cache, no-store, must-revalidate", 709 | "pragma": "no-cache", 710 | "expires": "0" 711 | }, 712 | "isBase64Encoded": false, 713 | "body": "GIF89a\u0001\u0000\u0001\u0000�\u0001\u0000���\u0000\u0000\u0000!�\u0004\u0001\n\u0000\u0001\u0000,\u0000\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0002\u0002L\u0001\u0000;" 714 | } 715 | ``` 716 |
717 | 718 |
Response for connect 719 | 720 | ```js 721 | { 722 | "statusCode": 404, 723 | "headers": { 724 | "content-type": "text/html; charset=utf-8", 725 | "accept-ranges": "none", 726 | "content-length": "2630" 727 | }, 728 | "isBase64Encoded": true, 729 | "body": "PCFkb2N0eXBlIGh0bWw+CjxodG1sIGRhdGEtbi1oZWFkLXNzcj4KICA8aGVhZCA+CiAgICA8dGl0bGU+VGhpcyBwYWdlIGNvdWxkIG5vdCBiZSBmb3VuZDwvdGl0bGU+PG1ldGEgZGF0YS1uLWhlYWQ9InNzciIgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCxpbml0aWFsLXNjYWxlPTEuMCxtaW5pbXVtLXNjYWxlPTEuMCxtYXhpbXVtLXNjYWxlPTEuMCx1c2VyLXNjYWxhYmxlPW5vIj48bGluayByZWw9InByZWxvYWQiIGhyZWY9Ii9fbnV4dC80NTZlZTE3YWU1MTE5NzdkMDRlZC5qcyIgYXM9InNjcmlwdCI+PGxpbmsgcmVsPSJwcmVsb2FkIiBocmVmPSIvX251eHQvZTgzOGQ1NmJlZDU1ZDA2NWJiMzAuanMiIGFzPSJzY3JpcHQiPjxsaW5rIHJlbD0icHJlbG9hZCIgaHJlZj0iL19udXh0LzY3ZDE3NGNlMmExODdhY2UwMjI2LmpzIiBhcz0ic2NyaXB0Ij48c3R5bGUgZGF0YS12dWUtc3NyLWlkPSIxMjRlMjg4YzowIj4ubnV4dC1wcm9ncmVzc3twb3NpdGlvbjpmaXhlZDt0b3A6MDtsZWZ0OjA7cmlnaHQ6MDtoZWlnaHQ6MnB4O3dpZHRoOjA7b3BhY2l0eToxO3RyYW5zaXRpb246d2lkdGggLjFzLG9wYWNpdHkgLjRzO2JhY2tncm91bmQtY29sb3I6IzAwMDt6LWluZGV4Ojk5OTk5OX0ubnV4dC1wcm9ncmVzcy5udXh0LXByb2dyZXNzLW5vdHJhbnNpdGlvbnt0cmFuc2l0aW9uOm5vbmV9Lm51eHQtcHJvZ3Jlc3MtZmFpbGVke2JhY2tncm91bmQtY29sb3I6cmVkfTwvc3R5bGU+PHN0eWxlIGRhdGEtdnVlLXNzci1pZD0iN2RiOWI3OGM6MCI+Ll9fbnV4dC1lcnJvci1wYWdle3BhZGRpbmc6MXJlbTtiYWNrZ3JvdW5kOiNmN2Y4ZmI7Y29sb3I6IzQ3NDk0ZTt0ZXh0LWFsaWduOmNlbnRlcjtkaXNwbGF5OmZsZXg7anVzdGlmeS1jb250ZW50OmNlbnRlcjthbGlnbi1pdGVtczpjZW50ZXI7ZmxleC1kaXJlY3Rpb246Y29sdW1uO2ZvbnQtZmFtaWx5OnNhbnMtc2VyaWY7Zm9udC13ZWlnaHQ6MTAwIWltcG9ydGFudDstbXMtdGV4dC1zaXplLWFkanVzdDoxMDAlOy13ZWJraXQtdGV4dC1zaXplLWFkanVzdDoxMDAlOy13ZWJraXQtZm9udC1zbW9vdGhpbmc6YW50aWFsaWFzZWQ7cG9zaXRpb246YWJzb2x1dGU7dG9wOjA7bGVmdDowO3JpZ2h0OjA7Ym90dG9tOjB9Ll9fbnV4dC1lcnJvci1wYWdlIC5lcnJvcnttYXgtd2lkdGg6NDUwcHh9Ll9fbnV4dC1lcnJvci1wYWdlIC50aXRsZXtmb250LXNpemU6MS41cmVtO21hcmdpbi10b3A6MTVweDtjb2xvcjojNDc0OTRlO21hcmdpbi1ib3R0b206OHB4fS5fX251eHQtZXJyb3ItcGFnZSAuZGVzY3JpcHRpb257Y29sb3I6IzdmODI4YjtsaW5lLWhlaWdodDoyMXB4O21hcmdpbi1ib3R0b206MTBweH0uX19udXh0LWVycm9yLXBhZ2UgYXtjb2xvcjojN2Y4MjhiIWltcG9ydGFudDt0ZXh0LWRlY29yYXRpb246bm9uZX0uX19udXh0LWVycm9yLXBhZ2UgLmxvZ297cG9zaXRpb246Zml4ZWQ7bGVmdDoxMnB4O2JvdHRvbToxMnB4fTwvc3R5bGU+CiAgPC9oZWFkPgogIDxib2R5ID4KICAgIDxkaXYgZGF0YS1zZXJ2ZXItcmVuZGVyZWQ9InRydWUiIGlkPSJfX251eHQiPjwhLS0tLT48ZGl2IGlkPSJfX2xheW91dCI+PGRpdj48ZGl2IGNsYXNzPSJfX251eHQtZXJyb3ItcGFnZSI+PGRpdiBjbGFzcz0iZXJyb3IiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iOTAiIGhlaWdodD0iOTAiIGZpbGw9IiNEQkUxRUMiIHZpZXdCb3g9IjAgMCA0OCA0OCI+PHBhdGggZD0iTTIyIDMwaDR2NGgtNHptMC0xNmg0djEyaC00em0xLjk5LTEwQzEyLjk0IDQgNCAxMi45NSA0IDI0czguOTQgMjAgMTkuOTkgMjBTNDQgMzUuMDUgNDQgMjQgMzUuMDQgNCAyMy45OSA0ek0yNCA0MGMtOC44NCAwLTE2LTcuMTYtMTYtMTZTMTUuMTYgOCAyNCA4czE2IDcuMTYgMTYgMTYtNy4xNiAxNi0xNiAxNnoiPjwvcGF0aD48L3N2Zz4gPGRpdiBjbGFzcz0idGl0bGUiPlRoaXMgcGFnZSBjb3VsZCBub3QgYmUgZm91bmQ8L2Rpdj4gPHAgY2xhc3M9ImRlc2NyaXB0aW9uIj48YSBocmVmPSIvIiBjbGFzcz0iZXJyb3ItbGluayBudXh0LWxpbmstYWN0aXZlIj5CYWNrIHRvIHRoZSBob21lIHBhZ2U8L2E+PC9wPiA8ZGl2IGNsYXNzPSJsb2dvIj48YSBocmVmPSJodHRwczovL251eHRqcy5vcmciIHRhcmdldD0iX2JsYW5rIiByZWw9Im5vb3BlbmVyIj5OdXh0LmpzPC9hPjwvZGl2PjwvZGl2PjwvZGl2PiA8Zm9vdGVyPjxzbWFsbD5TZXJ2ZWQgYnkgYSBudXh0LWxhbWJkYTwvc21hbGw+PC9mb290ZXI+PC9kaXY+PC9kaXY+PC9kaXY+PHNjcmlwdD53aW5kb3cuX19OVVhUX189e2xheW91dDoiZGVmYXVsdCIsZGF0YTpbXSxlcnJvcjp7c3RhdHVzQ29kZTo0MDQscGF0aDoiXHUwMDJGbnV4dC1pY29uLnBuZyIsbWVzc2FnZToiVGhpcyBwYWdlIGNvdWxkIG5vdCBiZSBmb3VuZCJ9LHNlcnZlclJlbmRlcmVkOnRydWV9Ozwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvNDU2ZWUxN2FlNTExOTc3ZDA0ZWQuanMiIGRlZmVyPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvZTgzOGQ1NmJlZDU1ZDA2NWJiMzAuanMiIGRlZmVyPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX251eHQvNjdkMTc0Y2UyYTE4N2FjZTAyMjYuanMiIGRlZmVyPjwvc2NyaXB0PgogIDwvYm9keT4KPC9odG1sPgo=" 730 | } 731 | ``` 732 |
733 | 734 |
Response for minimal 735 | 736 | ```js 737 | { 738 | "statusCode": 200, 739 | "headers": { 740 | "content-type": "text/html; charset=utf-8", 741 | "accept-ranges": "none", 742 | "content-length": 2630 743 | }, 744 | "isBase64Encoded": false, 745 | "body": "\n\n \n This page could not be found\n \n \n
This page could not be found

Back to the home page

Served by a nuxt-lambda
\n \n\n" 746 | } 747 | ``` 748 |
749 | 750 | ### Config _with-static_ 751 | |handler|load time|exec time|total time|memory usage|chksum| 752 | |---|---|---|---|---|---| 753 | |_full_|123.2|50.1|173.3|108 MB|Gvr3GK7TiagcVisLZJpVkw==| 754 | |_connect_|39.1|11.6|50.7|69.3 MB|sgtrRMARd8gLXpvMh5W4Bg==| 755 | |_minimal_|29.3|39.5|68.7|68.5 MB|WOlebsjuEP8seruYOzY0Kw==| 756 |
Response for full 757 | 758 | ```js 759 | blob_with_size_30359 760 | ``` 761 |
762 | 763 |
Response for connect 764 | 765 | ```js 766 | blob_with_size_30359 767 | ``` 768 |
769 | 770 |
Response for minimal 771 | 772 | ```js 773 | { 774 | "statusCode": 200, 775 | "headers": { 776 | "content-type": "text/html; charset=utf-8", 777 | "accept-ranges": "none", 778 | "content-length": 2630 779 | }, 780 | "isBase64Encoded": false, 781 | "body": "\n\n \n This page could not be found\n \n \n
This page could not be found

Back to the home page

Served by a nuxt-lambda
\n \n\n" 782 | } 783 | ``` 784 |
785 | 786 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### 0.5.8 (2022-02-09) 6 | 7 | 8 | ### Features 9 | 10 | * add decode option to test-lambda to return plain html ([c94eee0](https://github.com/pimlie/nuxt-lambda/commit/c94eee0d434aa5d72d91a74819b4477cc79d388f)) 11 | * add excludeClientFiles option to exclude assets from the nuxt lambda ([9c5494e](https://github.com/pimlie/nuxt-lambda/commit/9c5494e3232b924a97f8bad1a752dce777b3134c)) 12 | * add support for (optionally) serving static files ([15076c1](https://github.com/pimlie/nuxt-lambda/commit/15076c1a84958b2d1e92e330e94275153169620d)) 13 | * add support for excludeFiles config ([6ada5de](https://github.com/pimlie/nuxt-lambda/commit/6ada5de4cd50ac61856761109bd51d3d0d5c275a)) 14 | * add support for pssing options to zisi ([0847895](https://github.com/pimlie/nuxt-lambda/commit/0847895a66894cfb6599e6fc26b4e1b873ad922d)) 15 | * add support for rendering error page on internal server error ([8a33241](https://github.com/pimlie/nuxt-lambda/commit/8a33241af126a13fb5dcedf0ccc5fb060479209b)) 16 | * add support to build full lambda (optimize is default) ([2f59fd5](https://github.com/pimlie/nuxt-lambda/commit/2f59fd540de31f51122c5178f10a97cf3f2655f6)) 17 | * improve modern build support ([c36ac08](https://github.com/pimlie/nuxt-lambda/commit/c36ac082b16bfff2d4e82c9ca2f3ca64c884c4e1)) 18 | * refactoring, better support for different handlers ([ba3bcc2](https://github.com/pimlie/nuxt-lambda/commit/ba3bcc25733ac3803cb897cefb12f95ce6bdaac7)) 19 | * support separate build/dir dirs ([7caffda](https://github.com/pimlie/nuxt-lambda/commit/7caffdae9992f1dc631667095cf5b1bd94e4530d)) 20 | 21 | 22 | ### Bug Fixes 23 | 24 | * add better stub for consola ([73629b3](https://github.com/pimlie/nuxt-lambda/commit/73629b34497a13215402026d4d1d32f696684b0a)) 25 | * add bracket syntax for replacing consola ([36c270a](https://github.com/pimlie/nuxt-lambda/commit/36c270ab499124102727a8fadc46d32591aa628b)) 26 | * allow setting handler in nuxt.options again ([50c40c5](https://github.com/pimlie/nuxt-lambda/commit/50c40c5d2aee8372f02a1299485dea7eec982f30)) 27 | * an unknown config issue ([dd790c4](https://github.com/pimlie/nuxt-lambda/commit/dd790c456fdf250a0b5cdb99d21050a811f1798b)) 28 | * by default always add files to zip (duh) ([30c08ac](https://github.com/pimlie/nuxt-lambda/commit/30c08aca7426feebd013b700a9c6dca85d5c6d3a)) 29 | * chore update dependencies ([3237a69](https://github.com/pimlie/nuxt-lambda/commit/3237a690624e5a6b6935a6397ead336ece49c223)) 30 | * exclude is not add to zip ([f2f2993](https://github.com/pimlie/nuxt-lambda/commit/f2f2993827b701989039e83d35f2249662503c24)) 31 | * nuxt config changes ([281d259](https://github.com/pimlie/nuxt-lambda/commit/281d259124304c9b79fc7933a81fa452e3c15ab2)) 32 | * remove unnecessary replace ([9055d99](https://github.com/pimlie/nuxt-lambda/commit/9055d99b52d2130f399ab6b1ce073579f9790415)) 33 | * **renderer:** provide `nuxt.options` ([3e90db7](https://github.com/pimlie/nuxt-lambda/commit/3e90db7d13e2402b8da2ae52061a8dae53115da7)) 34 | * **renderer:** provide `nuxt.options` ([1fccb1c](https://github.com/pimlie/nuxt-lambda/commit/1fccb1c1e91ecf3a358da464bc26b9aa4bf99ec0)) 35 | * replace routePath in nuxt context ([2b8bdd2](https://github.com/pimlie/nuxt-lambda/commit/2b8bdd27d23c667020214855fdb9dd9c10650eee)) 36 | * revert build script changes ([477d93f](https://github.com/pimlie/nuxt-lambda/commit/477d93f058c30543a90a4ff21ab31da503cec121)) 37 | * root/build dir issue ([a0dc0d6](https://github.com/pimlie/nuxt-lambda/commit/a0dc0d6a3adb70db92f6a34d71e68e992437cbf4)) 38 | * serialize/eval shouldPrefetch/Preload functions ([2273537](https://github.com/pimlie/nuxt-lambda/commit/2273537b9cb006cec1a01797400946084694f688)) 39 | * support nuxt v2.12 ([515b2a0](https://github.com/pimlie/nuxt-lambda/commit/515b2a0746870d439437f1738ce951b497dc6f50)) 40 | * upgrade to @babel/eslint-parser ([966133b](https://github.com/pimlie/nuxt-lambda/commit/966133b537fefc194121d9ca870a9b78040dd5fd)) 41 | 42 | ### [0.5.7](https://github.com/pimlie/nuxt-lambda/compare/v0.5.6...v0.5.7) (2021-09-28) 43 | 44 | 45 | ### Features 46 | 47 | * improve modern build support ([c36ac08](https://github.com/pimlie/nuxt-lambda/commit/c36ac082b16bfff2d4e82c9ca2f3ca64c884c4e1)) 48 | 49 | ### [0.5.6](https://github.com/pimlie/nuxt-lambda/compare/v0.5.5...v0.5.6) (2021-06-12) 50 | 51 | 52 | ### Features 53 | 54 | * add decode option to test-lambda to return plain html ([c94eee0](https://github.com/pimlie/nuxt-lambda/commit/c94eee0d434aa5d72d91a74819b4477cc79d388f)) 55 | 56 | 57 | ### Bug Fixes 58 | 59 | * replace routePath in nuxt context ([2b8bdd2](https://github.com/pimlie/nuxt-lambda/commit/2b8bdd27d23c667020214855fdb9dd9c10650eee)) 60 | 61 | ### [0.5.5](https://github.com/pimlie/nuxt-lambda/compare/v0.5.4...v0.5.5) (2021-06-12) 62 | 63 | 64 | ### Features 65 | 66 | * add support for rendering error page on internal server error ([8a33241](https://github.com/pimlie/nuxt-lambda/commit/8a33241af126a13fb5dcedf0ccc5fb060479209b)) 67 | 68 | 69 | ### Bug Fixes 70 | 71 | * upgrade to @babel/eslint-parser ([966133b](https://github.com/pimlie/nuxt-lambda/commit/966133b537fefc194121d9ca870a9b78040dd5fd)) 72 | 73 | ### [0.5.4](https://github.com/pimlie/nuxt-lambda/compare/v0.5.3...v0.5.4) (2021-02-25) 74 | 75 | 76 | ### Bug Fixes 77 | 78 | * chore update dependencies ([3237a69](https://github.com/pimlie/nuxt-lambda/commit/3237a690624e5a6b6935a6397ead336ece49c223)) 79 | 80 | ### [0.5.3](https://github.com/pimlie/nuxt-lambda/compare/v0.5.2...v0.5.3) (2020-09-23) 81 | 82 | 83 | ### Bug Fixes 84 | 85 | * exclude is not add to zip ([f2f2993](https://github.com/pimlie/nuxt-lambda/commit/f2f2993827b701989039e83d35f2249662503c24)) 86 | 87 | ### [0.5.2](https://github.com/pimlie/nuxt-lambda/compare/v0.5.1...v0.5.2) (2020-09-22) 88 | 89 | 90 | ### Features 91 | 92 | * add support for excludeFiles config ([6ada5de](https://github.com/pimlie/nuxt-lambda/commit/6ada5de4cd50ac61856761109bd51d3d0d5c275a)) 93 | 94 | ### [0.5.1](https://github.com/pimlie/nuxt-lambda/compare/v0.5.0...v0.5.1) (2020-09-16) 95 | 96 | 97 | ### Bug Fixes 98 | 99 | * by default always add files to zip (duh) ([30c08ac](https://github.com/pimlie/nuxt-lambda/commit/30c08aca7426feebd013b700a9c6dca85d5c6d3a)) 100 | 101 | ## [0.5.0](https://github.com/pimlie/nuxt-lambda/compare/v0.4.4...v0.5.0) (2020-09-16) 102 | 103 | 104 | ### Features 105 | 106 | * add excludeClientFiles option to exclude assets from the nuxt lambda ([9c5494e](https://github.com/pimlie/nuxt-lambda/commit/9c5494e3232b924a97f8bad1a752dce777b3134c)) 107 | 108 | ### [0.4.4](https://github.com/pimlie/nuxt-lambda/compare/v0.4.3...v0.4.4) (2020-09-03) 109 | 110 | 111 | ### Bug Fixes 112 | 113 | * add better stub for consola ([73629b3](https://github.com/pimlie/nuxt-lambda/commit/73629b34497a13215402026d4d1d32f696684b0a)) 114 | * **renderer:** provide `nuxt.options` ([3e90db7](https://github.com/pimlie/nuxt-lambda/commit/3e90db7d13e2402b8da2ae52061a8dae53115da7)) 115 | * **renderer:** provide `nuxt.options` ([1fccb1c](https://github.com/pimlie/nuxt-lambda/commit/1fccb1c1e91ecf3a358da464bc26b9aa4bf99ec0)) 116 | 117 | ### [0.4.3](https://github.com/pimlie/nuxt-lambda/compare/v0.4.2...v0.4.3) (2020-05-04) 118 | 119 | 120 | ### Bug Fixes 121 | 122 | * add bracket syntax for replacing consola ([36c270a](https://github.com/pimlie/nuxt-lambda/commit/36c270ab499124102727a8fadc46d32591aa628b)) 123 | 124 | ### [0.4.2](https://github.com/pimlie/nuxt-lambda/compare/v0.4.1...v0.4.2) (2020-04-06) 125 | 126 | 127 | ### Bug Fixes 128 | 129 | * support nuxt v2.12 ([515b2a0](https://github.com/pimlie/nuxt-lambda/commit/515b2a0746870d439437f1738ce951b497dc6f50)) 130 | 131 | ### [0.4.1](https://github.com/pimlie/nuxt-lambda/compare/v0.4.0...v0.4.1) (2020-03-24) 132 | 133 | 134 | ### Bug Fixes 135 | 136 | * remove unnecessary replace ([9055d99](https://github.com/pimlie/nuxt-lambda/commit/9055d99b52d2130f399ab6b1ce073579f9790415)) 137 | 138 | ## [0.4.0](https://github.com/pimlie/nuxt-lambda/compare/v0.3.0...v0.4.0) (2020-02-27) 139 | 140 | 141 | ### Features 142 | 143 | * add support for (optionally) serving static files ([15076c1](https://github.com/pimlie/nuxt-lambda/commit/15076c1a84958b2d1e92e330e94275153169620d)) 144 | 145 | 146 | ### Bug Fixes 147 | 148 | * allow setting handler in nuxt.options again ([50c40c5](https://github.com/pimlie/nuxt-lambda/commit/50c40c5d2aee8372f02a1299485dea7eec982f30)) 149 | 150 | ## [0.3.0](https://github.com/pimlie/nuxt-lambda/compare/v0.2.2...v0.3.0) (2020-02-16) 151 | 152 | 153 | ### Features 154 | 155 | * refactoring, better support for different handlers ([ba3bcc2](https://github.com/pimlie/nuxt-lambda/commit/ba3bcc25733ac3803cb897cefb12f95ce6bdaac7)) 156 | 157 | 158 | ### Bug Fixes 159 | 160 | * revert build script changes ([477d93f](https://github.com/pimlie/nuxt-lambda/commit/477d93f058c30543a90a4ff21ab31da503cec121)) 161 | * serialize/eval shouldPrefetch/Preload functions ([2273537](https://github.com/pimlie/nuxt-lambda/commit/2273537b9cb006cec1a01797400946084694f688)) 162 | 163 | ### [0.2.2](https://github.com/pimlie/nuxt-lambda/compare/v0.2.1...v0.2.2) (2020-02-03) 164 | 165 | 166 | ### Bug Fixes 167 | 168 | * an unknown config issue ([dd790c4](https://github.com/pimlie/nuxt-lambda/commit/dd790c456fdf250a0b5cdb99d21050a811f1798b)) 169 | 170 | ### [0.2.1](https://github.com/pimlie/nuxt-lambda/compare/v0.2.0...v0.2.1) (2020-02-03) 171 | 172 | 173 | ### Features 174 | 175 | * support separate build/dir dirs ([7caffda](https://github.com/pimlie/nuxt-lambda/commit/7caffdae9992f1dc631667095cf5b1bd94e4530d)) 176 | 177 | ## [0.2.0](https://github.com/pimlie/nuxt-lambda/compare/v0.1.0...v0.2.0) (2020-02-02) 178 | 179 | 180 | ### Features 181 | 182 | * add support to build full lambda (optimize is default) ([2f59fd5](https://github.com/pimlie/nuxt-lambda/commit/2f59fd540de31f51122c5178f10a97cf3f2655f6)) 183 | 184 | ## 0.1.0 (2020-02-02) 185 | 186 | 187 | ### Bug Fixes 188 | 189 | * root/build dir issue ([a0dc0d6](https://github.com/pimlie/nuxt-lambda/commit/a0dc0d6a3adb70db92f6a34d71e68e992437cbf4)) 190 | 191 | ## 0.1.0 (2020-02-02) 192 | 193 | 194 | ### Bug Fixes 195 | 196 | * root/build dir issue ([a0dc0d6](https://github.com/pimlie/nuxt-lambda/commit/a0dc0d6a3adb70db92f6a34d71e68e992437cbf4)) 197 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) pimlie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nuxt command to create a Nuxt.js lambda (beta) 2 | [![npm version][npm-version-src]][npm-version-href] 3 | [![npm downloads][npm-downloads-src]][npm-downloads-href] 4 | [![License][license-src]][license-href] 5 | 6 | ### :warning: Optimized build by default with limited Nuxt.js functionality 7 | 8 | > You can enable the full Nuxt.js handler by setting `lambda.handler: 'full'` in your nuxt.config 9 | 10 | This command will by default create a lambda for Nuxt.js SSR that is optimized to run serverless. That means most non-essential Nuxt.js SSR features have been stripped out. 11 | 12 | - No connect server (it uses @nuxt/vue-renderer directly instead of going through @nuxt/server) 13 | - No support for runtime modules (only buildModules) 14 | - No support for server middlewares (although we might be able to add support for this at a later time) 15 | - No support for serving static files (there is optional support for the connect handler) 16 | - No support for hooks 17 | 18 | ### Possible tunables 19 | 20 | - turn off compression `render.compressor: false` 21 | - turn off etags `render.etag: false` 22 | 23 | ## How to use 24 | 25 | #### Install 26 | ```js 27 | $ yarn add nuxt-lambda 28 | ``` 29 | 30 | #### Configure (optional) 31 | 32 | See [Options](#options) below 33 | 34 | #### Build & test 35 | 36 | See [Commands](#commands) 37 | 38 | ## Commands 39 | 40 | ### nuxt-lambda 41 | 42 | The command to build a lambda. See `-h` for all available options (not all are supported). 43 | 44 | It will by default create a `.lambda` dir in your rootDir containing the intermediates and a `dist` folder with the zip file you need to upload 45 | 46 | ```js 47 | $ yarn nuxt-lambda [-c nuxt.config.js] 48 | 49 | // eg: yarn nuxt-lambda src 50 | ``` 51 | 52 | - `--handler` 53 | 54 | Same as `options.lambda.handler`, see [Options](#options). Used to quickly override the handler you want to use 55 | 56 | - `--static` 57 | 58 | Same as `options.lambda.static`, see [Options](#options). Used to quickly override whether to enable support for serving static files or not 59 | 60 | - `--no-optimize` 61 | 62 | _Not supported yet_ 63 | 64 | - `--no-nuxt` 65 | 66 | Build a lambda without re-building your Nuxt.js project. 67 | 68 | - `--no-lambda` 69 | 70 | _Not supported yet_ 71 | 72 | - `--no-zip` 73 | 74 | _Not supported yet_ 75 | 76 | ### test-lambda 77 | 78 | The command to test a lambda. See `-h` for available options. 79 | 80 | Accepts either a path to a nuxt rootDir or a path to a packed lambda zip file. 81 | 82 | ```js 83 | $ yarn test-lambda 84 | 85 | // eg: yarn test-lambda src /about 86 | // eg: yarn test-lambda /about 87 | ``` 88 | 89 | - `-p`, `--persistent` 90 | 91 | Normally when running test-lambda the lambda is unzipped in a temporary directory which is cleaned-up when the lambda has finished. Passing the persistent option unpacks the lambda in the `options.lambda.distDir` folder instead only when it doesnt exists yet 92 | 93 | - `--debug` 94 | 95 | If your lambda was built with `debug: true` in your nuxt.config, use this switch to toggle verbose logging 96 | 97 | ## Available Handlers 98 | 99 | #### connect (default) 100 | 101 | This is an optimized handler that uses `connect` & `serverless-http` to process requests without starting a full blown server. 102 | 103 | #### minimal (experimental, proof of concept) 104 | 105 | This is an extremely optimized handler, it uses abstracted implementations for most dependencies which cannot be tree-shaked but have a reasonable effect on cold-boot / execution times. 106 | 107 | > Dont use this handler yet in production until we have implemented more tests 108 | 109 | - no `serverless-http`, uses a lightweight custom implementation 110 | - `fs-extra` is stubbed with a lightweight custom implementation 111 | - a custom `compression` middleware is used 112 | 113 | Preliminary savings seem to be around ~10ms compared to the connect handler 114 | 115 | #### full 116 | 117 | Just start a full-blown Nuxt.js server instance with all Nuxt.js features and dependencies. Not optimized at all, provided as fallback so you can still benefit from the packaging this command provides 118 | 119 | ## Debugging 120 | 121 | For the optimized handlers, make sure to set `debug: true` in your `nuxt.config`. Then re-build your lambda & run the `test-lambda` command with `--debug` 122 | 123 | Debugging is implemented by returning a Proxy for the `res` and `req` variables which logs any `get`, `set` or `call` command. When you have compression enabled, it also prints debug logs about compression. 124 | 125 | Also when `debug: true` the lambda wont be minimized, so you can check the file `${options.lambda.buildDir}/${options.lambda.name}.js` to debug the webpack build 126 | 127 | ## Options 128 | 129 | You can add a `lambda` section in your nuxt.config with the following properties 130 | 131 | - `name` (default: _nuxt_) 132 | 133 | The name of your lambda, ie the zip will be named `.zip`. Dont change this for now if you want to run the test-lambda command 134 | 135 | - `handler` (default: _connect_) 136 | 137 | Either `full`, `connect` or `minimal`. See [available handlers](#available-handlers) 138 | 139 | - `buildDir` (default: _.lambda_) 140 | 141 | The name of the folder where the intermediates for the lambda build are saved (dont use nuxt's buildDir, config is created before nuxt build which removes the nuxt builddir). If relative then relative to _rootDir_ 142 | 143 | - `distDir` (default: _dist_) 144 | 145 | The name of the folder where the zipped lambda will be saved. If relative then relative to _rootDir_ 146 | 147 | - `spa` (default: _false_) 148 | 149 | This is intended to be an override when you are using universal mode but have some single pages running as SPA. In essence it will _not_ optimize the lambda by removing SPA-only features 150 | 151 | - `static` (default: _false_) 152 | 153 | If true then full support for serving static files from `nuxt.options.dir.static` will also be added to the lambda. 154 | 155 | For the full handler this means the serve-static middleware can resolve all static files 156 | For the connect handler the serve-static middleware is added to handle static file loading (but only when `static: true`) 157 | For the minimal handler this option only adds the files, so you probably shouldnt use this (there is no safeguard though) 158 | 159 | - `zisi` (default: _undefined_) 160 | 161 | Options passed to zisi. See [@netlify/zip-it-and-ship-it](https://github.com/netlify/zip-it-and-ship-it) 162 | 163 | - `excludeClientFiles` (default: _undefined_) 164 | 165 | Used to exclude certain assets from Nuxt's dist/client directory to be included in the Nuxt lambda zip file. Useful when you are combining your lambda with a static file deployment and are using image, video etc assets in your application. Those assets dont need to be included in the lambda zip file when you also deploy all your client files statically already. 166 | 167 | Can either be an array of paths or a function. If an array then each file from the `.nuxt/dist/client` folder is only added when they don't start with the given path. We only support startsWith for now because this feature should be used in conjunction with Nuxt's `build.filenames` property to move certain file types to a specific subfolder 168 | 169 | If a function is provided it receives the relative path to the client folder as first argument, and the full (absolute) filename as second argument 170 | 171 | Example 172 | ```js 173 | // nuxt.config.js 174 | build: { 175 | filenames: { 176 | img: ({ isDev }) => isDev ? '[path][name].[ext]' : 'img/[contenthash:7].[ext]', 177 | } 178 | }, 179 | lambda: { 180 | excludeClientFiles: ['img/'], 181 | // or 182 | excludeClientFiles: clientAssetPath => !clientAssetPath.startsWith('img/'), 183 | } 184 | ``` 185 | 186 | - `excludeFiles` (default: _undefined_) 187 | 188 | Similar to `excludeClientFiles` but can be used to also filter server files. Recieves the file path related from `.nuxt`. 189 | 190 | Example 191 | ```js 192 | // nuxt.config.js 193 | build: { 194 | filenames: { 195 | img: ({ isDev }) => isDev ? '[path][name].[ext]' : 'img/[contenthash:7].[ext]', 196 | } 197 | }, 198 | lambda: { 199 | excludeFiles: ['dist/client/img/'], // same as excludeClientFiles example above 200 | // or 201 | excludeFiles: filePath => filePath.endsWith('.map'), // ignore all sourcemaps 202 | } 203 | ``` 204 | 205 | - `webpack` (default: _null_) 206 | 207 | Any additional webpack config that is needed for your lambda build. At the moment it doesnt make really sense to touch this 208 | 209 | ## Benchmarks 210 | 211 | Please check [Benchmarks](./BENCHMARKS.md) 212 | 213 | #### Remarks about `/` page 214 | 215 | This page uses the uuid plugin, which means its expected that checksums for the response are _never_ the same 216 | 217 | #### Remarks about `/about` page 218 | 219 | This page does a request to a 3rd pary API, differences in execution time are likely caused by variance in contacting this API 220 | 221 | #### Remarks about `/redirect` page 222 | 223 | `serverless-http` up to `v2.3.1` contains a [bug](https://github.com/dougmoscrop/serverless-http/pull/137) which causes Nuxt.js redirections to fail unless a previous header has been set 224 | 225 | #### Remarks about `/nuxt-icon.png` file 226 | 227 | This file should only return a 200 for the full and connect handlers and the `with-static` config. The checksums wont be the same due to differences in last-modified times 228 | 229 | ### Running the benchmarks 230 | 231 | The benchmark command is located in `./benchmark/run.js` 232 | 233 | #### arguments 234 | 235 | - `--results ` (optional) 236 | 237 | Point to a previous created results file instead of running a new benchmark. Useful for markdown tweaking. 238 | 239 | - `--runs ` 240 | 241 | If you want to change the default number of runs (which is 3) 242 | 243 | ## Rationale for using the optimized handler by default 244 | 245 | A lambda should be very good at running a single task, ie have a single responsibility. Nuxt will by default start a connect server which provides a similar abstraction layer as lambdas provide. The connect server is actually a very strong feature of Nuxt.js, it helps greatly to simplify deployments. But there are also cases where you dont need this abstraction (anymore) or where this second abstraction layer is just the _wrong_ approach because you deploy serverless. Eg a common approach is to use Nuxt.js serverMiddleware's to deploy an API, but when you are deploying serverless that API should really be run in a separate lambda. 246 | 247 | ##### Why is a single responsibility important? 248 | 249 | Most important/practical reason is cold boot performance of the lambda. A lambda is started for every single request, although the lambda context might be 'warm' from a previous request there are no guarantees that it will still be warm. Response times/performance of your Nuxt.js website could vary greatly because of this. 250 | 251 | Having a single responsibility means less dependencies, less code to parse so quicker cold boots: 252 | 253 | |Lambda implementation|Memory|Cold boot time| 254 | |---|---|---| 255 | |Full Nuxt.js|1GB|~500ms| 256 | |Full Nuxt.js|1.5GB|~300ms| 257 | |nuxt-lambda|1GB|~175ms| 258 | |nuxt-lambda|1.5GB|?| 259 | 260 | More information about AWS cold starts in this excellent article by @MikhailShilkov: [Cold Starts in AWS Lambda](https://mikhail.io/serverless/coldstarts/aws/) 261 | 262 | ## How does it work 263 | 264 | In general the lambda build consists of 4 steps: 265 | 266 | - Create a pre-normalized and optimized nuxt.config 267 | - Build Nuxt.js 268 | - Build Lambda, if using the optimized handler then stub any Nuxt.js feature/dependencies we dont need/want 269 | - Create a Zip file for the lambda: 270 | - Start with [@netlify/zip-it-and-ship-it](https://github.com/netlify/zip-it-and-ship-it) to zip the Nuxt.js lambda and all/just its dependencies 271 | - Then add all Nuxt.js resources from `//dist` to it 272 | 273 | ##### Is stubbing imports to optimize the lambda build really a good approach? 274 | 275 | Probably not, it would be better to fix these imports in Nuxt.js core. That might still happen, but stubbing gets us fastests to where we want to be for now. Also it will work with any Nuxt.js version and not just some future release. 276 | 277 | Eg just parsing/loading the `consola` dependency takes up to 10ms. Do we really need fancy logging in a lambda? Plain console should be good enough too, so we replace all `consola.` statements to `console.`. 278 | 279 | > This of course could break your code if you use non-standard log functions. Eg we have to set `render.ssrLog: false` because otherwise Nuxt.js will try to add a consola reporter 280 | 281 | ## Notice about running nuxt-lambda as Netlify function 282 | 283 | At the moment you cant both deploy static files and use a Netlify function as fallback for serving SSR routes due to a redirect issue on their platform. The issue causes your Nuxt function (often, but not always) to be preferred instead of the static files. Meaning that if you dont serve your static file _also_ with your lambda you will get 404's 284 | 285 | ## TODO 286 | 287 | - [ ] (minimal handler) Improve feature testing 288 | - [ ] (minimal/connect handler) Allow additional middleware's 289 | - [ ] (maybe) Try to pack `vue-server-renderer` in a single file (less files could be faster unzipping?) 290 | - [ ] (maybe) Provide Nuxt.js lambdas as AWS layers (we'd need to remove the stubbing and just use optional requires instead) 291 | - Then users really only have to deploy their `.nuxt/dist` folder 292 | 293 | ## License 294 | 295 | [MIT License](./LICENSE) 296 | 297 | Copyright (c) pimlie 298 | 299 | 300 | [npm-version-src]: https://img.shields.io/npm/v/nuxt-lambda/latest.svg?style=flat-square 301 | [npm-version-href]: https://npmjs.com/package/nuxt-lambda 302 | 303 | [npm-downloads-src]: https://img.shields.io/npm/dt/nuxt-lambda.svg?style=flat-square 304 | [npm-downloads-href]: https://npmjs.com/package/nuxt-lambda 305 | 306 | [circle-ci-src]: https://img.shields.io/circleci/project/github/pimlie/nuxt-lambda.svg?style=flat-square 307 | [circle-ci-href]: https://circleci.com/gh/pimlie/nuxt-lambda 308 | 309 | [codecov-src]: https://img.shields.io/codecov/c/github/pimlie/nuxt-lambda.svg?style=flat-square 310 | [codecov-href]: https://codecov.io/gh/pimlie/nuxt-lambda 311 | 312 | [license-src]: https://img.shields.io/npm/l/nuxt-lambda.svg?style=flat-square 313 | [license-href]: https://npmjs.com/package/nuxt-lambda 314 | -------------------------------------------------------------------------------- /benchmark/configs/base.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | const rootDir = path.resolve(__dirname, '../../example') 4 | 5 | const baseConfig = require(`${rootDir}/nuxt.config.js`) 6 | 7 | export default { 8 | ...baseConfig.default, 9 | rootDir, 10 | render: { 11 | compressor: false, 12 | etag: false 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /benchmark/configs/with-compression.js: -------------------------------------------------------------------------------- 1 | import baseConfig from './base' 2 | 3 | export default { 4 | ...baseConfig, 5 | lambda: { 6 | buildDir: '.lambda-with-compression', 7 | distDir: 'dist-with-compression' 8 | }, 9 | render: { 10 | compressor: {}, 11 | etag: false 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /benchmark/configs/with-etag.js: -------------------------------------------------------------------------------- 1 | import baseConfig from './base' 2 | 3 | export default { 4 | ...baseConfig, 5 | lambda: { 6 | buildDir: '.lambda-with-etag', 7 | distDir: 'dist-with-etag' 8 | }, 9 | render: { 10 | compressor: false, 11 | etag: true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /benchmark/configs/with-static.js: -------------------------------------------------------------------------------- 1 | import baseConfig from './base' 2 | 3 | export default { 4 | ...baseConfig, 5 | lambda: { 6 | buildDir: '.lambda-with-static', 7 | distDir: 'dist-with-static', 8 | static: true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /benchmark/run.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const path = require('path') 4 | const crypto = require('crypto') 5 | const fs = require('fs-extra') 6 | const consola = require('consola') 7 | const execa = require('execa') 8 | const minimist = require('minimist') 9 | const prettyBytes = require('pretty-bytes') 10 | const { NuxtCommand } = require('../lib/utils/nuxt') 11 | const LambdaBuild = require('../lib/build') 12 | // const LambdaTest = require('../lib/test') 13 | 14 | const testCmd = path.resolve(__dirname, '../bin/test-lambda.js') 15 | const rootDir = path.resolve(__dirname, '../example') 16 | 17 | let takeAverageOfRuns = 3 18 | 19 | const configs = [ 20 | 'base', 21 | 'with-compression', 22 | 'with-etag', 23 | 'with-static' 24 | ] 25 | 26 | const handlers = [ 27 | 'full', 28 | 'connect', 29 | 'minimal' 30 | ] 31 | 32 | const paths = [ 33 | '/', 34 | '/about', 35 | '/redirect', 36 | '/nuxt-icon.png' 37 | ] 38 | 39 | function setBuildArgv ({ config, handler, buildNuxt }) { 40 | process.argv = [ 41 | 'yarn', 42 | 'nuxt-lambda', 43 | '-c', 44 | path.resolve(__dirname, `configs/${config}.js`), 45 | '--handler', 46 | handler, 47 | buildNuxt ? '' : '--no-nuxt' 48 | ] 49 | } 50 | 51 | function getTestArgs ({ config, pathname }) { 52 | return [ 53 | '-c', 54 | path.resolve(__dirname, `configs/${config}.js`), 55 | '--benchmark', 56 | rootDir, 57 | pathname 58 | ] 59 | } 60 | 61 | async function main () { 62 | const argv = minimist(process.argv.slice(2)) 63 | 64 | let results 65 | if (argv.results) { 66 | results = require(path.resolve(argv.results)) 67 | } else { 68 | if (argv.runs) { 69 | takeAverageOfRuns = parseInt(argv.runs) || takeAverageOfRuns 70 | } 71 | 72 | results = await runBenchmarks() 73 | await fs.outputFile(path.resolve(__dirname, 'results.json'), JSON.stringify(results, null, 2), { encoding: 'utf8' }) 74 | } 75 | 76 | const md = createMarkdown(results) 77 | await fs.outputFile(path.resolve(__dirname, '../BENCHMARKS.md'), md, { encoding: 'utf8' }) 78 | 79 | consola.info('Benchmarks created') 80 | } 81 | 82 | async function runBenchmarks () { 83 | const results = {} 84 | 85 | for (const config of configs) { 86 | let buildNuxt = true 87 | 88 | for (const handler of handlers) { 89 | setBuildArgv({ config, handler, buildNuxt }) 90 | buildNuxt = false 91 | 92 | await NuxtCommand.run(LambdaBuild) 93 | 94 | for (const pathname of paths) { 95 | results[pathname] = results[pathname] || {} 96 | results[pathname][config] = results[pathname][config] || {} 97 | 98 | const takeAverages = [] 99 | for (let i = 0; i < takeAverageOfRuns; i++) { 100 | const { stdout } = await execa(testCmd, getTestArgs({ config, pathname })) 101 | 102 | consola.log(pathname, stdout) 103 | 104 | try { 105 | const benchmark = JSON.parse(stdout) 106 | 107 | if (!results[pathname][config][handler]) { 108 | results[pathname][config][handler] = benchmark 109 | } 110 | 111 | takeAverages.push(benchmark) 112 | } catch (err) { 113 | consola.error('Error parsing stdout', stdout) 114 | } 115 | } 116 | 117 | if (takeAverageOfRuns > 1) { 118 | for (const key in results[pathname][config][handler].stats) { 119 | results[pathname][config][handler].stats[key] = Math.round(takeAverages.reduce( 120 | (t, benchmark) => (t += 1 * benchmark.stats[key]), 121 | 0 122 | ) / takeAverageOfRuns) 123 | } 124 | 125 | for (const key in results[pathname][config][handler].mem) { 126 | results[pathname][config][handler].mem[key] = Math.round(takeAverages.reduce( 127 | (t, benchmark) => (t += 1 * benchmark.mem[key]), 128 | 0 129 | ) / takeAverageOfRuns) 130 | } 131 | } 132 | } 133 | } 134 | } 135 | 136 | return results 137 | } 138 | 139 | function createMarkdown (results) { 140 | let markdown = ` 141 | # Benchmarks 142 | 143 | > :warning: The total times are measured without downloading & unzipping what normally occures on the AWS platform. Therefore these benchmarks only list _load/parse times_ and not _coldboot times_ 144 | 145 | - Times are in \`ms\` 146 | - Times/memory usage are the averages of ${takeAverageOfRuns} runs 147 | 148 | Check the benchmark folder for details how these benchmarks are created 149 | 150 | ` 151 | 152 | for (const pathname in results) { 153 | markdown += `## Route \`${pathname}\`\n` 154 | 155 | for (const config in results[pathname]) { 156 | markdown += `### Config _${config}_\n` 157 | 158 | markdown += '|handler|load time|exec time|total time|memory usage|chksum|\n' 159 | markdown += '|---|---|---|---|---|---|\n' 160 | 161 | const responses = {} 162 | for (const handler in results[pathname][config]) { 163 | const result = results[pathname][config][handler] 164 | 165 | const bootTime = prettyHrtime(result.stats.boot) 166 | const execTime = prettyHrtime(result.stats.execute) 167 | const totalTime = prettyHrtime(result.stats.boot + result.stats.execute) 168 | const memUsage = prettyBytes(result.mem.rss) 169 | 170 | const response = JSON.stringify(result.res, null, 2) 171 | responses[handler] = response 172 | 173 | const md5sum = crypto.createHash('md5') 174 | md5sum.update(response) 175 | 176 | markdown += `|_${handler}_|${bootTime}|${execTime}|${totalTime}|${memUsage}|${md5sum.digest('base64')}|\n` 177 | } 178 | 179 | for (const handler in responses) { 180 | markdown += `
Response for ${handler} 181 | 182 | \`\`\`js 183 | ${responses[handler].length > 4000 ? 'blob_with_size_' + responses[handler].length : responses[handler]} 184 | \`\`\` 185 |
186 | 187 | ` 188 | } 189 | } 190 | } 191 | 192 | return markdown 193 | } 194 | 195 | function prettyHrtime (time) { 196 | return Math.round(time / 1e5) / 10 197 | } 198 | 199 | main() 200 | -------------------------------------------------------------------------------- /bin/nuxt-lambda.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../lib/utils/nuxt').NuxtCommand.run(require('../lib/build')) 4 | -------------------------------------------------------------------------------- /bin/test-lambda.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../lib/utils/nuxt').NuxtCommand.run(require('../lib/test')) 4 | -------------------------------------------------------------------------------- /example/assets/nuxt-square.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /example/nuxt.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | telemetry: false, 3 | lambda: { 4 | static: true, 5 | errorPage: '/handle-internal-server-error', 6 | excludeClientFiles: ['img/'] 7 | }, 8 | buildModules: [ 9 | '@nuxtjs/axios' 10 | ], 11 | plugins: [ 12 | '~/plugins/vue-uuid.js' 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /example/pages/about.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 31 | -------------------------------------------------------------------------------- /example/pages/handle-internal-server-error.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 28 | -------------------------------------------------------------------------------- /example/pages/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 33 | 34 | -------------------------------------------------------------------------------- /example/pages/redirect.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /example/pages/with-error.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /example/plugins/vue-uuid.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import UUID from 'vue-uuid' 3 | 4 | Vue.use(UUID) 5 | 6 | // eslint-disable-next-line no-console 7 | console.log('UUID plugin loaded') 8 | -------------------------------------------------------------------------------- /example/static/nuxt-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimlie/nuxt-lambda/75c2b036842e2ce25d1d3ddd955aaeb75457a5fd/example/static/nuxt-icon.png -------------------------------------------------------------------------------- /lib/build.js: -------------------------------------------------------------------------------- 1 | const { options: { common } } = require('./utils/nuxt') 2 | const options = require('./utils/build-options') 3 | 4 | module.exports = { 5 | name: 'lambda', 6 | description: 'Build an optimized nuxt lambda', 7 | usage: 'lambda ', 8 | options: { 9 | ...common, 10 | ...options 11 | }, 12 | async run (cmd) { 13 | // TODO: fix configFile, options when false 14 | // TODO: fix lambdaPath when false 15 | 16 | // create the optimized nuxt.config that will be included in the lambda 17 | const [configFile, options] = await require('./commands/config').run(cmd) 18 | 19 | // build nuxt with standalone: true 20 | await require('./commands/build-nuxt').run(cmd, options) 21 | 22 | // build the lambda 23 | const stats = await require('./commands/build-lambda').run(cmd, { configFile, options }) 24 | const [[, { existsAt: lambdaPath }]] = Object.entries(stats.compilation.assets) 25 | 26 | // create the zip file for the lambda & add the Nuxt.js dist files to it 27 | await require('./commands/zip').run(cmd, { options, lambdaPath }) 28 | 29 | // DONE! 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/commands/build-lambda.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const consola = require('consola') 3 | const webpack = require('webpack') 4 | // const deepmerge = require('deepmerge') 5 | const merge = require('webpack-merge') 6 | const nodeExternals = require('webpack-node-externals') 7 | const ReplacePlugin = require('webpack-plugin-replace') 8 | const { defaultHandler, isSupportedHandler } = require('../utils/options') 9 | 10 | const r = p => path.resolve(__dirname, '../..', p) 11 | 12 | async function run (cmd, config) { 13 | if (!cmd.argv.lambda) { 14 | return 15 | } 16 | 17 | consola.info('Start building lambda') 18 | const webpackConfig = createWebpackConfig(config, cmd) 19 | 20 | try { 21 | return await new Promise(function (resolve, reject) { 22 | webpack(webpackConfig, (err, stats) => { 23 | if (err) { 24 | return reject(err) 25 | } 26 | 27 | const info = stats.toJson('minimal') 28 | if (stats.hasErrors()) { 29 | return reject(info.errors) 30 | } 31 | 32 | if (stats.hasWarnings()) { 33 | info.warnings.forEach(e => consola.warn(e)) 34 | } 35 | 36 | consola.info('Lambda build succesfully') 37 | resolve(stats) 38 | }) 39 | }) 40 | } catch (err) { 41 | consola.error('Webpack build for lambda failed', err) 42 | } 43 | } 44 | 45 | function createWebpackConfig ({ configFile, options }, cmd) { 46 | const isDev = process.env.NODE_ENV === 'development' 47 | 48 | let devtool = '' 49 | let mode = 'production' 50 | 51 | if (isDev) { 52 | devtool = 'cheap-eval-source-map' 53 | mode = 'development' 54 | } 55 | 56 | let handler = cmd.argv.handler || options.lambda.handler 57 | if (!isSupportedHandler(handler)) { 58 | handler = defaultHandler 59 | } 60 | 61 | const optimizeBuild = handler !== 'full' 62 | 63 | const alias = { 64 | 'nuxt-handler': r(`src/handlers/${handler}`), 65 | 'nuxt.config.js': configFile 66 | } 67 | 68 | let nodeExternalsOpts 69 | const replaceOpts = null 70 | 71 | const defineOpts = { 72 | 'process.env.NETLIFY': process.env.NETLIFY === 'true', 73 | 'process.env.LAMBDA_USE_COMPRESSION': !!options.render.compressor, 74 | 'process.env.LAMBDA_USE_SERVESTATIC': !!options.lambda.static 75 | } 76 | 77 | if (!options.debug) { 78 | defineOpts['process.env.LAMBDA_DEBUG'] = false 79 | } 80 | 81 | const plugins = [ 82 | new webpack.DefinePlugin(defineOpts) 83 | ] 84 | 85 | if (optimizeBuild) { 86 | const stub = r('lib/stub') 87 | 88 | Object.assign(alias, { 89 | 'fs-extra': r('lib/stub/fs'), 90 | consola: r('lib/stub/consola'), 91 | jsdom: stub, 92 | esm: stub, 93 | '@nuxtjs/youch': stub, 94 | 'proper-lockfile': stub 95 | }) 96 | 97 | if (!options.modern) { 98 | alias.semver = stub 99 | alias['ua-parser-js'] = stub 100 | } 101 | 102 | if (!options.render.etag) { 103 | // dont stub etag when serving static files, serve-static depends on it 104 | if (!options.lambda.static) { 105 | alias.etag = stub 106 | } 107 | alias.fresh = stub 108 | } 109 | 110 | if (options.mode !== 'spa' && !options.lambda.spa && !options.modern) { 111 | alias['lru-cache'] = r('lib/stub/class') 112 | } 113 | 114 | nodeExternalsOpts = { 115 | allowlist: !optimizeBuild 116 | ? [] 117 | : [ 118 | /^(?!(vue-server-renderer))/ 119 | ] 120 | } 121 | /* 122 | replaceOpts = deepmerge({ 123 | patterns: [ 124 | { regex: /consola\./gi, value: 'console.' }, 125 | { regex: /consola\[/gi, value: 'console[' } 126 | ] 127 | }, replaceOpts || {}) 128 | */ 129 | } 130 | 131 | if (replaceOpts) { 132 | if (!replaceOpts.values) { 133 | // I dare you to remove this 134 | replaceOpts.values = { 135 | '________|WTF?|________': '________|WTF!|________' 136 | } 137 | } 138 | 139 | plugins.push(new ReplacePlugin(replaceOpts)) 140 | } 141 | 142 | const entry = cmd.argv.handler === 'minimal' ? 'event' : 'http' 143 | 144 | const webpackConfig = { 145 | devtool, 146 | mode, 147 | target: 'node', 148 | entry: { 149 | [options.lambda.name]: r(`src/entries/${entry}.js`) 150 | }, 151 | context: options.rootDir, 152 | output: { 153 | path: options.lambda.buildDir, 154 | filename: '[name].js', 155 | libraryTarget: 'commonjs' 156 | }, 157 | externals: [nodeExternals(nodeExternalsOpts)], 158 | node: { 159 | __dirname: false 160 | }, 161 | optimization: { 162 | usedExports: !isDev, 163 | minimize: !isDev && !options.debug 164 | }, 165 | resolve: { 166 | mainFields: ['module', 'main'], 167 | alias 168 | }, 169 | plugins 170 | } 171 | 172 | if (options.lambda.webpack) { 173 | return merge.smart(webpackConfig, options.lambda.webpack) 174 | } 175 | 176 | return webpackConfig 177 | } 178 | 179 | exports.run = run 180 | -------------------------------------------------------------------------------- /lib/commands/build-nuxt.js: -------------------------------------------------------------------------------- 1 | const consola = require('consola') 2 | 3 | async function run (cmd, config) { 4 | if (!cmd.argv.nuxt) { 5 | return 6 | } 7 | 8 | consola.info('Start building Nuxt.js') 9 | 10 | const nuxt = await cmd.getNuxt(config) 11 | 12 | const builder = await cmd.getBuilder(nuxt) 13 | await builder.build() 14 | 15 | consola.info('Build complete') 16 | } 17 | 18 | exports.run = run 19 | -------------------------------------------------------------------------------- /lib/commands/config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs-extra') 3 | const consola = require('consola') 4 | const { getNuxtConfig, determineGlobals } = require('../utils/nuxt') 5 | 6 | async function run (cmd) { 7 | if (!cmd.argv.optimize) { 8 | return 9 | } 10 | 11 | const userConfig = await cmd.getNuxtConfig({ 12 | dev: false, 13 | server: false, 14 | _build: true 15 | }) 16 | const routerBaseSpecified = 17 | userConfig.router && typeof userConfig.router.base === 'string' 18 | const normalizedConfig = getNuxtConfig(userConfig) 19 | 20 | normalizedConfig.build.standalone = true 21 | normalizedConfig.render.ssrLog = false // invokes consola.addReporter which isnt supported by console 22 | 23 | const { 24 | buildDir, 25 | build: { publicPath, crossorigin }, 26 | app: { cdnURL, assetsPath }, 27 | lambda: { errorPage }, 28 | env, 29 | head, 30 | globalName, 31 | globals, 32 | messages, 33 | mode, 34 | modern, 35 | modules, 36 | render, 37 | rootDir, 38 | router 39 | } = normalizedConfig 40 | 41 | const optimizedOptions = { 42 | buildDir: userConfig.buildDir || path.relative(rootDir, buildDir), 43 | build: { 44 | crossorigin 45 | }, 46 | app: { 47 | cdnURL, 48 | assetsPath 49 | }, 50 | lambda: { 51 | errorPage 52 | }, 53 | dev: false, 54 | env, 55 | head, 56 | globalName, 57 | globals: determineGlobals(globalName, globals), 58 | messages, 59 | mode, 60 | modern, 61 | modules, 62 | render, 63 | rootDir: '__dirname', 64 | router 65 | } 66 | 67 | const isStaticLambda = cmd.argv.static || normalizedConfig.lambda.static 68 | if (isStaticLambda) { 69 | normalizedConfig.lambda.static = true 70 | optimizedOptions.clientDir = path.join( 71 | optimizedOptions.buildDir, 72 | 'dist', 73 | 'client' 74 | ) 75 | } 76 | 77 | if (isStaticLambda || modern) { 78 | optimizedOptions.build.publicPath = publicPath 79 | } 80 | 81 | optimizedOptions.render.bundleRenderer.shouldPreload = 82 | render.bundleRenderer.shouldPreload.toString() 83 | optimizedOptions.render.bundleRenderer.shouldPrefetch = 84 | render.bundleRenderer.shouldPrefetch.toString() 85 | 86 | if (routerBaseSpecified) { 87 | optimizedOptions._routerBaseSpecified = true 88 | } else { 89 | delete optimizedOptions.router.base 90 | } 91 | 92 | // resolve & prepare dirs 93 | normalizedConfig.lambda.buildDir = path.resolve( 94 | rootDir, 95 | normalizedConfig.lambda.buildDir 96 | ) 97 | normalizedConfig.lambda.distDir = path.resolve( 98 | rootDir, 99 | normalizedConfig.lambda.distDir 100 | ) 101 | 102 | await fs.remove(normalizedConfig.lambda.buildDir) 103 | await fs.remove(normalizedConfig.lambda.distDir) 104 | 105 | // write config file for lambda 106 | const configFile = path.resolve( 107 | normalizedConfig.lambda.buildDir, 108 | 'nuxt-lambda.config.js' 109 | ) 110 | const configSerialized = JSON.stringify(optimizedOptions, null, 2).replace( 111 | '"__dirname"', 112 | '__dirname' 113 | ) 114 | await fs.outputFile(configFile, `export default ${configSerialized}`) 115 | 116 | consola.info(`Optimized lambda config saved at: ${configFile}`) 117 | 118 | /* if (normalizedConfig.modern === true || normalizedConfig.modern === 'server') { 119 | consola.warn('modern: true is not supported by the lambda, falling back to \'client\' instead') 120 | normalizedConfig.modern = 'client' 121 | } */ 122 | 123 | return [configFile, normalizedConfig, optimizedOptions] 124 | } 125 | 126 | exports.run = run 127 | -------------------------------------------------------------------------------- /lib/commands/zip.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs-extra') 3 | const consola = require('consola') 4 | const JSZip = require('jszip') 5 | const klaw = require('klaw') 6 | const { zipFunction } = require('@netlify/zip-it-and-ship-it') 7 | 8 | async function addFolderToZip (zip, dir, options, createEntry) { 9 | const files = await new Promise((resolve) => { 10 | const items = [] 11 | klaw(dir) 12 | .on('data', (item) => { 13 | if (item.stats.isFile()) { 14 | items.push(item.path) 15 | } 16 | }) 17 | .on('end', () => resolve(items)) 18 | }) 19 | 20 | let { excludeClientFiles, excludeFiles } = options.lambda 21 | if (Array.isArray(excludeClientFiles)) { 22 | excludeClientFiles = entry => !options.lambda.excludeClientFiles.some(path => entry.startsWith(path)) 23 | } else if (excludeClientFiles && typeof excludeClientFiles !== 'function') { 24 | consola.warn('excludeClientFiles should be a function or array') 25 | return 26 | } 27 | 28 | if (Array.isArray(excludeFiles)) { 29 | excludeFiles = entry => !options.lambda.excludeFiles.some(path => entry.startsWith(path)) 30 | } else if (excludeFiles && typeof excludeFiles !== 'function') { 31 | consola.warn('excludeFiles should be a function or array') 32 | return 33 | } 34 | 35 | for (const file of files) { 36 | let entry 37 | if (typeof createEntry === 'function') { 38 | entry = createEntry(file) 39 | } else { 40 | entry = path.relative(options.rootDir, file) 41 | } 42 | 43 | let addToZip = true 44 | if (excludeFiles || excludeClientFiles) { 45 | const buildEntry = path.relative(options.buildDir, file) 46 | 47 | if (excludeClientFiles && buildEntry.startsWith('dist/client/')) { 48 | addToZip = excludeClientFiles(buildEntry.slice(12), file) 49 | } else if (excludeFiles && excludeFiles(buildEntry, file)) { 50 | addToZip = false 51 | } 52 | } 53 | 54 | if (addToZip) { 55 | zip.file(entry, await fs.readFile(file)) 56 | } 57 | } 58 | } 59 | 60 | async function run (cmd, { lambdaPath, options }) { 61 | if (!cmd.argv.zip) { 62 | return 63 | } 64 | 65 | const distDir = options.lambda.distDir 66 | const nuxtDir = path.resolve(options.buildDir, 'dist') 67 | 68 | await fs.remove(distDir) 69 | await fs.ensureDir(distDir) 70 | 71 | consola.info('Creating zip file with dependencies') 72 | 73 | const { path: lambdaZip } = await zipFunction(lambdaPath, distDir, options.lambda.zisi || {}) 74 | consola.info('Zip file created, adding Nuxt.js files') 75 | 76 | const zip = new JSZip() 77 | 78 | const zipBinary = await fs.readFile(lambdaZip) 79 | const zipContents = await zip.loadAsync(zipBinary, { checkCRC32: true }) 80 | 81 | await addFolderToZip(zipContents, nuxtDir, options) 82 | 83 | if (options.lambda.static) { 84 | const staticDir = path.resolve(options.srcDir, options.dir.static) 85 | await addFolderToZip(zipContents, staticDir, options, (entry) => { 86 | return path.join('static', path.relative(staticDir, entry)) 87 | }) 88 | } 89 | 90 | await new Promise((resolve) => { 91 | zip 92 | .generateNodeStream({ 93 | type: 'nodebuffer', 94 | streamFiles: true 95 | }) 96 | .pipe(fs.createWriteStream(lambdaZip)) 97 | .on('finish', resolve) 98 | }) 99 | 100 | consola.info('Lambda is fully zipped:', lambdaZip) 101 | } 102 | 103 | exports.run = run 104 | -------------------------------------------------------------------------------- /lib/stub/class.js: -------------------------------------------------------------------------------- 1 | export default class {} 2 | -------------------------------------------------------------------------------- /lib/stub/consola.js: -------------------------------------------------------------------------------- 1 | export default console 2 | -------------------------------------------------------------------------------- /lib/stub/fs.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const util = require('util') 3 | 4 | module.exports.exists = function exists (path) { 5 | return new Promise((resolve) => { 6 | fs.access(path, fs.constants.R_OK, (err) => { 7 | resolve(!err) 8 | }) 9 | }) 10 | } 11 | 12 | module.exports.readFile = util.promisify(fs.readFile) 13 | 14 | module.exports.existsSync = fs.existsSync 15 | -------------------------------------------------------------------------------- /lib/stub/index.js: -------------------------------------------------------------------------------- 1 | export default {} 2 | -------------------------------------------------------------------------------- /lib/test.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs-extra') 3 | const consola = require('consola') 4 | 5 | const { getNuxtConfig, options: { common } } = require('./utils/nuxt') 6 | const { atob } = require('./utils/options') 7 | 8 | module.exports = { 9 | name: 'test-lambda', 10 | description: 'Test a nuxt lambda', 11 | usage: 'lambda ', 12 | options: { 13 | ...common, 14 | persistent: { 15 | alias: 'p', 16 | type: 'boolean', 17 | description: 'Do not use a temporary dir, unpack in the lambda dist folder and use unpacked lambda if available' 18 | }, 19 | decode: { 20 | type: 'boolean', 21 | description: 'Decode response from base64 to ascii' 22 | }, 23 | debug: { 24 | type: 'boolean', 25 | description: 'Print debugging logs. Your lambda needs to have been built with debug: true in your nuxt.config' 26 | }, 27 | benchmark: { 28 | type: 'boolean', 29 | description: 'Run in benchmark mode, only output boot / execution times and memory usage in json' 30 | } 31 | }, 32 | async run (cmd) { 33 | if (cmd.argv.debug) { 34 | process.env.LAMBDA_DEBUG = 1 35 | } else { 36 | delete process.env.LAMBDA_DEBUG 37 | } 38 | 39 | if (cmd.argv.benchmark) { 40 | console.log = _ => _ // eslint-disable-line no-console 41 | console.debug = _ => _ // eslint-disable-line no-console 42 | consola.setReporters([]) 43 | } 44 | 45 | let [rootDir, pathname] = cmd.argv._ 46 | 47 | if (!pathname) { 48 | pathname = rootDir 49 | rootDir = '.' 50 | } 51 | 52 | let workDir 53 | let lambdaZip 54 | let handlerPath 55 | let needsUnzipping = true 56 | 57 | if (rootDir.endsWith('.zip') && !cmd.argv.persistent) { 58 | workDir = await createTmpDir('nuxt-lambda') 59 | 60 | rootDir = path.resolve(rootDir) 61 | lambdaZip = rootDir 62 | handlerPath = `${rootDir.slice(0, -4)}.js` 63 | } else { 64 | const userConfig = await cmd.getNuxtConfig({ dev: false, server: false, _build: true }) 65 | const config = getNuxtConfig(userConfig) 66 | 67 | if (!cmd.argv.persistent) { 68 | workDir = await createTmpDir('nuxt-lambda') 69 | handlerPath = path.resolve(workDir, `${config.lambda.name}.js`) 70 | } else { 71 | workDir = path.resolve(config.rootDir, config.lambda.distDir) 72 | handlerPath = path.resolve(config.rootDir, config.lambda.distDir, `${config.lambda.name}.js`) 73 | } 74 | 75 | lambdaZip = path.resolve(config.rootDir, config.lambda.distDir, `${config.lambda.name}.zip`) 76 | needsUnzipping = !await fs.exists(handlerPath) 77 | 78 | if ( 79 | (!cmd.argv.persistent || needsUnzipping) && 80 | !await fs.exists(lambdaZip) 81 | ) { 82 | consola.fatal('Lambda not found, is it build yet?') 83 | return 84 | } 85 | } 86 | 87 | // change to rootDir, otherwise __dirname 88 | // in the lambda config doesnt resolve correctly 89 | process.chdir(workDir) 90 | 91 | if (!cmd.argv.persistent || needsUnzipping) { 92 | consola.info(`---- EXTRACTING ZIP INTO ${workDir} ----`) 93 | await unzip(workDir, lambdaZip) 94 | } 95 | 96 | const stats = { 97 | boot: 0, 98 | execute: 0 99 | } 100 | 101 | for (const id in require.cache) { 102 | delete require.cache[id] 103 | } 104 | 105 | consola.info('---- LOADING HANDLER ----') 106 | const bootStart = process.hrtime.bigint() 107 | const { handler } = require(handlerPath) 108 | stats.boot = parseInt(process.hrtime.bigint() - bootStart) 109 | 110 | const event = { 111 | httpMethod: 'GET', 112 | queryStringParameters: '', 113 | headers: { 114 | Accept: 'text/html' 115 | }, 116 | body: '', 117 | path: pathname || '/' 118 | } 119 | 120 | if (!cmd.argv.decode) { 121 | event.headers['Accept-Encoding'] = 'gzip, deflate, br' 122 | } 123 | 124 | const context = { 125 | requestId: 1 126 | } 127 | 128 | consola.info('---- CALLING HANDLER ----') 129 | const execStart = process.hrtime.bigint() 130 | const res = await handler(event, context) 131 | stats.execute = parseInt(process.hrtime.bigint() - execStart) 132 | 133 | consola.info('---- HANDLER CALLED, RESPONSE: ----') 134 | consola.log({ ...res, body: !!res.body }) 135 | for (const type in stats) { 136 | consola.log(`${type} took: ${Math.round(stats[type] / 1e5) / 10}ms`) 137 | } 138 | consola.log(`total took: ${Math.round(Object.values(stats).reduce((t, s) => (t += s), 0) / 1e5) / 10}ms`) 139 | consola.log(cmd.argv.decode ? atob(res.body) : res.body) 140 | 141 | if (cmd.argv.benchmark) { 142 | const benchmark = { 143 | stats, 144 | res, 145 | mem: process.memoryUsage() 146 | } 147 | 148 | // eslint-disable-next-line no-console 149 | console.info(JSON.stringify(benchmark)) 150 | return benchmark 151 | } 152 | } 153 | } 154 | 155 | function createTmpDir (dir) { 156 | const util = require('util') 157 | const createTempDir = util.promisify(require('temp').track().mkdir) 158 | return createTempDir(dir) 159 | } 160 | 161 | async function unzip (outDir, zipFile) { 162 | const JSZip = require('jszip') 163 | const zip = new JSZip() 164 | 165 | const zipBinary = await fs.readFile(zipFile) 166 | const zipContents = await zip.loadAsync(zipBinary, { checkCRC32: true }) 167 | 168 | for (const file of Object.values(zipContents.files)) { 169 | if (file.dir) { 170 | continue 171 | } 172 | 173 | const filePath = path.resolve(outDir, file.name) 174 | await fs.outputFile(filePath, await file.async('string')) 175 | consola.log(`Extracting ${file.name}`) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /lib/utils/build-options.js: -------------------------------------------------------------------------------- 1 | const { defaultHandler, isSupportedHandler } = require('./options') 2 | 3 | module.exports = { 4 | handler: { 5 | type: 'string', 6 | description: 'Which handler to use, either full, connect or minimal. Default connect', 7 | default: '', 8 | prepare (cmd, options, argv) { 9 | if (!isSupportedHandler(argv.handler)) { 10 | argv.handler = defaultHandler 11 | } 12 | } 13 | }, 14 | static: { 15 | type: 'boolean', 16 | description: 'If true add static middleware for connect/minimal handlers' 17 | }, 18 | optimize: { 19 | alias: 'o', 20 | type: 'boolean', 21 | description: 'Do not create optimized config', 22 | default: true 23 | }, 24 | nuxt: { 25 | alias: 'n', 26 | type: 'boolean', 27 | description: 'Do not build Nuxt.js project', 28 | default: true 29 | }, 30 | lambda: { 31 | alias: 'l', 32 | type: 'boolean', 33 | description: 'Do not build Lambda', 34 | default: true 35 | }, 36 | zip: { 37 | alias: 'z', 38 | type: 'boolean', 39 | description: 'Do not create a zip for the lambda and all its dependencies', 40 | default: true 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/utils/nuxt.js: -------------------------------------------------------------------------------- 1 | const config = require('@nuxt/config') 2 | const cli = require('@nuxt/cli') 3 | const utils = require('@nuxt/utils') 4 | 5 | module.exports = { 6 | ...config, 7 | ...cli, 8 | ...utils 9 | } 10 | 11 | module.exports.getNuxtConfig = (options) => { 12 | options = config.getNuxtConfig(options) 13 | 14 | /* if (options.lambda && options.lambda.handler) { 15 | 16 | } */ 17 | 18 | options.lambda = Object.assign({}, { 19 | name: 'nuxt', 20 | handler: 'connect', 21 | buildDir: '.lambda', 22 | distDir: 'dist', 23 | spa: false, 24 | static: false, 25 | webpack: false 26 | }, options.lambda) 27 | 28 | return options 29 | } 30 | -------------------------------------------------------------------------------- /lib/utils/options.js: -------------------------------------------------------------------------------- 1 | const consola = require('consola') 2 | 3 | const defaultHandler = 'connect' 4 | 5 | module.exports.defaultHandler = defaultHandler 6 | 7 | module.exports.isSupportedHandler = function isSupportedHandler (handler) { 8 | if (handler === undefined || handler === '') { 9 | return true 10 | } 11 | 12 | const supportedHandlers = [ 13 | 'full', 14 | 'connect', 15 | 'minimal' 16 | ] 17 | 18 | const isSupported = supportedHandlers.includes(handler) 19 | 20 | if (!isSupported) { 21 | consola.error(`Unsupported handler '${handler}', falling back to '${defaultHandler}'`) 22 | } 23 | 24 | return isSupported 25 | } 26 | 27 | module.exports.atob = data => Buffer.from(data, 'base64').toString('utf-8') 28 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | # Docs: https://www.netlify.com/docs/netlify-toml-reference/ 2 | [build] 3 | command = "yarn build" 4 | functions = "example/dist" 5 | publish = "example/dist-static/" 6 | 7 | # This redirect is broken in Netlify 8 | [[redirects]] 9 | from = "/*" 10 | to = "/.netlify/functions/nuxt" 11 | status = 200 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-lambda", 3 | "version": "0.5.8", 4 | "description": "Nuxt.js command to quickly & easily create an optimized lambda", 5 | "repository": "pimlie/nuxt-lambda", 6 | "license": "MIT", 7 | "contributors": [ 8 | "Pim (@pimlie)" 9 | ], 10 | "scripts": { 11 | "build": "scripts/build.sh", 12 | "lint": "eslint --ext .js .", 13 | "release": "yarn lint && standard-version" 14 | }, 15 | "bin": { 16 | "nuxt-lambda": "bin/nuxt-lambda.js", 17 | "test-lambda": "bin/test-lambda.js" 18 | }, 19 | "files": [ 20 | "bin", 21 | "lib", 22 | "src" 23 | ], 24 | "sideEffects": false, 25 | "dependencies": { 26 | "@netlify/zip-it-and-ship-it": "^5.7.4", 27 | "consola": "^2.15.3", 28 | "deepmerge": "^4.2.2", 29 | "fs-extra": "^10.0.0", 30 | "jszip": "^3.7.1", 31 | "klaw": "^4.0.1", 32 | "serverless-http": "^2.7.0", 33 | "temp": "^0.9.4", 34 | "webpack-merge": "^5.8.0", 35 | "webpack-node-externals": "^3.0.0", 36 | "webpack-plugin-replace": "^1.2.0" 37 | }, 38 | "devDependencies": { 39 | "@babel/eslint-parser": "^7.17.0", 40 | "@nuxtjs/axios": "^5.13.6", 41 | "@nuxtjs/eslint-config": "^8.0.0", 42 | "eslint": "^8.8.0", 43 | "minimist": "^1.2.5", 44 | "nuxt": "^2.15.8", 45 | "standard-version": "^9.3.2", 46 | "vue-uuid": "^2.0.2" 47 | }, 48 | "peerDependencies": { 49 | "nuxt": "^2.15.8" 50 | }, 51 | "publishConfig": { 52 | "access": "public" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This is a simple helper script 4 | # mostly because we have to copy statoc files 5 | # from 2 places for static deployment 6 | 7 | ./bin/nuxt-lambda.js example 8 | 9 | mkdir -p example/dist-static/_nuxt 10 | 11 | cp -a example/static/* example/dist-static/ 12 | cp -a example/.nuxt/dist/client/* example/dist-static/_nuxt 13 | -------------------------------------------------------------------------------- /src/entries/event.js: -------------------------------------------------------------------------------- 1 | export { default as handler } from 'nuxt-handler' 2 | -------------------------------------------------------------------------------- /src/entries/http.js: -------------------------------------------------------------------------------- 1 | import sls from 'serverless-http' 2 | import nuxtHandler from 'nuxt-handler' 3 | import binary from '../utils/binaryMimeTypes' 4 | 5 | export const handler = sls(nuxtHandler, { 6 | requestId: /* requestID */ false, 7 | binary 8 | }) 9 | -------------------------------------------------------------------------------- /src/handlers/connect.js: -------------------------------------------------------------------------------- 1 | import connect from 'connect' 2 | import { initVueRenderer } from '../renderer' 3 | import { prepareUrl } from '../utils' 4 | import nuxtMiddleware from '../middleware/nuxt' // => packages/server/src/middleware/nuxt 5 | import errorMiddleware from '../middleware/error' // => packages/server/src/middleware/error 6 | import compressionMiddleware from '../middleware/compression' 7 | import staticMiddleware from '../middleware/serve-static' 8 | 9 | const renderer = initVueRenderer() 10 | 11 | const app = connect() 12 | 13 | if (process.env.LAMBDA_USE_SERVESTATIC) { 14 | staticMiddleware(app, renderer.serverContext) 15 | } 16 | 17 | if (process.env.LAMBDA_USE_COMPRESSION) { 18 | app.use(compressionMiddleware(renderer.serverContext)) 19 | } 20 | 21 | app.use(nuxtMiddleware(renderer.serverContext)) 22 | app.use(errorMiddleware(renderer.serverContext)) 23 | 24 | const optimizedHandler = async (req, res) => { 25 | req.url = prepareUrl(req.url) 26 | 27 | await renderer.ready() 28 | 29 | return app(req, res) 30 | } 31 | 32 | export default optimizedHandler 33 | -------------------------------------------------------------------------------- /src/handlers/full.js: -------------------------------------------------------------------------------- 1 | import config from 'nuxt.config.js' 2 | import { Nuxt } from '@nuxt/core' 3 | import { prepareUrl, fixConfig } from '../utils' 4 | 5 | const nuxt = new Nuxt(config) 6 | 7 | const fullHandler = async (req, res) => { 8 | fixConfig(config) 9 | 10 | req.url = prepareUrl(req.url) 11 | 12 | await nuxt.ready() 13 | 14 | return nuxt.server.app(req, res) 15 | } 16 | 17 | export default fullHandler 18 | -------------------------------------------------------------------------------- /src/handlers/minimal.js: -------------------------------------------------------------------------------- 1 | import { initVueRenderer } from '../renderer' 2 | import nuxtMiddleware from '../middleware/nuxt' // => packages/server/src/middleware/nuxt 3 | import errorMiddleware from '../middleware/error' // => packages/server/src/middleware/error 4 | 5 | import { createRequest, createResponse, compressionMiddleware } from '../utils' 6 | 7 | const renderer = initVueRenderer() 8 | 9 | const minimalHandler = async (event, context) => { 10 | const req = createRequest(event, context) 11 | const res = createResponse(event, context) 12 | 13 | const next = err => errorMiddleware(renderer.serverContext)(err || {}, req, res) 14 | 15 | await renderer.ready() 16 | 17 | const body = await Promise.race([ 18 | nuxtMiddleware(renderer.serverContext)(req, res, next), 19 | res.ready() 20 | ]) 21 | 22 | if (process.env.LAMBDA_USE_COMPRESSION && body) { 23 | const compressedBody = await compressionMiddleware(renderer.serverContext)(req, res, body) 24 | if (compressedBody) { 25 | return res.format(compressedBody, true) 26 | } 27 | } 28 | 29 | return res.format(body) 30 | } 31 | 32 | export default minimalHandler 33 | -------------------------------------------------------------------------------- /src/middleware/compression.js: -------------------------------------------------------------------------------- 1 | import compression from 'compression' 2 | 3 | export default ({ options, nuxt }) => function compressionMiddleware (req, res, next) { 4 | const { compressor } = options.render 5 | 6 | // Compression middleware for production 7 | if (options.dev || !compressor) { 8 | return 9 | } 10 | 11 | if (typeof compressor === 'object') { 12 | // If only setting for `compression` are provided, require the module and insert 13 | compression(compressor)(req, res, next) 14 | } else if (compressor) { 15 | // Else, require own compression middleware if compressor is actually truthy 16 | compressor(req, res, next) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/middleware/error.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import fs from 'fs-extra' 3 | // import consola from 'consola' 4 | 5 | import Youch from '@nuxtjs/youch' 6 | 7 | export default ({ resources, options }) => async function errorMiddleware (err, req, res, next) { 8 | // ensure statusCode, message and name fields 9 | 10 | const error = { 11 | statusCode: err.statusCode || 500, 12 | message: err.message || 'Nuxt Server Error', 13 | name: !err.name || err.name === 'Error' ? 'NuxtServerError' : err.name 14 | } 15 | 16 | const sendResponse = (content, type = 'text/html') => { 17 | // Set Headers 18 | res.statusCode = error.statusCode 19 | res.statusMessage = error.name 20 | res.setHeader('Content-Type', type + '; charset=utf-8') 21 | res.setHeader('Content-Length', Buffer.byteLength(content)) 22 | res.setHeader('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate') 23 | 24 | // Send Response 25 | res.end(content, 'utf-8') 26 | } 27 | 28 | // Check if request accepts JSON 29 | const hasReqHeader = (header, includes) => 30 | req.headers[header] && req.headers[header].toLowerCase().includes(includes) 31 | const isJson = 32 | hasReqHeader('accept', 'application/json') || 33 | hasReqHeader('user-agent', 'curl/') 34 | 35 | // Use basic errors when debug mode is disabled 36 | if (!options.debug) { 37 | // We hide actual errors from end users, so show them on server logs 38 | if (err.statusCode !== 404) { 39 | // eslint-disable-next-line no-console 40 | console.error(err) 41 | } 42 | // Json format is compatible with Youch json responses 43 | const json = { 44 | status: error.statusCode, 45 | message: error.message, 46 | name: error.name 47 | } 48 | if (isJson) { 49 | sendResponse(JSON.stringify(json, undefined, 2), 'text/json') 50 | return 51 | } 52 | const html = resources.errorTemplate(json) 53 | sendResponse(html) 54 | return 55 | } 56 | 57 | const errorFull = err instanceof Error 58 | ? err 59 | : typeof err === 'string' 60 | ? new Error(err) 61 | : new Error(err.message || JSON.stringify(err)) 62 | 63 | errorFull.name = error.name 64 | errorFull.statusCode = error.statusCode 65 | errorFull.stack = err.stack || undefined 66 | 67 | // Show stack trace 68 | const youch = new Youch( 69 | errorFull, 70 | req, 71 | readSourceFactory({ 72 | srcDir: options.srcDir, 73 | rootDir: options.rootDir, 74 | buildDir: options.buildDir 75 | }), 76 | options.router.base, 77 | true 78 | ) 79 | if (isJson) { 80 | const json = await youch.toJSON() 81 | sendResponse(JSON.stringify(json, undefined, 2), 'text/json') 82 | return 83 | } 84 | 85 | const html = await youch.toHTML() 86 | sendResponse(html) 87 | } 88 | 89 | const readSourceFactory = ({ srcDir, rootDir, buildDir }) => async function readSource (frame) { 90 | // Remove webpack:/// & query string from the end 91 | const sanitizeName = name => name ? name.replace('webpack:///', '').split('?')[0] : null 92 | frame.fileName = sanitizeName(frame.fileName) 93 | 94 | // Return if fileName is unknown 95 | if (!frame.fileName) { 96 | return 97 | } 98 | 99 | // Possible paths for file 100 | const searchPath = [ 101 | srcDir, 102 | rootDir, 103 | path.join(buildDir, 'dist', 'server'), 104 | buildDir, 105 | process.cwd() 106 | ] 107 | 108 | // Scan filesystem for real source 109 | for (const pathDir of searchPath) { 110 | const fullPath = path.resolve(pathDir, frame.fileName) 111 | const source = await fs.readFile(fullPath, 'utf-8').catch(() => null) 112 | if (source) { 113 | frame.contents = source 114 | frame.fullPath = fullPath 115 | if (path.isAbsolute(frame.fileName)) { 116 | frame.fileName = path.relative(rootDir, fullPath) 117 | } 118 | return 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/middleware/nuxt.js: -------------------------------------------------------------------------------- 1 | import generateETag from 'etag' 2 | import fresh from 'fresh' 3 | import devalue from 'devalue' 4 | // import consola from 'consola' 5 | import { normalizeURL } from 'ufo' 6 | 7 | const consola = console // eslint-disable-line no-console 8 | // import { getContext, TARGETS } from '@nuxt/utils' 9 | 10 | export default ({ options, nuxt, renderRoute, resources }) => async function nuxtMiddleware (req, res, next) { 11 | // Get context 12 | const context = { req, res } // getContext(req, res) 13 | 14 | try { 15 | const url = normalizeURL(req.url) 16 | res.statusCode = 200 17 | 18 | let result 19 | try { 20 | result = await renderRoute(url, context) 21 | } catch (err) { 22 | if (options.lambda.errorPage) { 23 | result = await renderRoute(options.lambda.errorPage, context) 24 | 25 | // If the error page was rendered correctly, replace the route in the nuxt context 26 | if (result && result.html) { 27 | const escapedErrorUrl = devalue(options.lambda.errorPage) 28 | const escapedSrcUrl = devalue(url) 29 | 30 | result.html = result.html.replace(`routePath:${escapedErrorUrl}`, `routePath:${escapedSrcUrl}`) 31 | } 32 | } else { 33 | throw err 34 | } 35 | } 36 | 37 | // If result is falsy, call renderLoading 38 | if (!result) { 39 | await nuxt.callHook('server:nuxt:renderLoading', req, res) 40 | return 41 | } 42 | 43 | await nuxt.callHook('render:route', url, result, context) 44 | const { 45 | html, 46 | cspScriptSrcHashes, 47 | error, 48 | redirected, 49 | preloadFiles 50 | } = result 51 | 52 | if (redirected /* && context.target !== TARGETS.static */) { 53 | await nuxt.callHook('render:routeDone', url, result, context) 54 | return html 55 | } 56 | if (error) { 57 | res.statusCode = context.nuxt.error.statusCode || 500 58 | } 59 | 60 | if (options.render.csp && cspScriptSrcHashes) { 61 | const { allowedSources, policies } = options.render.csp 62 | const isReportOnly = !!options.render.csp.reportOnly 63 | const cspHeader = isReportOnly ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy' 64 | 65 | res.setHeader(cspHeader, getCspString({ cspScriptSrcHashes, allowedSources, policies, isReportOnly })) 66 | } 67 | 68 | // Add ETag header 69 | if (!error && options.render.etag) { 70 | const { hash } = options.render.etag 71 | const etag = hash ? hash(html, options.render.etag) : generateETag(html, options.render.etag) 72 | if (fresh(req.headers, { etag })) { 73 | res.statusCode = 304 74 | await nuxt.callHook('render:beforeResponse', url, result, context) 75 | res.end() 76 | await nuxt.callHook('render:routeDone', url, result, context) 77 | return 78 | } 79 | res.setHeader('ETag', etag) 80 | } 81 | 82 | // HTTP2 push headers for preload assets 83 | if (!error && options.render.http2.push) { 84 | // Parse resourceHints to extract HTTP.2 prefetch/push headers 85 | // https://w3c.github.io/preload/#server-push-http-2 86 | const { shouldPush, pushAssets } = options.render.http2 87 | const { publicPath } = resources.clientManifest 88 | 89 | const links = pushAssets 90 | ? pushAssets(req, res, publicPath, preloadFiles) 91 | : defaultPushAssets(preloadFiles, shouldPush, publicPath, options) 92 | 93 | // Pass with single Link header 94 | // https://blog.cloudflare.com/http-2-server-push-with-multiple-assets-per-link-header 95 | // https://www.w3.org/Protocols/9707-link-header.html 96 | if (links.length > 0) { 97 | res.setHeader('Link', links.join(', ')) 98 | } 99 | } 100 | 101 | // Send response 102 | res.setHeader('Content-Type', 'text/html; charset=utf-8') 103 | res.setHeader('Accept-Ranges', 'none') // #3870 104 | res.setHeader('Content-Length', Buffer.byteLength(html)) 105 | await nuxt.callHook('render:beforeResponse', url, result, context) 106 | res.end(html, 'utf8') 107 | await nuxt.callHook('render:routeDone', url, result, context) 108 | return html 109 | } catch (err) { 110 | if (context && context.redirected) { 111 | consola.error(err) 112 | return err 113 | } 114 | 115 | if (err.name === 'URIError') { 116 | err.statusCode = 400 117 | } 118 | next(err) 119 | } 120 | } 121 | 122 | const defaultPushAssets = (preloadFiles, shouldPush, publicPath, options) => { 123 | if (shouldPush && options.dev) { 124 | consola.warn('http2.shouldPush is deprecated. Use http2.pushAssets function') 125 | } 126 | 127 | const links = [] 128 | preloadFiles.forEach(({ file, asType, fileWithoutQuery, modern }) => { 129 | // By default, we only preload scripts or css 130 | if (!shouldPush && asType !== 'script' && asType !== 'style') { 131 | return 132 | } 133 | 134 | // User wants to explicitly control what to preload 135 | if (shouldPush && !shouldPush(fileWithoutQuery, asType)) { 136 | return 137 | } 138 | 139 | const { crossorigin } = options.render 140 | const cors = `${crossorigin ? ` crossorigin=${crossorigin};` : ''}` 141 | // `modulepreload` rel attribute only supports script-like `as` value 142 | // https://html.spec.whatwg.org/multipage/links.html#link-type-modulepreload 143 | const rel = modern && asType === 'script' ? 'modulepreload' : 'preload' 144 | 145 | links.push(`<${publicPath}${file}>; rel=${rel};${cors} as=${asType}`) 146 | }) 147 | return links 148 | } 149 | 150 | const getCspString = ({ cspScriptSrcHashes, allowedSources, policies, isReportOnly }) => { 151 | const joinedHashes = cspScriptSrcHashes.join(' ') 152 | const baseCspStr = `script-src 'self' ${joinedHashes}` 153 | const policyObjectAvailable = typeof policies === 'object' && policies !== null && !Array.isArray(policies) 154 | 155 | if (Array.isArray(allowedSources) && allowedSources.length) { 156 | return isReportOnly && policyObjectAvailable && !!policies['report-uri'] ? `${baseCspStr} ${allowedSources.join(' ')}; report-uri ${policies['report-uri']};` : `${baseCspStr} ${allowedSources.join(' ')}` 157 | } 158 | 159 | if (policyObjectAvailable) { 160 | const transformedPolicyObject = transformPolicyObject(policies, cspScriptSrcHashes) 161 | return Object.entries(transformedPolicyObject).map(([k, v]) => `${k} ${Array.isArray(v) ? v.join(' ') : v}`).join('; ') 162 | } 163 | 164 | return baseCspStr 165 | } 166 | 167 | const transformPolicyObject = (policies, cspScriptSrcHashes) => { 168 | const userHasDefinedScriptSrc = policies['script-src'] && Array.isArray(policies['script-src']) 169 | 170 | const additionalPolicies = userHasDefinedScriptSrc ? policies['script-src'] : [] 171 | 172 | // Self is always needed for inline-scripts, so add it, no matter if the user specified script-src himself. 173 | const hashAndPolicyList = cspScriptSrcHashes.concat('\'self\'', additionalPolicies) 174 | 175 | return { ...policies, 'script-src': hashAndPolicyList } 176 | } 177 | -------------------------------------------------------------------------------- /src/middleware/serve-static.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import serveStatic from 'serve-static' 3 | 4 | const r = p => path.join(__dirname, p) 5 | 6 | export default (app, { options }) => { 7 | app.use(serveStatic(r('static'))) 8 | app.use(options.build.publicPath, serveStatic(r(options.clientDir))) 9 | } 10 | -------------------------------------------------------------------------------- /src/renderer.js: -------------------------------------------------------------------------------- 1 | import { VueRenderer } from '@nuxt/vue-renderer' 2 | import options from 'nuxt.config.js' 3 | import { fixConfig } from './utils' 4 | 5 | export function initVueRenderer () { 6 | fixConfig(options) 7 | 8 | const nuxt = { 9 | options, 10 | hook: () => {}, 11 | callHook: () => {} 12 | } 13 | 14 | const resources = {} 15 | 16 | const serverContext = { 17 | nuxt, 18 | globals: options.globals, 19 | options, 20 | resources, 21 | renderRoute: undefined 22 | } 23 | 24 | const renderer = new VueRenderer(serverContext) 25 | serverContext.renderRoute = (...args) => renderer.renderRoute(...args) 26 | 27 | return renderer 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/binaryMimeTypes.js: -------------------------------------------------------------------------------- 1 | // See: https://forums.aws.amazon.com/thread.jspa?messageID=668306#668306 2 | export default [ 3 | 'application/javascript', 4 | 'application/json', 5 | 'application/octet-stream', 6 | 'application/xml', 7 | 'font/eot', 8 | 'font/opentype', 9 | 'font/otf', 10 | 'image/jpeg', 11 | 'image/png', 12 | 'image/svg+xml', 13 | 'text/comma-separated-values', 14 | 'text/css', 15 | 'text/html', 16 | 'text/javascript', 17 | 'text/plain', 18 | 'text/text', 19 | 'text/xml' 20 | ] 21 | -------------------------------------------------------------------------------- /src/utils/compression.js: -------------------------------------------------------------------------------- 1 | import zlib from 'zlib' 2 | import { debug } from './debug' 3 | 4 | // This method is based on https://github.com/expressjs/compression/blob/master/index.js 5 | // but without streaming stuff 6 | 7 | const cacheControlNoTransformRegExp = /(?:^|,)\s*?no-transform\s*?(?:,|$)/ 8 | 9 | export default () => async function compressionMiddleware (req, res, body) { 10 | const cacheControl = req.getHeader('Cache-Control') 11 | if (cacheControl && cacheControlNoTransformRegExp.test(cacheControl)) { 12 | debug('Not compressing, cache-control', cacheControl) 13 | return 14 | } 15 | 16 | res.setHeader('Vary', 'Accept-Encoding') 17 | 18 | if (req.method === 'HEAD') { 19 | debug('Not compressing, method', req.method) 20 | return 21 | } 22 | 23 | const currentEncoding = req.getHeader('Content-Encoding') || 'identity' 24 | if (currentEncoding !== 'identity') { 25 | debug('Not compressing, encoding', currentEncoding) 26 | return 27 | } 28 | 29 | let encodings = req.getHeader('Accept-Encoding') 30 | if (encodings) { 31 | encodings = encodings.split(',').map(s => s.trim()).filter(Boolean) 32 | } else { 33 | encodings = [] 34 | } 35 | 36 | let method = '' 37 | if (encodings.includes('gzip')) { 38 | method = 'gzip' 39 | } else if (encodings.includes('deflate')) { 40 | method = 'deflate' 41 | } 42 | 43 | if (method) { 44 | try { 45 | const compressedBody = await new Promise((resolve, reject) => { 46 | zlib[method](body, (err, result) => { 47 | if (err) { 48 | reject(err) 49 | } 50 | 51 | resolve(result) 52 | }) 53 | }) 54 | 55 | res.setHeader('Content-Encoding', method) 56 | res.removeHeader('Content-Length') 57 | 58 | return Buffer.from(compressedBody).toString('base64') 59 | } catch (err) { 60 | debug('Not compressing, error', err) 61 | // TODO: log 62 | } 63 | } else { 64 | debug('Not compressing, no encoding') 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/utils/config.js: -------------------------------------------------------------------------------- 1 | 2 | export function fixConfig (options) { 3 | const bundleRenderer = options.render.bundleRenderer 4 | 5 | /* eslint-disable no-eval */ 6 | eval(`bundleRenderer.shouldPreload = ${bundleRenderer.shouldPreload}`) 7 | eval(`bundleRenderer.shouldPrefetch = ${bundleRenderer.shouldPrefetch}`) 8 | /* eslint-enable no-eval */ 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/debug.js: -------------------------------------------------------------------------------- 1 | export function debug () { 2 | if (process.env.LAMBDA_DEBUG) { 3 | // eslint-disable-next-line no-console 4 | console.log(...arguments) 5 | } 6 | } 7 | 8 | export function createDebugProxy (obj, name) { 9 | return new Proxy(obj, { 10 | get: (target, prop) => { 11 | if (typeof target[prop] === 'function') { 12 | // eslint-disable-next-line no-console 13 | console.log(`${name}.get:`, prop, 'function') 14 | 15 | return new Proxy(target[prop], { 16 | apply (fn, fnThis, fnArgs) { 17 | // eslint-disable-next-line no-console 18 | console.log(`${name}.call:`, prop, fnArgs.join(', ')) 19 | return fn.apply(fnThis, fnArgs) 20 | } 21 | }) 22 | } 23 | 24 | // eslint-disable-next-line no-console 25 | console.log(`${name}.get:`, prop, target[prop]) 26 | 27 | return target[prop] 28 | }, 29 | set: (target, prop, value) => { 30 | // eslint-disable-next-line no-console 31 | console.log(`${name}.set:`, prop, value, target[prop]) 32 | target[prop] = value 33 | return true 34 | } 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export { default as compressionMiddleware } from './compression' 2 | export * from './config' 3 | export * from './debug' 4 | export * from './request' 5 | export * from './response' 6 | export * from './url' 7 | -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | import url from 'url' 2 | import { createDebugProxy } from './debug' 3 | import { prepareUrl } from './url' 4 | 5 | export function createRequest (event, context) { 6 | const remoteAddress = event.requestContext ? event.requestContext.identity.sourceIp : null 7 | const method = event.httpMethod 8 | const headers = requestHeaders(event) 9 | const body = requestBody(event) 10 | 11 | const req = { 12 | method, 13 | remoteAddress, 14 | url: url.format({ 15 | pathname: prepareUrl(event.path), 16 | query: event.multiValueQueryStringParameters || event.queryStringParameters 17 | }), 18 | headers, 19 | body, 20 | getHeader: (name) => { 21 | return headers[name.toLowerCase()] 22 | } 23 | } 24 | 25 | if (process.env.LAMBDA_DEBUG) { 26 | return createDebugProxy(req, 'req') 27 | } 28 | 29 | return req 30 | } 31 | 32 | export function requestHeaders (event) { 33 | const headers = {} 34 | 35 | for (const name in event.headers) { 36 | headers[name.toLowerCase()] = event.headers[name] 37 | } 38 | 39 | return headers 40 | } 41 | 42 | export function requestBody (event) { 43 | const type = typeof event.body 44 | 45 | if (Buffer.isBuffer(event.body)) { 46 | return event.body 47 | } else if (type === 'string') { 48 | return Buffer.from(event.body, event.isBase64Encoded ? 'base64' : 'utf8') 49 | } else if (type === 'object') { 50 | return Buffer.from(JSON.stringify(event.body)) 51 | } 52 | 53 | throw new Error(`Unexpected event.body type: ${typeof event.body}`) 54 | } 55 | -------------------------------------------------------------------------------- /src/utils/response.js: -------------------------------------------------------------------------------- 1 | import { createDebugProxy } from './debug' 2 | 3 | export function createResponse (event, context) { 4 | let statusCode = 200 5 | const headers = {} 6 | 7 | let resolveReady 8 | 9 | const res = { 10 | // used by compression middleware 11 | _implicitHeader: _ => _, 12 | setHeader: (name, value) => { 13 | headers[name.toLowerCase()] = value 14 | }, 15 | getHeader: (name) => { 16 | return headers[name.toLowerCase()] 17 | }, 18 | removeHeader: (name) => { 19 | delete headers[name.toLowerCase()] 20 | }, 21 | writeHead: (_statusCode, reason, obj) => { 22 | statusCode = _statusCode 23 | 24 | const _headers = typeof reason === 'string' 25 | ? obj 26 | : reason 27 | 28 | for (const name in _headers) { 29 | res.setHeader(name, _headers[name]) 30 | } 31 | }, 32 | ready: () => { 33 | return new Promise((resolve) => { 34 | resolveReady = resolve 35 | }) 36 | }, 37 | end: (html, encoding) => { 38 | if (resolveReady) { 39 | resolveReady(html || '') 40 | } 41 | }, 42 | format: (body, isBase64Encoded = false) => ({ 43 | statusCode, 44 | headers, 45 | isBase64Encoded, 46 | body 47 | }) 48 | } 49 | 50 | if (process.env.LAMBDA_DEBUG) { 51 | return createDebugProxy(res, 'res') 52 | } 53 | 54 | return res 55 | } 56 | -------------------------------------------------------------------------------- /src/utils/url.js: -------------------------------------------------------------------------------- 1 | 2 | export function prepareUrl (url) { 3 | if (process.env.NETLIFY) { 4 | if (url.startsWith('/.netlify/functions/nuxt')) { 5 | url = url.slice(24) 6 | } 7 | 8 | if (!url) { 9 | return '/' 10 | } 11 | } 12 | 13 | return url 14 | } 15 | --------------------------------------------------------------------------------