├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── examples
└── nextjs-v14
│ ├── .eslintrc.json
│ ├── README.md
│ ├── __tests__
│ └── pages
│ │ └── index.test.tsx
│ ├── app
│ ├── favicon.ico
│ ├── globals.css
│ └── layout.tsx
│ ├── function.js
│ ├── jest.config.js
│ ├── next.config.js
│ ├── package-lock.json
│ ├── package.json
│ ├── pages
│ ├── _app.tsx
│ ├── api
│ │ └── hello.ts
│ ├── index.tsx
│ ├── optimized-images
│ │ └── index.tsx
│ └── ssr
│ │ ├── SSR.tsx
│ │ └── index.tsx
│ ├── postcss.config.js
│ ├── public
│ ├── images
│ │ ├── sample.avif
│ │ ├── sample.gif
│ │ ├── sample.jpg
│ │ ├── sample.png
│ │ └── sample.webp
│ ├── next.svg
│ └── vercel.svg
│ ├── styles
│ ├── Home.module.css
│ └── globals.css
│ ├── tailwind.config.ts
│ ├── terraform
│ ├── main.tf
│ ├── outputs.tf
│ ├── providers.tf
│ └── variables.tf
│ └── tsconfig.json
├── main.tf
├── modules
├── distribution
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
├── image-optimization
│ ├── main.tf
│ ├── outputs.tf
│ ├── variables.tf
│ └── versions.tf
├── public-assets-hosting
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
├── server-side-rendering
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
└── static-assets-hosting
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
├── original-license.md
├── outputs.tf
├── packages
├── ns-build
│ ├── LICENSE
│ ├── README.md
│ ├── bin
│ │ └── ns-build.sh
│ ├── package-lock.json
│ ├── package.json
│ ├── server.js
│ ├── server.ts
│ └── tsconfig.json
├── ns-img-opt
│ ├── LICENSE
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── scripts
│ │ └── pre-publish.sh
│ ├── src
│ │ ├── constants.ts
│ │ ├── helpers.ts
│ │ └── index.ts
│ └── tsconfig.json
└── ns-img-rdr
│ ├── LICENSE
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── scripts
│ └── pre-publish.sh
│ ├── src
│ └── index.ts
│ └── tsconfig.json
├── variables.tf
├── versions.tf
└── visuals
├── cache.drawio
├── cache.webp
├── diagram.excalidraw
├── distribution.drawio
├── distribution.webp
├── module.drawio
└── module.webp
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | **/node_modules
5 | **/.pnp
6 | .pnp.js
7 |
8 | # testing
9 | **/coverage
10 |
11 | # next.js
12 | **/.next/
13 | **/.swc/
14 | **/.next-tf/
15 | **/out/
16 |
17 | # production
18 | **/build
19 |
20 | # misc
21 | .DS_Store
22 | *.pem
23 |
24 | # debug
25 | npm-debug.log*
26 | yarn-debug.log*
27 | yarn-error.log*
28 | .pnpm-debug.log*
29 |
30 | # local env files
31 | .env*.local
32 |
33 | # vercel
34 | .vercel
35 |
36 | # typescript
37 | *.tsbuildinfo
38 | next-env.d.ts
39 |
40 | #terraform
41 | **/.terraform/*
42 | **/tf-plan
43 | .terraform.lock.hcl
44 | .terraform
45 | examples/nextjs-v13/terraform/.terraform*
46 | env.tfvars
47 |
48 | **/.DS_Store
49 |
50 | **/deployments/
51 | **/standalone/
52 |
53 | packages/**/*.zip
54 |
55 | .idea/
56 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 |
6 |
7 |
8 |
9 | ## [v1.8.1] - 2025-04-22
10 |
11 | - Bugfix: cached paths
12 |
13 | ## [v1.8.0] - 2025-04-16
14 |
15 | - Use AWS-managed cache policies
16 | - Drop `referrer` in favor of `host`
17 | - Switch `301` to `302`
18 | - Add `viewer-request` cloudfront function to the distribution
19 | - Add `custom_cache_policy_id` tf variable
20 |
21 | ## [v1.7.1] - 2025-04-11
22 |
23 | - Bugfix: SPA (followup)
24 |
25 | ## [v1.7.0] - 2025-04-10
26 |
27 | - Bugfix: SPA
28 |
29 | ## [v1.6.1] - 2025-03-20
30 |
31 | - Bugfix: SPA for dynamic routes
32 |
33 | ## [v1.6.0] - 2025-03-19
34 |
35 | - Bugfix: SPA for dynamic routes
36 |
37 | ## [v1.5.1] - 2025-03-14
38 |
39 | - Bugfix: SPA for static routes
40 |
41 | ## [v1.5.0] - 2025-03-14
42 |
43 | - Bugfix: SPA for static routes
44 |
45 | ## [v1.4.0] - 2025-03-03
46 |
47 | - Feature: enabled compression for cached paths
48 |
49 | ## [v1.3.2] - 2025-02-11
50 |
51 | - Bugfix: add function assosiation for cached paths
52 |
53 | ## [v1.3.1] - 2025-02-11
54 |
55 | - Bugfix: reference to resourse in distribution module
56 |
57 | ## [v1.3.0] - 2025-02-11
58 |
59 | - Add `cloudfront_cached_paths` tf variable
60 |
61 | ## [v1.2.0] - 2024-12-23
62 |
63 | - Add `enable_image_optimization` tf variable
64 |
65 | ## [v1.1.2] - 2024-10-11
66 |
67 | - Add `delete_resized_versions` tf variable
68 |
69 | ## [v1.1.1] - 2024-10-11
70 |
71 | - Bugfix: having multiple images to pre-build was breaking terraform
72 |
73 | ## [v1.1.0] - 2024-10-11
74 |
75 | - Add `pre_resize_images` tf variable
76 | - Image optimization now returns speficic widths (16, 32, 64, 128, 256, 512, 1024)
77 | - Image quality for optimized images is now fix to 75%
78 | - On deployment, all image versions are optimized & stores to s3
79 | - Image optimization now redirects to `resized-assets/`, which serves already optimizated images
80 |
81 | ## [v1.0.3] - 2024-06-28
82 |
83 | - Add required dependencies for the module
84 |
85 | ## [v1.0.2] - 2024-06-07
86 |
87 | - Mark `next` as peer dependency
88 | - Silence zip process
89 | - Remove `app/` route example
90 | - pump version
91 |
92 | ## [v1.0.0] - 2024-05-15
93 |
94 | - Finally, we reached the point that we can consider this module as `stable`, while supporting the most popular **Next.js** feature for **pages/** router
95 |
96 | ## [v0.4.6] - 2024-05-15
97 |
98 | - Pump default nodejs runtime to v18
99 | - Completely disable caching for ssr paths
100 | - Fix redirect issue for public assets
101 |
102 | ## [v0.4.5] - 2024-05-15
103 |
104 | - Bugfix: some default image types were breaking ssr lambda
105 |
106 | ## [v0.4.3] - 2024-05-15
107 |
108 | - Add `custom_image_types` tf variable
109 |
110 | ## [v0.4.2] - 2024-05-14
111 |
112 | - Add `create_cloudfront_invalidation` tf variable
113 |
114 | ## [v0.4.1] - 2024-05-14
115 |
116 | - Bugfix: disable caching for server-side rendering
117 | - Add cloudfront invalidation after every deployment
118 |
119 | ## [v0.4.0] - 2024-05-14
120 |
121 | - Support `context` argument on `getServerSideProps()`, when using the `pages/` router
122 |
123 | ## [v0.3.9] - 2024-05-13
124 |
125 | - Support all components for rendering images (`'webp', 'jpeg', 'png', 'gif', 'avif', 'svg'`)
126 |
127 | ## [v0.3.8] - 2024-04-30
128 |
129 | - Add `x-forwarded-host` headers to the request info
130 | - Deprecate `override_host_header` variable
131 |
132 | ## [v0.3.7] - 2024-04-29
133 |
134 | - Add `cloudfront_function_associations` variable to associate cloudfront functions with the defaulf distribution
135 | - Add `override_host_header` variable to enabled overriding of host header, by the custom domain
136 | - Add `wait_for_distribution_deployment` variable to stop waiting for the distribution status to change from `InProgress` to `Deployed`
137 |
138 | ## [v0.3.6] - 2024-04-25
139 |
140 | - Bug fix next handler
141 |
142 | ## [v0.3.5] - 2024-04-25
143 |
144 | - Bug fix next handler
145 |
146 | ## [v0.3.4] - 2024-04-25
147 |
148 | - Add `show_debug_logs` variable to enabled debug logs
149 |
150 | ## [v0.3.3] - 2024-04-25
151 |
152 | - Add `use_default_server_side_props_handler` variable to enabled usage of the default server side props handler, instead of the our custom one
153 |
154 | ## [v0.3.2] - 2024-04-23
155 |
156 | Updates:
157 |
158 | - Rename SSR Lambda
159 | - Pump default nodejs runtime to v18
160 |
161 | **Breaking Changes**
162 |
163 | - Require an aws provider for global region (must be `us-east-1`)
164 |
165 | ## [v0.3.1] - 2024-03-26
166 |
167 | - Fix git urls
168 |
169 | ## [v0.3.0] - 2024-03-09
170 |
171 | - Set up forked repo, module & packages
172 |
173 | ## [v0.2.20] - 2023-12-13
174 |
175 | - Fix broken SPA feature
176 |
177 | ## [v0.2.19] - 2023-12-06
178 |
179 | - Option to copy all packages directly into the next_lambda
180 |
181 | ## [v0.2.18] - 2023-12-04
182 |
183 | - Support remote images
184 |
185 | ## [v0.2.17] - 2023-11-23
186 |
187 | Updates:
188 |
189 | - add & update customization options of the module through terraform variables terraform-aws-nextjs-serverless/pull/42)
190 | - store next_lambda source zip in s3
191 | - expand image examples
192 | - update cache diagram
193 | - update dependencies versions to latest
194 |
195 | **Breaking Changes**
196 |
197 | - `lambda_memory_size` is replaced by `next_lambda_memory_size` & `image_optimization_lambda_memory_size`
198 | - `runtime` is replaced by `next_lambda_runtime` & `image_optimization_runtime`
199 | - `logs_retention` is replaced by `next_lambda_logs_retention` & `image_optimization_logs_retention`
200 |
201 | ## [v0.2.16] - 2023-11-20
202 |
203 | - Bugfix: next_lambda_layer was not updating
204 | - `ns-build`: Option to copy some packages directly into the next_lambda, since in some rare cases import from the layer fails
205 |
206 | ## [v0.2.13] - 2023-11-16
207 |
208 | - Add docs about dependancies
209 | - Bugfix: Image redirection had issues with deeply nested files
210 |
211 | ## [v0.2.11] - 2023-11-03
212 |
213 | - Add SSR example
214 | - Add CHANGELOG docs
215 | - Fix: Set a version for every package used by `ns-build`
216 |
217 | ## [v0.2.10] - 2023-11-01
218 |
219 | **Add License**
220 |
221 | - Add License for the module
222 | - Add License for the modulethe packages
223 |
224 | ## [v0.2.8] - 2023-11-01
225 |
226 | **Intialize Terraform Tests**
227 |
228 | - Add terraform tests using TerraTest
229 | - Improve visualizations
230 | - Improved Documentation
231 |
232 | ## [v0.2.6] - 2023-10-26
233 |
234 | - Improve visualizations
235 |
236 | ## [v0.2.5] - 2023-10-26
237 |
238 | - Add visualization diagrams for the module and the distribution
239 |
240 | ## [v0.2.4] - 2023-10-23
241 |
242 | - Fix: S3 cross-region access for Image Optimization on Lambda@Edge
243 |
244 | ## [v0.2.1] - 2023-10-23
245 |
246 | **Improve Image Optimization**
247 |
248 | - Image Optimization: fetch images from S3, instead of public S3 URL
249 |
250 | ## [v0.2.0] - 2023-10-23
251 |
252 | **Restructure Modules**
253 |
254 | - Restructure the Modules' structure to support future plans
255 | - Release a functional version of Image Optimization feature
256 |
257 | ## [v0.1.1] - 2023-10-20
258 |
259 | - Fix: lambda@edge source code read
260 |
261 | ## [v0.1.0] - 2023-10-20
262 |
263 | **Intial Image Optimization Feature Releaze**
264 |
265 | - Serve public assets using Lambda@Edge to optimize size, file type, quality
266 |
267 | ## [v0.0.7] - 2023-09-12
268 |
269 | - Fix cloudwatch log group name mis-configuration
270 |
271 | ## [v0.0.6] - 2023-09-12
272 |
273 | - Add next_lambda_policy_statements option
274 |
275 | ## [v0.0.4] - 2023-09-12
276 |
277 | - Change: Store next_lambda layer in S3, instead of uploading it directly
278 |
279 | ## [v0.0.2] - 2023-09-07
280 |
281 | - Add the custom domain option for the CloudFront distribution terraform-aws-nextjs-serverless/pull/5)
282 | - Add the option for next_lambda env vars
283 | - Fix BucketACL issue
284 |
285 | ## [v0.0.1] - 2023-09-04
286 |
287 | **Initial Release**
288 |
289 | - Serve next.js app with AWS Lambda & API Gateway
290 | - Serve static assets with CloudFront & S3
291 | - Serve public assets with CloudFront & S3
292 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ### Feel free to contribute to this module.
4 |
5 | As a general advice it is always a good idea to raise an [issue](https://github.com/emyriounis/terraform-aws-nextjs-serverless/issues) before creating a new pull request. This ensures that we don't have to reject [pull requests](https://github.com/emyriounis/terraform-aws-nextjs-serverless/pulls) that are not aligning with our roadmap and not wasting your valuable time.
6 |
7 | ## Reporting Bugs
8 |
9 | If you encounter a bug, please open an [issue](https://github.com/emyriounis/terraform-aws-nextjs-serverless/issues) on the GitHub repository. Be sure to include as much information as possible to help us understand and reproduce the problem.
10 |
11 | ## Feature Requests
12 |
13 | If you have an idea for a new feature or enhancement, please feel free to open an [issue](https://github.com/emyriounis/terraform-aws-nextjs-serverless/issues) and describe it. We'd love to hear your suggestions!
14 |
15 | ## Testing
16 |
17 | Please check our [testing guidelines](https://github.com/emyriounis/terraform-aws-nextjs-serverless/blob/main/tests).
18 |
19 | ## Development Workflow
20 |
21 | ### ns-build package
22 |
23 | 1. Edit the `examples/[selected_example]/node_modules/ns-build/bin/ns-build.sh` file.
24 | 2. Run `npm run ns-build` to build the Next.js app using the custom script.
25 | 3. Re-deploy the module.
26 | 4. Make sure your changes are applied and existing functionality is not broken.
27 | 5. Copy the updates you made to the `packages/ns-build/bin/ns-build.sh` file.
28 |
29 | ### ns-img-rdr packages
30 |
31 | 1. Make your changes
32 | 2. Run `npm run prepare-lambda`
33 | 3. Copy `packages/ns-img-rdr/source.zip` to `examples/nextjs-v13/deployments/ns-img-rdr/source.zip`
34 | 4. Re-deploy the module.
35 | 5. Make sure your changes are applied and existing functionality is not broken.
36 |
37 | ### ns-img-opt packages
38 |
39 | 1. Make your changes
40 | 2. Run `npm run prepare-lambda`
41 | 3. Copy `packages/ns-img-opt/source.zip` to `examples/nextjs-v13/deployments/ns-img-opt/source.zip`
42 | 4. Re-deploy the module.
43 | 5. Make sure your changes are applied and existing functionality is not broken.
44 |
45 | ### next_serverless module
46 |
47 | 1. Switch to the local source
48 |
49 | ```diff
50 | module "tf_next" {
51 | - source = "emyriounis/nextjs-serverless/aws"
52 | - version = "1.8.1"
53 | + source = "../../../"
54 | ...
55 | }
56 | ```
57 |
58 | 2. Make your changes to the terraform files
59 | 3. Validate them (`terraform validate`)
60 | 4. Re-deploy the module.
61 | 5. Make sure your changes are applied and existing functionality is not broken.
62 | 6. Follow existing format (`terraform fmt -recursive`)
63 |
64 | ## Issues
65 |
66 | If you face any problem, feel free to open an [issue](https://github.com/emyriounis/terraform-aws-nextjs-serverless/issues).
67 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | the copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor to discuss and improve the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by the Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributors that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, the Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assuming any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | Copyright 2024\
179 | Eleftherios Myriounis
180 |
181 | Licensed under the Apache License, Version 2.0 (the "License");
182 | you may not use this file except in compliance with the License.
183 | You may obtain a copy of the License at
184 |
185 | http://www.apache.org/licenses/LICENSE-2.0
186 |
187 | Unless required by applicable law or agreed to in writing, software
188 | distributed under the License is distributed on an "AS IS" BASIS,
189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190 | See the License for the specific language governing permissions and
191 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Terraform Next.js module for AWS
2 |
3 | Zero-Config Terraform Module to deploy Next.js Apps on AWS using Serverless resources
4 |
5 | ## Usage
6 |
7 | ### Dependencies
8 |
9 | - Node: 16+
10 | - Terraform: 1.6.3+
11 | - bash
12 | - zip
13 |
14 | ### Prepare
15 |
16 | Add the following dependencies & script to your _package.json_ file
17 |
18 | ```json
19 | package.json
20 |
21 | {
22 | "scripts": {
23 | "ns-build": "ns-build",
24 | ...
25 | },
26 | "dependencies": {
27 | "ns-build": "latest",
28 | "next": "^14",
29 | ...
30 | },
31 | ...
32 | }
33 | ```
34 |
35 | Add the `output: "standalone"` option to the _next.config.js_ file
36 |
37 | ```json
38 | next.config.js
39 |
40 | const nextConfig = {
41 | ...
42 | "output": "standalone",
43 | ...
44 | }
45 |
46 | module.exports = nextConfig
47 |
48 | ```
49 |
50 | ### Create Terraform deployment
51 |
52 | Check it on [Terraform Registry](https://registry.terraform.io/modules/emyriounis/nextjs-serverless) for more details.
53 |
54 | _Ensure that the deployment name is unique since its used for creating s3 buckets._
55 |
56 | ```
57 | main.tf
58 |
59 | provider "aws" {
60 | region = "eu-central-1" #customize your region
61 | }
62 |
63 | provider "aws" {
64 | alias = "global_region"
65 | region = "us-east-1" #must be us-east-1
66 | }
67 |
68 | module "next_serverless" {
69 | source = "emyriounis/nextjs-serverless/aws"
70 |
71 | deployment_name = "nextjs-serverless" #needs to be unique since it will create s3 buckets
72 | region = "eu-central-1" #customize your region
73 | base_dir = "./" #The base directory of the next.js app
74 | }
75 |
76 | output "next_serverless" {
77 | value = module.next_serverless
78 | }
79 | ```
80 |
81 | ### Deployment
82 |
83 | Build the Next.js Code and deploy
84 |
85 | ```bash
86 | npm i ns-build
87 | npm run ns-build
88 |
89 | terraform init
90 | terraform apply
91 | ```
92 |
93 | ## Architecture
94 |
95 | ### Module
96 |
97 | 
98 |
99 | ### Distribution
100 |
101 | 
102 |
103 | ### Cache
104 |
105 | 
106 |
107 | ## Examples
108 |
109 | - [Next.js v14](https://github.com/emyriounis/terraform-aws-nextjs-serverless/tree/main/examples/nextjs-v14) Complete example with SSR, API, static pages, image optimization & custom domain
110 |
111 | ## Known Issues
112 |
113 | - The `ns-build` _package's version_ must match the `next_serverless` _module's version_
114 | - The `app/` folder must be in the root directory (ex. not in the `src/` directory)
115 | - When destroying the `next_serverless` module, Lambda@Edge function need at least 15mins to be destroy, since they're [replicated functions](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-delete-replicas.html)
116 | - In some rare cases, some modules can not be imported by next_lambda (for unknown reasons). To solve this issue use `ns-build --copyAllPackages` to copy all the packages or `ns-build --packages-to-copy=package_1,package_2,package_3` to copy specific packages only
117 | - Currently, we support the `pages/` router for node v16, v18, v20 & `app/` router for node v16
118 |
119 | ## Contributing
120 |
121 | Feel free to improve this module. Our [contributing guidelines](https://github.com/emyriounis/terraform-aws-nextjs-serverless/tree/main/CONTRIBUTING.md) will help you get started.
122 |
123 | ## License
124 |
125 | Apache-2.0 - see [LICENSE](https://github.com/emyriounis/terraform-aws-nextjs-serverless/tree/main/LICENSE) for details.\
126 | Disclaimer: This module was originally developed by [me](https://github.com/emyriounis) during my time at Nexode Consulting GmbH.
127 |
--------------------------------------------------------------------------------
/examples/nextjs-v14/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/nextjs-v14/README.md:
--------------------------------------------------------------------------------
1 | ## Next.js v13 example
2 |
3 | - update providers,tf
4 | - update terraform.tfvars
5 |
6 |
7 | ```
8 | npm i
9 | npm run ns-build
10 | terraform apply ...
11 | ```
12 |
--------------------------------------------------------------------------------
/examples/nextjs-v14/__tests__/pages/index.test.tsx:
--------------------------------------------------------------------------------
1 | // pages/__tests__/index.test.js
2 | import { render, screen } from '@testing-library/react'
3 | import '@testing-library/jest-dom'
4 |
5 | import Home from '../../pages/index'
6 |
7 | describe('Index page', () => {
8 | it('renders welcome message', () => {
9 | render()
10 |
11 | const title = screen.getByText('Welcome to')
12 | const subtitle = screen.getByText('Get started by editing')
13 |
14 | expect(title).toBeInTheDocument()
15 | expect(subtitle).toBeInTheDocument()
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/examples/nextjs-v14/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emyriounis/terraform-aws-nextjs-serverless/f2a298c8509861e9976752cc9a52401a808075fc/examples/nextjs-v14/app/favicon.ico
--------------------------------------------------------------------------------
/examples/nextjs-v14/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --foreground-rgb: 0, 0, 0;
7 | --background-start-rgb: 214, 219, 220;
8 | --background-end-rgb: 255, 255, 255;
9 | }
10 |
11 | @media (prefers-color-scheme: dark) {
12 | :root {
13 | --foreground-rgb: 255, 255, 255;
14 | --background-start-rgb: 0, 0, 0;
15 | --background-end-rgb: 0, 0, 0;
16 | }
17 | }
18 |
19 | body {
20 | color: rgb(var(--foreground-rgb));
21 | background: linear-gradient(
22 | to bottom,
23 | transparent,
24 | rgb(var(--background-end-rgb))
25 | )
26 | rgb(var(--background-start-rgb));
27 | }
28 |
--------------------------------------------------------------------------------
/examples/nextjs-v14/app/layout.tsx:
--------------------------------------------------------------------------------
1 | // import './globals.css'
2 | // import type { Metadata } from 'next'
3 | // import { Inter } from 'next/font/google'
4 |
5 | // const inter = Inter({ subsets: ['latin'] })
6 |
7 | // export const metadata: Metadata = {
8 | // title: 'Create Next App',
9 | // description: 'Generated by create next app',
10 | // }
11 |
12 | // export default function RootLayout({
13 | // children,
14 | // }: {
15 | // children: React.ReactNode
16 | // }) {
17 | // return (
18 | //
19 | //
16 |
17 |
18 | )
19 | }
20 |
21 | // This gets called on every request
22 | export async function getServerSideProps(context: GetServerSidePropsContext) {
23 | console.log({ context })
24 |
25 | // Fetch data from nextjs API
26 | const host = context.req.headers.host
27 | const res = await fetch('https://' + host + '/api/hello')
28 | const date = new Date()
29 | const data = { ...(await res.json()), date: date.toISOString() }
30 |
31 | // Pass data to the page via props
32 | return {
33 | props: {
34 | data,
35 | status: res.status,
36 | env: process.env.AWS_EXECUTION_ENV ?? null,
37 | },
38 | }
39 | }
40 |
41 | export default Home
42 |
--------------------------------------------------------------------------------
/examples/nextjs-v14/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/examples/nextjs-v14/public/images/sample.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emyriounis/terraform-aws-nextjs-serverless/f2a298c8509861e9976752cc9a52401a808075fc/examples/nextjs-v14/public/images/sample.avif
--------------------------------------------------------------------------------
/examples/nextjs-v14/public/images/sample.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emyriounis/terraform-aws-nextjs-serverless/f2a298c8509861e9976752cc9a52401a808075fc/examples/nextjs-v14/public/images/sample.gif
--------------------------------------------------------------------------------
/examples/nextjs-v14/public/images/sample.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emyriounis/terraform-aws-nextjs-serverless/f2a298c8509861e9976752cc9a52401a808075fc/examples/nextjs-v14/public/images/sample.jpg
--------------------------------------------------------------------------------
/examples/nextjs-v14/public/images/sample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emyriounis/terraform-aws-nextjs-serverless/f2a298c8509861e9976752cc9a52401a808075fc/examples/nextjs-v14/public/images/sample.png
--------------------------------------------------------------------------------
/examples/nextjs-v14/public/images/sample.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emyriounis/terraform-aws-nextjs-serverless/f2a298c8509861e9976752cc9a52401a808075fc/examples/nextjs-v14/public/images/sample.webp
--------------------------------------------------------------------------------
/examples/nextjs-v14/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/nextjs-v14/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/nextjs-v14/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | padding: 0 2rem;
3 | }
4 |
5 | .main {
6 | /* min-height: 100vh; */
7 | padding: 4rem 4rem;
8 | flex: 1;
9 | display: flex;
10 | flex-direction: column;
11 | justify-content: center;
12 | align-items: center;
13 | }
14 |
15 | .footer {
16 | display: flex;
17 | flex: 1;
18 | padding: 2rem 0;
19 | border-top: 1px solid #eaeaea;
20 | justify-content: center;
21 | align-items: center;
22 | }
23 |
24 | .footer a {
25 | display: flex;
26 | justify-content: center;
27 | align-items: center;
28 | flex-grow: 1;
29 | }
30 |
31 | .title a {
32 | color: #0070f3;
33 | text-decoration: none;
34 | }
35 |
36 | .title a:hover,
37 | .title a:focus,
38 | .title a:active {
39 | text-decoration: underline;
40 | }
41 |
42 | .title {
43 | margin: 0;
44 | line-height: 1.15;
45 | font-size: 4rem;
46 | }
47 |
48 | .title,
49 | .description {
50 | text-align: center;
51 | }
52 |
53 | .description {
54 | margin: 4rem 0;
55 | line-height: 1.5;
56 | font-size: 1.5rem;
57 | }
58 |
59 | .code {
60 | background: #fafafa;
61 | border-radius: 5px;
62 | padding: 0.75rem;
63 | font-size: 1.1rem;
64 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
65 | Bitstream Vera Sans Mono, Courier New, monospace;
66 | }
67 |
68 | .grid {
69 | display: flex;
70 | align-items: center;
71 | justify-content: center;
72 | flex-wrap: wrap;
73 | max-width: 800px;
74 | }
75 |
76 | .card {
77 | margin: 1rem;
78 | padding: 1.5rem;
79 | text-align: left;
80 | color: inherit;
81 | text-decoration: none;
82 | border: 1px solid #eaeaea;
83 | border-radius: 10px;
84 | transition: color 0.15s ease, border-color 0.15s ease;
85 | max-width: 300px;
86 | }
87 |
88 | .card:hover,
89 | .card:focus,
90 | .card:active {
91 | color: #0070f3;
92 | border-color: #0070f3;
93 | }
94 |
95 | .card h2 {
96 | margin: 0 0 1rem 0;
97 | font-size: 1.5rem;
98 | }
99 |
100 | .card p {
101 | margin: 0;
102 | font-size: 1.25rem;
103 | line-height: 1.5;
104 | }
105 |
106 | .logo {
107 | height: 1em;
108 | margin-left: 0.5rem;
109 | }
110 |
111 | @media (max-width: 600px) {
112 | .grid {
113 | width: 100%;
114 | flex-direction: column;
115 | }
116 | }
117 |
118 | @media (prefers-color-scheme: dark) {
119 | .card,
120 | .footer {
121 | border-color: #222;
122 | }
123 | .code {
124 | background: #111;
125 | }
126 | .logo img {
127 | filter: invert(1);
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/examples/nextjs-v14/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: inherit;
11 | text-decoration: none;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
18 | @media (prefers-color-scheme: dark) {
19 | html {
20 | color-scheme: dark;
21 | }
22 | body {
23 | color: white;
24 | background: black;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/nextjs-v14/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'tailwindcss'
2 |
3 | const config: Config = {
4 | content: [
5 | './pages/**/*.{js,ts,jsx,tsx,mdx}',
6 | './components/**/*.{js,ts,jsx,tsx,mdx}',
7 | './app/**/*.{js,ts,jsx,tsx,mdx}',
8 | ],
9 | theme: {
10 | extend: {
11 | backgroundImage: {
12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
13 | 'gradient-conic':
14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
15 | },
16 | },
17 | },
18 | plugins: [],
19 | }
20 | export default config
21 |
--------------------------------------------------------------------------------
/examples/nextjs-v14/terraform/main.tf:
--------------------------------------------------------------------------------
1 | resource "aws_cloudfront_function" "test" {
2 | name = "test"
3 | runtime = "cloudfront-js-2.0"
4 | comment = "my function"
5 | publish = true
6 | code = file("../function.js")
7 | }
8 |
9 | module "next_serverless" {
10 | # source = "../../../"
11 | source = "emyriounis/nextjs-serverless/aws"
12 | version = "1.8.1"
13 |
14 | providers = {
15 | aws.global_region = aws.global_region
16 | }
17 |
18 | deployment_name = var.deployment_name
19 | region = var.region
20 | base_dir = var.base_dir
21 |
22 | cloudfront_acm_certificate_arn = (var.deployment_domain != null) ? module.next_cloudfront_certificate[0].acm_certificate_arn : null
23 | cloudfront_aliases = (var.deployment_domain != null) ? [var.deployment_domain] : []
24 |
25 | pre_resize_images = true
26 | wait_for_distribution_deployment = false
27 | show_debug_logs = true
28 | use_default_server_side_props_handler = false
29 |
30 | cloudfront_function_associations = [{
31 | event_type = "viewer-request"
32 | function_arn = aws_cloudfront_function.test.arn
33 | }]
34 |
35 | next_lambda_env_vars = {
36 | NODE_ENV = "production"
37 | }
38 | }
39 |
40 | module "next_cloudfront_certificate" {
41 | count = (var.deployment_domain != null) ? 1 : 0
42 |
43 | source = "terraform-aws-modules/acm/aws"
44 | version = "4.3.2"
45 |
46 | domain_name = (var.deployment_domain != null) ? var.deployment_domain : null
47 | zone_id = (var.deployment_domain != null) ? data.aws_route53_zone.hosted_zone[0].zone_id : null
48 |
49 | providers = {
50 | aws = aws.global_region
51 | }
52 | }
53 |
54 | data "aws_route53_zone" "hosted_zone" {
55 | count = (var.hosted_zone != null) ? 1 : 0
56 |
57 | name = var.hosted_zone
58 | }
59 |
60 | resource "aws_route53_record" "next_cloudfront_alias" {
61 | count = (var.deployment_domain != null) ? 1 : 0
62 |
63 | zone_id = data.aws_route53_zone.hosted_zone[0].zone_id
64 | name = var.deployment_domain
65 | type = "A"
66 |
67 | allow_overwrite = true
68 |
69 | alias {
70 | name = module.next_serverless.cloudfront_url
71 | zone_id = module.next_serverless.distribution.next_distribution.hosted_zone_id
72 | evaluate_target_health = false
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/examples/nextjs-v14/terraform/outputs.tf:
--------------------------------------------------------------------------------
1 | output "next_serverless" {
2 | value = module.next_serverless.cloudfront_url
3 | }
4 |
--------------------------------------------------------------------------------
/examples/nextjs-v14/terraform/providers.tf:
--------------------------------------------------------------------------------
1 | provider "aws" {
2 | region = var.region
3 | }
4 |
5 | provider "aws" {
6 | alias = "global_region"
7 | region = var.global_region
8 | }
9 |
--------------------------------------------------------------------------------
/examples/nextjs-v14/terraform/variables.tf:
--------------------------------------------------------------------------------
1 | variable "deployment_name" {
2 | type = string
3 | default = "tm-next-serverless"
4 | }
5 |
6 | variable "region" {
7 | type = string
8 | default = "eu-central-1"
9 | }
10 |
11 | variable "global_region" {
12 | type = string
13 | default = "us-east-1"
14 | }
15 |
16 | variable "hosted_zone" {
17 | description = "Hosted Zone in Route53, e.g. my-dns-zone.de"
18 | type = string
19 | default = null
20 | }
21 |
22 | variable "deployment_domain" {
23 | type = string
24 | description = "Url where the deployment should be availale at, e.g. website1.my-dns-zone.de"
25 | default = null
26 | }
27 |
28 | variable "base_dir" {
29 | type = string
30 | default = "../"
31 | }
32 |
--------------------------------------------------------------------------------
/examples/nextjs-v14/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": true,
12 | "noEmit": true,
13 | "esModuleInterop": true,
14 | "module": "esnext",
15 | "moduleResolution": "node",
16 | "resolveJsonModule": true,
17 | "isolatedModules": true,
18 | "jsx": "preserve",
19 | "incremental": true,
20 | "plugins": [
21 | {
22 | "name": "next"
23 | }
24 | ],
25 | "paths": {
26 | "@/*": [
27 | "./app/*"
28 | ]
29 | },
30 | "forceConsistentCasingInFileNames": true
31 | },
32 | "include": [
33 | "next-env.d.ts",
34 | "**/*.ts",
35 | "**/*.tsx",
36 | ".next-standalone/types/**/*.ts",
37 | ".next/types/**/*.ts"
38 | , "server.js" ],
39 | "exclude": [
40 | "node_modules"
41 | ]
42 | }
--------------------------------------------------------------------------------
/main.tf:
--------------------------------------------------------------------------------
1 | module "static-assets-hosting" {
2 | source = "./modules/static-assets-hosting"
3 |
4 | deployment_name = var.deployment_name
5 | base_dir = var.base_dir
6 | }
7 |
8 | module "public-assets-hosting" {
9 | source = "./modules/public-assets-hosting"
10 |
11 | deployment_name = var.deployment_name
12 | base_dir = var.base_dir
13 | }
14 |
15 | module "server-side-rendering" {
16 | source = "./modules/server-side-rendering"
17 |
18 | deployment_name = var.deployment_name
19 | base_dir = var.base_dir
20 |
21 | next_lambda_memory_size = var.next_lambda_memory_size
22 | next_lambda_logs_retention = var.next_lambda_logs_retention
23 | next_lambda_runtime = var.next_lambda_runtime
24 | next_lambda_ephemeral_storage_size = var.next_lambda_ephemeral_storage_size
25 |
26 | next_lambda_env_vars = var.next_lambda_env_vars
27 | custom_image_types = var.custom_image_types
28 | next_lambda_policy_statements = var.next_lambda_policy_statements
29 | show_debug_logs = var.show_debug_logs
30 | use_default_server_side_props_handler = var.use_default_server_side_props_handler
31 |
32 | api_gateway_log_format = var.api_gateway_log_format
33 | }
34 |
35 | module "image-optimization" {
36 | source = "./modules/image-optimization"
37 |
38 | providers = {
39 | aws.global_region = aws.global_region
40 | }
41 |
42 | deployment_name = var.deployment_name
43 | base_dir = var.base_dir
44 |
45 | image_optimization_runtime = var.image_optimization_runtime
46 | image_optimization_logs_retention = var.image_optimization_logs_retention
47 | image_optimization_lambda_memory_size = var.image_optimization_lambda_memory_size
48 | image_optimization_ephemeral_storage_size = var.image_optimization_ephemeral_storage_size
49 |
50 | public_assets_bucket = module.public-assets-hosting.public_assets_bucket
51 | }
52 |
53 | module "distribution" {
54 | source = "./modules/distribution"
55 |
56 | static_assets_bucket = module.static-assets-hosting.static_assets_bucket
57 | static_assets_origin_id = module.static-assets-hosting.static_assets_oai
58 |
59 | public_assets_bucket = module.public-assets-hosting.public_assets_bucket
60 | public_assets_bucket_region = var.region
61 | public_assets_origin_id = module.public-assets-hosting.public_assets_oai
62 |
63 | dynamic_origin_domain_name = module.server-side-rendering.api_gateway.default_apigatewayv2_stage_domain_name
64 |
65 | enable_image_optimization = var.enable_image_optimization
66 | image_optimization_qualified_arn = module.image-optimization.image_optimization.lambda_function_qualified_arn
67 | image_redirection_qualified_arn = module.image-optimization.image_redirection.lambda_function_qualified_arn
68 |
69 | cloudfront_acm_certificate_arn = var.cloudfront_acm_certificate_arn
70 | cloudfront_aliases = var.cloudfront_aliases
71 | cloudfront_price_class = var.cloudfront_price_class
72 |
73 | cloudfront_cached_paths = var.cloudfront_cached_paths
74 | custom_cache_policy_id = var.custom_cache_policy_id
75 | cloudfront_cache_default_ttl = var.cloudfront_cache_default_ttl
76 | cloudfront_cache_max_ttl = var.cloudfront_cache_max_ttl
77 | cloudfront_cache_min_ttl = var.cloudfront_cache_min_ttl
78 |
79 | deployment_name = var.deployment_name
80 | base_dir = var.base_dir
81 | cloudfront_function_associations = var.cloudfront_function_associations
82 | wait_for_distribution_deployment = var.wait_for_distribution_deployment
83 | }
84 |
85 | # delete previously resized public assets
86 | resource "null_resource" "delete_resized_versions" {
87 | count = var.delete_resized_versions ? 1 : 0
88 |
89 | provisioner "local-exec" {
90 | command = < uri.includes('.' + type))) {
48 | const response = {
49 | statusCode: 302,
50 | // Don't use `host` as it's not custom domain
51 | headers: { location: { value: '/assets' + uri } },
52 | };
53 |
54 | return response;
55 | }
56 |
57 | headers['x-forwarded-host'] = { value: host };
58 | return request;
59 | }
60 | EOF
61 | }
62 |
63 | resource "aws_cloudfront_cache_policy" "custom_paths_cache" {
64 | count = var.custom_cache_policy_id == null && length(var.cloudfront_cached_paths.paths) > 0 ? 1 : 0
65 | name = "${var.deployment_name}-custom-paths-cache-policy"
66 |
67 | default_ttl = var.cloudfront_cached_paths.default_ttl
68 | max_ttl = var.cloudfront_cached_paths.max_ttl
69 | min_ttl = var.cloudfront_cached_paths.min_ttl
70 |
71 | parameters_in_cache_key_and_forwarded_to_origin {
72 | cookies_config {
73 | cookie_behavior = "none"
74 | }
75 | headers_config {
76 | header_behavior = "whitelist"
77 | headers {
78 | items = ["x-forwarded-host"]
79 | }
80 | }
81 | query_strings_config {
82 | query_string_behavior = "all"
83 | }
84 |
85 | enable_accept_encoding_brotli = true
86 | enable_accept_encoding_gzip = true
87 | }
88 | }
89 |
90 | resource "aws_cloudfront_distribution" "next_distribution" {
91 | origin {
92 | domain_name = var.public_assets_bucket.s3_bucket_bucket_regional_domain_name
93 | origin_id = var.public_assets_origin_id.id
94 |
95 | s3_origin_config {
96 | origin_access_identity = var.public_assets_origin_id.cloudfront_access_identity_path
97 | }
98 | }
99 |
100 | origin {
101 | domain_name = var.static_assets_bucket.s3_bucket_bucket_regional_domain_name
102 | origin_id = var.static_assets_origin_id.id
103 |
104 | s3_origin_config {
105 | origin_access_identity = var.static_assets_origin_id.cloudfront_access_identity_path
106 | }
107 | }
108 |
109 | origin {
110 | domain_name = var.dynamic_origin_domain_name
111 | origin_id = aws_cloudfront_origin_access_identity.dynamic_assets_oai.id
112 |
113 | custom_origin_config {
114 | http_port = "80"
115 | https_port = "443"
116 | origin_protocol_policy = "https-only"
117 | origin_ssl_protocols = ["TLSv1.2"]
118 | }
119 | }
120 |
121 | origin {
122 | domain_name = "example.com"
123 | origin_id = aws_cloudfront_origin_access_identity.image_redirection_oai.id
124 |
125 | custom_origin_config {
126 | http_port = "80"
127 | https_port = "443"
128 | origin_protocol_policy = "https-only"
129 | origin_ssl_protocols = ["TLSv1.2"]
130 | }
131 |
132 | custom_header {
133 | name = "Enable-Image-Optimization"
134 | value = var.enable_image_optimization ? "true" : "false"
135 | }
136 | }
137 |
138 | origin {
139 | domain_name = "example.com"
140 | origin_id = aws_cloudfront_origin_access_identity.image_optimization_oai.id
141 |
142 | custom_origin_config {
143 | http_port = "80"
144 | https_port = "443"
145 | origin_protocol_policy = "https-only"
146 | origin_ssl_protocols = ["TLSv1.2"]
147 | }
148 |
149 | custom_header {
150 | name = "S3-Region"
151 | value = var.public_assets_bucket_region
152 | }
153 |
154 | custom_header {
155 | name = "Public-Assets-Bucket"
156 | value = var.public_assets_bucket.s3_bucket_id
157 | }
158 | }
159 |
160 | enabled = true
161 | is_ipv6_enabled = true
162 | wait_for_deployment = var.wait_for_distribution_deployment
163 |
164 | aliases = var.cloudfront_aliases
165 | default_root_object = null
166 |
167 | ordered_cache_behavior {
168 | path_pattern = "/_next/image/*"
169 | allowed_methods = ["GET", "HEAD", "OPTIONS"]
170 | cached_methods = ["GET", "HEAD", "OPTIONS"]
171 | target_origin_id = aws_cloudfront_origin_access_identity.image_optimization_oai.id
172 |
173 | lambda_function_association {
174 | event_type = "origin-request"
175 | lambda_arn = var.image_optimization_qualified_arn
176 | }
177 |
178 | cache_policy_id = data.aws_cloudfront_cache_policy.caching_optimized.id
179 |
180 | viewer_protocol_policy = "redirect-to-https"
181 | compress = true
182 | }
183 |
184 | ordered_cache_behavior {
185 | path_pattern = "/_next/image*"
186 | allowed_methods = ["GET", "HEAD", "OPTIONS"]
187 | cached_methods = ["GET", "HEAD", "OPTIONS"]
188 | target_origin_id = aws_cloudfront_origin_access_identity.image_redirection_oai.id
189 |
190 | lambda_function_association {
191 | event_type = "viewer-request"
192 | lambda_arn = var.image_redirection_qualified_arn
193 | include_body = true
194 | }
195 |
196 | cache_policy_id = data.aws_cloudfront_cache_policy.caching_optimized.id
197 |
198 | viewer_protocol_policy = "redirect-to-https"
199 | compress = true
200 | }
201 |
202 | ordered_cache_behavior {
203 | path_pattern = "/_next/static/*"
204 | allowed_methods = ["GET", "HEAD", "OPTIONS"]
205 | cached_methods = ["GET", "HEAD", "OPTIONS"]
206 | target_origin_id = var.static_assets_origin_id.id
207 |
208 | cache_policy_id = data.aws_cloudfront_cache_policy.caching_optimized.id
209 |
210 | viewer_protocol_policy = "redirect-to-https"
211 | compress = true
212 | }
213 |
214 | ordered_cache_behavior {
215 | path_pattern = "/assets/*"
216 | allowed_methods = ["GET", "HEAD", "OPTIONS"]
217 | cached_methods = ["GET", "HEAD", "OPTIONS"]
218 | target_origin_id = var.public_assets_origin_id.id
219 |
220 | cache_policy_id = data.aws_cloudfront_cache_policy.caching_optimized.id
221 |
222 | viewer_protocol_policy = "redirect-to-https"
223 | compress = true
224 | }
225 |
226 | ordered_cache_behavior {
227 | path_pattern = "/resized-assets/*"
228 | allowed_methods = ["GET", "HEAD", "OPTIONS"]
229 | cached_methods = ["GET", "HEAD", "OPTIONS"]
230 | target_origin_id = var.public_assets_origin_id.id
231 |
232 | cache_policy_id = data.aws_cloudfront_cache_policy.caching_optimized.id
233 |
234 | viewer_protocol_policy = "redirect-to-https"
235 | compress = true
236 | }
237 |
238 | dynamic "ordered_cache_behavior" {
239 | for_each = var.cloudfront_cached_paths.paths
240 | content {
241 | path_pattern = ordered_cache_behavior.value
242 | allowed_methods = ["GET", "HEAD", "OPTIONS"]
243 | cached_methods = ["GET", "HEAD", "OPTIONS"]
244 | target_origin_id = aws_cloudfront_origin_access_identity.dynamic_assets_oai.id
245 |
246 | cache_policy_id = var.custom_cache_policy_id != null ? var.custom_cache_policy_id : aws_cloudfront_cache_policy.custom_paths_cache[0].id
247 |
248 | dynamic "function_association" {
249 | for_each = concat([{ event_type : "viewer-request", function_arn : aws_cloudfront_function.viewer_request.arn }], var.cloudfront_function_associations)
250 | content {
251 | event_type = function_association.value.event_type
252 | function_arn = function_association.value.function_arn
253 | }
254 | }
255 |
256 | viewer_protocol_policy = "redirect-to-https"
257 | compress = true
258 | }
259 | }
260 |
261 | default_cache_behavior {
262 | allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
263 | cached_methods = ["GET", "HEAD"]
264 | target_origin_id = aws_cloudfront_origin_access_identity.dynamic_assets_oai.id
265 |
266 | cache_policy_id = data.aws_cloudfront_cache_policy.caching_disabled.id
267 | origin_request_policy_id = data.aws_cloudfront_origin_request_policy.caching_all_viewer_except_host_header.id
268 |
269 | dynamic "function_association" {
270 | for_each = concat([{ event_type : "viewer-request", function_arn : aws_cloudfront_function.viewer_request.arn }], var.cloudfront_function_associations)
271 | content {
272 | event_type = function_association.value.event_type
273 | function_arn = function_association.value.function_arn
274 | }
275 | }
276 |
277 | viewer_protocol_policy = "redirect-to-https"
278 | compress = true
279 | }
280 |
281 | price_class = var.cloudfront_price_class
282 |
283 | viewer_certificate {
284 | cloudfront_default_certificate = var.cloudfront_acm_certificate_arn == null
285 | acm_certificate_arn = var.cloudfront_acm_certificate_arn
286 | minimum_protocol_version = var.cloudfront_acm_certificate_arn == null ? "TLSv1" : "TLSv1.2_2021"
287 | ssl_support_method = var.cloudfront_acm_certificate_arn != null ? "sni-only" : null
288 | }
289 |
290 | restrictions {
291 | geo_restriction {
292 | restriction_type = "none"
293 | }
294 | }
295 | }
296 |
297 | resource "aws_cloudfront_monitoring_subscription" "next_distribution_monitoring" {
298 | distribution_id = aws_cloudfront_distribution.next_distribution.id
299 |
300 | monitoring_subscription {
301 | realtime_metrics_subscription_config {
302 | realtime_metrics_subscription_status = "Enabled"
303 | }
304 | }
305 | }
306 |
--------------------------------------------------------------------------------
/modules/distribution/outputs.tf:
--------------------------------------------------------------------------------
1 | output "next_distribution" {
2 | value = aws_cloudfront_distribution.next_distribution
3 | }
4 |
--------------------------------------------------------------------------------
/modules/distribution/variables.tf:
--------------------------------------------------------------------------------
1 | variable "deployment_name" {
2 | type = string
3 | }
4 |
5 | variable "base_dir" {
6 | type = string
7 | }
8 |
9 | variable "enable_image_optimization" {
10 | type = bool
11 | }
12 |
13 | variable "dynamic_origin_domain_name" {
14 | type = string
15 | }
16 |
17 | variable "cloudfront_acm_certificate_arn" {
18 | type = string
19 | default = null
20 | }
21 |
22 | variable "cloudfront_price_class" {
23 | type = string
24 | }
25 |
26 | variable "cloudfront_aliases" {
27 | type = list(string)
28 | }
29 |
30 | variable "image_optimization_qualified_arn" {
31 | type = string
32 | }
33 |
34 | variable "image_redirection_qualified_arn" {
35 | type = string
36 | }
37 |
38 | variable "static_assets_bucket" {
39 | type = map(any)
40 | }
41 |
42 | variable "static_assets_origin_id" {
43 | type = map(any)
44 | }
45 |
46 | variable "public_assets_bucket" {
47 | type = map(any)
48 | }
49 |
50 | variable "public_assets_bucket_region" {
51 | type = string
52 | }
53 |
54 | variable "public_assets_origin_id" {
55 | type = map(any)
56 | }
57 |
58 | variable "cloudfront_cached_paths" {
59 | type = object({
60 | paths = list(string)
61 | min_ttl = number
62 | default_ttl = number
63 | max_ttl = number
64 | })
65 | }
66 |
67 | variable "custom_cache_policy_id" {
68 | type = string
69 | }
70 |
71 | variable "cloudfront_cache_default_ttl" {
72 | type = number
73 | }
74 |
75 | variable "cloudfront_cache_max_ttl" {
76 | type = number
77 | }
78 |
79 | variable "cloudfront_cache_min_ttl" {
80 | type = number
81 | }
82 |
83 | variable "cloudfront_function_associations" {
84 | type = list(object({
85 | event_type = string
86 | function_arn = string
87 | }))
88 | }
89 |
90 | variable "wait_for_distribution_deployment" {
91 | type = bool
92 | }
93 |
--------------------------------------------------------------------------------
/modules/image-optimization/main.tf:
--------------------------------------------------------------------------------
1 | ####################################
2 | ######## image_optimization ########
3 | ####################################
4 |
5 | module "image_optimization" {
6 | providers = {
7 | aws = aws.global_region
8 | }
9 |
10 | source = "terraform-aws-modules/lambda/aws"
11 | version = "6.5.0"
12 |
13 | function_name = "${var.deployment_name}-image-optimization"
14 | description = "${var.deployment_name} Image Optimization"
15 |
16 | lambda_at_edge = true
17 | publish = true
18 | runtime = var.image_optimization_runtime
19 | memory_size = var.image_optimization_lambda_memory_size
20 | ephemeral_storage_size = var.image_optimization_ephemeral_storage_size
21 | timeout = 30
22 | maximum_event_age_in_seconds = 60
23 | maximum_retry_attempts = 0
24 |
25 | create_package = false
26 | local_existing_package = "${var.base_dir}deployments/image-optimization/source.zip"
27 | handler = "index.handler"
28 |
29 | attach_network_policy = false
30 | cloudwatch_logs_retention_in_days = var.image_optimization_logs_retention
31 |
32 | cors = {
33 | allow_credentials = true
34 | allow_origins = ["*"]
35 | allow_methods = ["*"]
36 | }
37 |
38 | attach_policy_statements = true
39 | policy_statements = {
40 | s3_public_assets_bucket = {
41 | effect = "Allow",
42 | actions = ["s3:GetObject", "s3:PutObject"],
43 | resources = ["${var.public_assets_bucket.s3_bucket_arn}/*"]
44 | }
45 | }
46 | }
47 |
48 | ####################################
49 | ######### image_redirection ########
50 | ####################################
51 |
52 | module "image_redirection" {
53 | providers = {
54 | aws = aws.global_region
55 | }
56 |
57 | source = "terraform-aws-modules/lambda/aws"
58 | version = "6.5.0"
59 |
60 | function_name = "${var.deployment_name}-ns-img-rdr"
61 | description = "${var.deployment_name} Image Redirection"
62 |
63 | lambda_at_edge = true
64 | publish = true
65 | runtime = var.image_optimization_runtime
66 | memory_size = 128
67 | ephemeral_storage_size = 512
68 | timeout = 5
69 | maximum_event_age_in_seconds = 60
70 | maximum_retry_attempts = 0
71 |
72 | create_package = false
73 | local_existing_package = "${var.base_dir}deployments/ns-img-rdr/source.zip"
74 | handler = "index.handler"
75 |
76 | attach_network_policy = false
77 | cloudwatch_logs_retention_in_days = var.image_optimization_logs_retention
78 |
79 | cors = {
80 | allow_credentials = true
81 | allow_origins = ["*"]
82 | allow_methods = ["*"]
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/modules/image-optimization/outputs.tf:
--------------------------------------------------------------------------------
1 | output "image_optimization" {
2 | value = module.image_optimization
3 | }
4 |
5 | output "image_redirection" {
6 | value = module.image_redirection
7 | }
8 |
--------------------------------------------------------------------------------
/modules/image-optimization/variables.tf:
--------------------------------------------------------------------------------
1 | variable "image_optimization_runtime" {
2 | type = string
3 | }
4 |
5 | variable "image_optimization_logs_retention" {
6 | type = number
7 | }
8 |
9 | variable "deployment_name" {
10 | type = string
11 | }
12 |
13 | variable "base_dir" {
14 | type = string
15 | }
16 |
17 | variable "public_assets_bucket" {
18 | type = map(any)
19 | }
20 |
21 | variable "image_optimization_lambda_memory_size" {
22 | type = number
23 | }
24 |
25 | variable "image_optimization_ephemeral_storage_size" {
26 | type = number
27 | }
28 |
--------------------------------------------------------------------------------
/modules/image-optimization/versions.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = ">= 1.6.3"
3 |
4 | required_providers {
5 | aws = {
6 | source = "hashicorp/aws"
7 | version = "~> 5.0"
8 | configuration_aliases = [aws.global_region]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/modules/public-assets-hosting/main.tf:
--------------------------------------------------------------------------------
1 | ####################################
2 | ####### public_assets_bucket #######
3 | ####################################
4 |
5 | locals {
6 | base_dir = "${var.base_dir}public"
7 |
8 | image_widths = [16, 32, 64, 128, 256, 512, 1024]
9 | image_types = ["webp", "jpeg", "png"]
10 | image_combinations = flatten([
11 | for width in local.image_widths : [
12 | for type in local.image_types : "${width}/${type}"
13 | ]
14 | ])
15 |
16 | all_paths = [
17 | for file in module.public_assets_static_files.files : replace(file.source_path, "${local.base_dir}/", "")
18 | ]
19 |
20 | all_resized_images_paths_list = flatten([
21 | for path in local.all_paths : [
22 | for prefix in local.image_combinations : "${prefix}/${path}"
23 | ]
24 | ])
25 |
26 | all_resized_images_paths_map = {
27 | for idx, path in local.all_resized_images_paths_list : idx => path
28 | }
29 | }
30 |
31 | module "public_assets_bucket" {
32 | source = "terraform-aws-modules/s3-bucket/aws"
33 | version = "3.15.1"
34 |
35 | bucket = "${var.deployment_name}-public-assets"
36 | acl = "private"
37 | force_destroy = true
38 | control_object_ownership = true
39 | object_ownership = "ObjectWriter"
40 |
41 | block_public_acls = true
42 | block_public_policy = true
43 | ignore_public_acls = true
44 | restrict_public_buckets = true
45 | }
46 |
47 | module "public_assets_static_files" {
48 | source = "hashicorp/dir/template"
49 | version = "1.0.2"
50 |
51 | base_dir = local.base_dir
52 | }
53 |
54 | resource "aws_s3_object" "public_assets_files" {
55 | bucket = module.public_assets_bucket.s3_bucket_id
56 | for_each = module.public_assets_static_files.files
57 |
58 | key = "assets/${each.key}" # necessary prefix
59 | source = each.value.source_path
60 | content = each.value.content
61 | content_type = each.value.content_type
62 | etag = each.value.digests.md5
63 | }
64 |
65 | # CloudFront IAM policy
66 | resource "aws_cloudfront_origin_access_identity" "public_assets_oai" {
67 | comment = "public_assets_origin"
68 | }
69 |
70 | data "aws_iam_policy_document" "public_assets_s3_policy" {
71 | statement {
72 | actions = ["s3:GetObject"]
73 | resources = ["${module.public_assets_bucket.s3_bucket_arn}/*"]
74 |
75 | principals {
76 | type = "AWS"
77 | identifiers = [aws_cloudfront_origin_access_identity.public_assets_oai.iam_arn]
78 | }
79 | }
80 | }
81 |
82 | resource "aws_s3_bucket_policy" "public_assets_bucket_policy" {
83 | bucket = module.public_assets_bucket.s3_bucket_id
84 | policy = data.aws_iam_policy_document.public_assets_s3_policy.json
85 | }
86 |
--------------------------------------------------------------------------------
/modules/public-assets-hosting/outputs.tf:
--------------------------------------------------------------------------------
1 | output "public_assets_bucket" {
2 | value = module.public_assets_bucket
3 | }
4 |
5 | output "public_assets_oai" {
6 | value = aws_cloudfront_origin_access_identity.public_assets_oai
7 | }
8 |
9 | output "all_resized_images_paths" {
10 | value = local.all_resized_images_paths_map
11 | }
12 |
--------------------------------------------------------------------------------
/modules/public-assets-hosting/variables.tf:
--------------------------------------------------------------------------------
1 | variable "deployment_name" {
2 | type = string
3 | }
4 |
5 | variable "base_dir" {
6 | type = string
7 | }
8 |
--------------------------------------------------------------------------------
/modules/server-side-rendering/main.tf:
--------------------------------------------------------------------------------
1 | ####################################
2 | ########### next_lambda ############
3 | ####################################
4 |
5 | locals {
6 | lambda_source_object_s3_key = "source-${filemd5("${var.base_dir}deployments/source.zip")}.zip"
7 | lambda_layer_object_s3_key = "layer-${filemd5("${var.base_dir}deployments/layer.zip")}.zip"
8 |
9 | lambda_default_env_vars = {
10 | DEFAULT_SS_PROPS_HANDLER = var.use_default_server_side_props_handler
11 | CUSTOM_IMAGE_TYPES = join(",", var.custom_image_types)
12 | SHOW_DEBUG_LOGS = var.show_debug_logs
13 | }
14 | }
15 |
16 | module "next_lambda_zips_bucket" {
17 | source = "terraform-aws-modules/s3-bucket/aws"
18 | version = "3.15.1"
19 |
20 | bucket = "${var.deployment_name}-next-lambda-zips"
21 | acl = "private"
22 | force_destroy = true
23 | control_object_ownership = true
24 | object_ownership = "ObjectWriter"
25 |
26 | block_public_acls = true
27 | block_public_policy = true
28 | ignore_public_acls = true
29 | restrict_public_buckets = true
30 | }
31 |
32 | resource "aws_s3_object" "lambda_source_object" {
33 | bucket = module.next_lambda_zips_bucket.s3_bucket_id
34 | key = local.lambda_source_object_s3_key
35 | source = "${var.base_dir}deployments/source.zip"
36 | }
37 |
38 | resource "aws_s3_object" "lambda_layer_object" {
39 | bucket = module.next_lambda_zips_bucket.s3_bucket_id
40 | key = local.lambda_layer_object_s3_key
41 | source = "${var.base_dir}deployments/layer.zip"
42 | }
43 |
44 | resource "aws_lambda_layer_version" "server_layer" {
45 | depends_on = [aws_s3_object.lambda_layer_object]
46 |
47 | layer_name = "${var.deployment_name}-layer"
48 | compatible_runtimes = [var.next_lambda_runtime]
49 |
50 | s3_bucket = module.next_lambda_zips_bucket.s3_bucket_id
51 | s3_key = local.lambda_layer_object_s3_key
52 | }
53 |
54 | module "next_lambda" {
55 | source = "terraform-aws-modules/lambda/aws"
56 | version = "6.5.0"
57 |
58 | function_name = "${var.deployment_name}-ssr"
59 | description = "${var.deployment_name} Server"
60 |
61 | lambda_at_edge = false
62 | runtime = var.next_lambda_runtime
63 | memory_size = var.next_lambda_memory_size
64 | ephemeral_storage_size = var.next_lambda_ephemeral_storage_size
65 | timeout = 30
66 | maximum_event_age_in_seconds = 60
67 | maximum_retry_attempts = 0
68 |
69 | create_package = false
70 | handler = "server.handler"
71 | s3_existing_package = {
72 | bucket = module.next_lambda_zips_bucket.s3_bucket_id
73 | key = local.lambda_source_object_s3_key
74 | }
75 |
76 | publish = true
77 | layers = [aws_lambda_layer_version.server_layer.arn]
78 |
79 | cloudwatch_logs_retention_in_days = var.next_lambda_logs_retention
80 |
81 | attach_network_policy = false
82 |
83 | cors = {
84 | allow_credentials = true
85 | allow_origins = ["*"] #TODO: update
86 | allow_methods = ["*"]
87 | }
88 |
89 | environment_variables = merge(local.lambda_default_env_vars, var.next_lambda_env_vars)
90 |
91 | allowed_triggers = {
92 | api_gateway = {
93 | action = "lambda:InvokeFunction"
94 | service = "apigateway"
95 | source_arn = "${module.api_gateway.apigatewayv2_api_execution_arn}/*/*"
96 | }
97 | }
98 |
99 | attach_policy_statements = length(keys(var.next_lambda_policy_statements)) != 0
100 | policy_statements = var.next_lambda_policy_statements
101 | }
102 |
103 | ####################################
104 | ########### api_gateway ############
105 | ####################################
106 |
107 | module "api_gateway_cloudwatch_log_group" {
108 | source = "terraform-aws-modules/cloudwatch/aws//modules/log-group"
109 | version = "4.3.0"
110 |
111 | name = "${var.deployment_name}-api-gateway-logs"
112 | retention_in_days = var.next_lambda_logs_retention
113 | }
114 |
115 | module "api_gateway" {
116 | source = "terraform-aws-modules/apigateway-v2/aws"
117 | version = "2.2.2"
118 |
119 | name = "${var.deployment_name}-api"
120 | description = "${var.deployment_name} API"
121 |
122 | create_vpc_link = false
123 | create_api_domain_name = false
124 |
125 | default_stage_access_log_destination_arn = module.api_gateway_cloudwatch_log_group.cloudwatch_log_group_arn
126 | default_stage_access_log_format = var.api_gateway_log_format
127 |
128 | cors_configuration = {
129 | allow_headers = ["*"]
130 | allow_origins = ["*"]
131 | allow_methods = ["*"]
132 | }
133 |
134 | integrations = {
135 | "$default" = {
136 | lambda_arn = module.next_lambda.lambda_function_arn
137 | payload_format_version = "2.0"
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/modules/server-side-rendering/outputs.tf:
--------------------------------------------------------------------------------
1 | output "next_lambda" {
2 | value = module.next_lambda
3 | }
4 |
5 | output "api_gateway" {
6 | value = module.api_gateway
7 | }
8 |
9 | output "api_gateway_cloudwatch_log_group" {
10 | value = module.api_gateway_cloudwatch_log_group
11 | }
12 |
13 | output "aws_lambda_layer_version" {
14 | value = aws_lambda_layer_version.server_layer
15 | }
16 |
--------------------------------------------------------------------------------
/modules/server-side-rendering/variables.tf:
--------------------------------------------------------------------------------
1 | variable "next_lambda_runtime" {
2 | type = string
3 | }
4 |
5 | variable "next_lambda_memory_size" {
6 | type = number
7 | }
8 |
9 | variable "next_lambda_logs_retention" {
10 | type = number
11 | }
12 |
13 | variable "deployment_name" {
14 | type = string
15 | }
16 |
17 | variable "base_dir" {
18 | type = string
19 | }
20 |
21 | variable "next_lambda_env_vars" {
22 | type = map(any)
23 | }
24 |
25 | variable "custom_image_types" {
26 | type = list(string)
27 | }
28 |
29 | variable "next_lambda_policy_statements" {
30 | type = map(any)
31 | }
32 |
33 | variable "next_lambda_ephemeral_storage_size" {
34 | type = number
35 | }
36 |
37 | variable "api_gateway_log_format" {
38 | type = string
39 | }
40 |
41 | variable "use_default_server_side_props_handler" {
42 | type = bool
43 | }
44 |
45 | variable "show_debug_logs" {
46 | type = bool
47 | }
48 |
--------------------------------------------------------------------------------
/modules/static-assets-hosting/main.tf:
--------------------------------------------------------------------------------
1 | ####################################
2 | ####### static_assets_bucket #######
3 | ####################################
4 |
5 | module "static_assets_bucket" {
6 | source = "terraform-aws-modules/s3-bucket/aws"
7 | version = "3.15.1"
8 |
9 | bucket = "${var.deployment_name}-static-assets"
10 | acl = "private"
11 | force_destroy = true
12 | control_object_ownership = true
13 | object_ownership = "ObjectWriter"
14 |
15 | block_public_acls = true
16 | block_public_policy = true
17 | ignore_public_acls = true
18 | restrict_public_buckets = true
19 | }
20 |
21 | module "static_assets_static_files" {
22 | source = "hashicorp/dir/template"
23 | version = "1.0.2"
24 |
25 | base_dir = "${var.base_dir}standalone/static"
26 | }
27 |
28 | resource "aws_s3_object" "static_assets_files" {
29 | bucket = module.static_assets_bucket.s3_bucket_id
30 | for_each = module.static_assets_static_files.files
31 |
32 | key = each.key
33 | source = each.value.source_path
34 | content = each.value.content
35 | content_type = each.value.content_type
36 | etag = each.value.digests.md5
37 | }
38 |
39 | # CloudFront IAM policy
40 | resource "aws_cloudfront_origin_access_identity" "static_assets_oai" {
41 | comment = "static_assets_origin"
42 | }
43 |
44 | data "aws_iam_policy_document" "static_assets_s3_policy" {
45 | statement {
46 | actions = ["s3:GetObject"]
47 | resources = ["${module.static_assets_bucket.s3_bucket_arn}/*"]
48 |
49 | principals {
50 | type = "AWS"
51 | identifiers = [aws_cloudfront_origin_access_identity.static_assets_oai.iam_arn]
52 | }
53 | }
54 | }
55 |
56 | resource "aws_s3_bucket_policy" "static_assets_bucket_policy" {
57 | bucket = module.static_assets_bucket.s3_bucket_id
58 | policy = data.aws_iam_policy_document.static_assets_s3_policy.json
59 | }
60 |
--------------------------------------------------------------------------------
/modules/static-assets-hosting/outputs.tf:
--------------------------------------------------------------------------------
1 | output "static_assets_bucket" {
2 | value = module.static_assets_bucket
3 | }
4 |
5 | output "static_assets_oai" {
6 | value = aws_cloudfront_origin_access_identity.static_assets_oai
7 | }
8 |
--------------------------------------------------------------------------------
/modules/static-assets-hosting/variables.tf:
--------------------------------------------------------------------------------
1 | variable "deployment_name" {
2 | type = string
3 | }
4 |
5 | variable "base_dir" {
6 | type = string
7 | }
8 |
--------------------------------------------------------------------------------
/original-license.md:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | the copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor to discuss and improve the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by the Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributors that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, the Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assuming any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2023 Nexode Consulting GmbH
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
--------------------------------------------------------------------------------
/outputs.tf:
--------------------------------------------------------------------------------
1 | output "static-assets-hosting" {
2 | value = module.static-assets-hosting
3 | description = "Resources created by the static-assets-hosting module"
4 | }
5 |
6 | output "public-assets-hosting" {
7 | value = module.public-assets-hosting
8 | description = "Resources created by the public-assets-hosting module"
9 | }
10 |
11 | output "image-optimization" {
12 | value = module.image-optimization
13 | description = "Resources created by the image-optimization module"
14 | }
15 |
16 | output "server-side-rendering" {
17 | value = module.server-side-rendering
18 | description = "Resources created by the server-side-rendering module"
19 | }
20 |
21 | output "distribution" {
22 | value = module.distribution
23 | description = "Resources created by the distribution module"
24 | }
25 |
26 | output "cloudfront_url" {
27 | value = module.distribution.next_distribution.domain_name
28 | description = "The URL where cloudfront hosts the distribution"
29 | }
30 |
--------------------------------------------------------------------------------
/packages/ns-build/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | the copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor to discuss and improve the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by the Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributors that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, the Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assuming any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | Copyright 2024\
179 | Eleftherios Myriounis
180 |
181 | Licensed under the Apache License, Version 2.0 (the "License");
182 | you may not use this file except in compliance with the License.
183 | You may obtain a copy of the License at
184 |
185 | http://www.apache.org/licenses/LICENSE-2.0
186 |
187 | Unless required by applicable law or agreed to in writing, software
188 | distributed under the License is distributed on an "AS IS" BASIS,
189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190 | See the License for the specific language governing permissions and
191 | limitations under the License.
--------------------------------------------------------------------------------
/packages/ns-build/README.md:
--------------------------------------------------------------------------------
1 | ## ns-build package
2 |
3 |
4 | 1. Add the `output: 'standalone'` option on the next.config.js configuration
5 |
6 | 2. Run `ns-build build`
7 |
8 | 3. Use the [nextjs-serverless](https://registry.terraform.io/modules/emyriounis/nextjs-serverless/aws/latest) terraform module to deploy your next.js app on aws
9 |
10 |
11 | ## Keywords
12 |
13 | - Next.js
14 | - Terraform
15 | - AWS
16 | - Serverless
17 |
--------------------------------------------------------------------------------
/packages/ns-build/bin/ns-build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 |
4 | # List of packages to copy directly on the lambda or ALL packages, instead of the layer
5 | copyAllPackages=false
6 | for ARGUMENT in "$@"
7 | do
8 | KEY=$(echo $ARGUMENT | cut -f1 -d=)
9 |
10 | if [[ $KEY == --copyAllPackages ]]; then
11 | copyAllPackages=true
12 | elif [[ $KEY == --packages-to-copy* ]]; then
13 | KEY_LENGTH=${#KEY}
14 | VALUE="${ARGUMENT:$KEY_LENGTH+1}"
15 |
16 | IFS=',' read -r -a packages_to_copy <<< "$VALUE"
17 | fi
18 | done
19 |
20 | # Clean-up old builds
21 | rm -r .next
22 | rm -r standalone
23 | rm -r deployments
24 |
25 | # Install necessary packages
26 | npm i -D serverless@3.38.0 serverless-esbuild@1.49.0 esbuild@0.19.7 serverless-http@3.2.0 ns-img-opt@1.8.1 ns-img-rdr@1.8.1
27 |
28 | # Inject code in build, and cleanup
29 | cp -a ./app ./app-backup
30 | find ./app -type f -name 'page.tsx' -exec sh -c 'printf "\nexport const runtime = '\''edge'\'';\n" >> "$0"' {} \;
31 | set -e
32 | npm run build
33 | set +e
34 | rm -r ./app
35 | mv ./app-backup ./app
36 |
37 | # Keep necessary files
38 | cp -a .next/static .next/standalone/.next
39 | cp -a .next/standalone standalone
40 | rm standalone/server.js
41 | cp node_modules/ns-build/server.js standalone
42 | cp next.config.js standalone
43 |
44 | # Prepare deployment
45 | mkdir deployments
46 | mkdir standalone
47 | mkdir nodejs
48 |
49 | # Keeps necessary node modules
50 | cp -a standalone/node_modules nodejs
51 | cp -a node_modules/serverless nodejs/node_modules
52 | cp -a node_modules/serverless-esbuild nodejs/node_modules
53 | cp -a node_modules/esbuild nodejs/node_modules
54 | cp -a node_modules/serverless-http nodejs/node_modules
55 |
56 | # Zip node modules
57 | echo "Generating layer.zip ..."
58 | zip -r -q deployments/layer.zip nodejs
59 | echo "layer.zip generated !"
60 |
61 | # Keep image optimization/redirection source code zips
62 | cd deployments
63 | mkdir ns-img-rdr
64 | mkdir image-optimization
65 | cd ..
66 | cp node_modules/ns-img-rdr/source.zip deployments/ns-img-rdr/
67 | cp node_modules/ns-img-opt/source.zip deployments/image-optimization/
68 |
69 | # Keep necessary files
70 | cd standalone
71 | mkdir -p static/_next
72 | cp -a .next/static static/_next
73 |
74 | # Prepare source code
75 | rm -r node_modules
76 |
77 | # optinal: add node_modules
78 | if [[ $copyAllPackages == true ]]; then
79 | cp -a ../.next/standalone/node_modules node_modules
80 | else
81 | mkdir node_modules
82 | for package in "${packages_to_copy[@]}"
83 | do
84 | cp -a ../node_modules/$package node_modules/$package
85 | done
86 | fi
87 |
88 | # zip source code
89 | echo "Generating source.zip ..."
90 | zip -r -q ../deployments/source.zip * .[!.]*
91 | echo "source.zip generated !"
92 | cd ..
93 |
94 | # Clean-up
95 | rm -r .next
96 | # rm -r standalone
97 | rm -r nodejs
98 |
99 | # Remove installed node modules
100 | npm uninstall serverless serverless-esbuild esbuild serverless-http ns-img-opt ns-img-rdr
101 |
--------------------------------------------------------------------------------
/packages/ns-build/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ns-build",
3 | "version": "1.8.1",
4 | "description": "Bash script to build serverless component for deploying to AWS using Terraform",
5 | "bin": {
6 | "ns-build": "./bin/ns-build.sh"
7 | },
8 | "license": "Apache-2.0",
9 | "main": "index.js",
10 | "scripts": {
11 | "pre-publish": "tsc"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/emyriounis/terraform-aws-nextjs-serverless.git"
16 | },
17 | "author": "Eleftherios Myriounis",
18 | "bugs": {
19 | "url": "https://github.com/emyriounis/terraform-aws-nextjs-serverless/issues"
20 | },
21 | "homepage": "https://github.com/emyriounis/terraform-aws-nextjs-serverless#readme",
22 | "dependencies": {
23 | "serverless-http": "^3.2.0"
24 | },
25 | "peerDependencies": {
26 | "next": "^13.0.0 || ^14.0.0"
27 | },
28 | "devDependencies": {
29 | "@types/aws-lambda": "^8.10.147",
30 | "@types/node": "^20.12.7",
31 | "typescript": "^5.4.5"
32 | }
33 | }
--------------------------------------------------------------------------------
/packages/ns-build/server.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4 | return new (P || (P = Promise))(function (resolve, reject) {
5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8 | step((generator = generator.apply(thisArg, _arguments || [])).next());
9 | });
10 | };
11 | var __importDefault = (this && this.__importDefault) || function (mod) {
12 | return (mod && mod.__esModule) ? mod : { "default": mod };
13 | };
14 | var _a, _b;
15 | Object.defineProperty(exports, "__esModule", { value: true });
16 | exports.handler = void 0;
17 | const fs_1 = __importDefault(require("fs"));
18 | const path_1 = __importDefault(require("path"));
19 | const next_server_1 = __importDefault(require("next/dist/server/next-server"));
20 | const serverless_http_1 = __importDefault(require("serverless-http"));
21 | // @ts-ignore
22 | const required_server_files_json_1 = require("./.next/required-server-files.json");
23 | const imageTypes = (_b = (_a = process.env.CUSTOM_IMAGE_TYPES) === null || _a === void 0 ? void 0 : _a.split(',')) !== null && _b !== void 0 ? _b : [
24 | 'webp',
25 | 'jpeg',
26 | 'jpg',
27 | 'png',
28 | 'gif',
29 | 'ico',
30 | 'svg',
31 | ];
32 | const showDebugLogs = process.env.SHOW_DEBUG_LOGS === 'true';
33 | // Check if the custom server-side props handler should be used.
34 | const useCustomServerSidePropsHandler = (path) => process.env.DEFAULT_SS_PROPS_HANDLER !== 'true' &&
35 | path.includes('/_next/data/');
36 | const parseCookies = (cookies = []) => {
37 | const parsedCookies = {};
38 | for (const cookie of cookies) {
39 | const parts = cookie.split(';');
40 | for (const part of parts) {
41 | const [key, value] = part.split('=');
42 | if (key && value) {
43 | parsedCookies[key.trim()] = decodeURIComponent(value.trim());
44 | }
45 | }
46 | }
47 | return parsedCookies;
48 | };
49 | // Modify the event object to match the one expected by Next.JS
50 | const parseEvent = (event) => {
51 | const parsedEvent = Object.assign(event);
52 | parsedEvent.path = parsedEvent.rawPath;
53 | parsedEvent.headers.host = parsedEvent.headers['x-forwarded-host'];
54 | return parsedEvent;
55 | };
56 | // Convert route file path to regex
57 | const routeToRegex = (filePath) => {
58 | const relativePath = filePath
59 | .replace('.next/server/pages', '')
60 | .replace(/\.js$/, '');
61 | const regexPattern = relativePath
62 | .replace(/\/\[\[\.\.\.(\w+)\]\]/g, '(?:/(.*))?') // Handle [[...param]] correctly (no extra slash)
63 | .replace(/\[\.\.\.(\w+)\]/g, '/(.*)') // Handle [...param]
64 | .replace(/\/?\[(\w+)\]/g, '/([^/]+)'); // Handle [param]
65 | return {
66 | regex: new RegExp('^' + regexPattern + '$'),
67 | paramNames: [
68 | ...relativePath.matchAll(/\[\[?\.\.\.(\w+)\]\]?|\[(\w+)\]/g),
69 | ].map(m => m[1] || m[2]),
70 | filePath,
71 | };
72 | };
73 | const depth = (path) => path.split('/').length; // Count path depth
74 | const dynamicCount = (path) => (path.match(/\[([^\]]+)\]/g) || []).length; // Count dynamic segments
75 | const isCatchAll = (path) => path.includes('[...');
76 | const isOptionalCatchAll = (path) => path.includes('[[...');
77 | const compareRoutes = (a, b) => {
78 | // 1. Sort by absolute path depth (more nested = higher priority)
79 | const depthDiff = depth(b) - depth(a); // Reverse order (deeper first)
80 | if (depthDiff !== 0)
81 | return depthDiff;
82 | // 2. Fewer dynamic segments take priority
83 | const dynamicDiff = dynamicCount(a) - dynamicCount(b);
84 | if (dynamicDiff !== 0)
85 | return dynamicDiff;
86 | // 3. Catch-all `[...param]` has lower priority than `[param]`
87 | const aCatchAll = isCatchAll(a);
88 | const bCatchAll = isCatchAll(b);
89 | if (aCatchAll !== bCatchAll)
90 | return aCatchAll ? 1 : -1;
91 | // 4. Optional catch-all `[[...param]]` has the lowest priority
92 | const aOptionalCatchAll = isOptionalCatchAll(a);
93 | const bOptionalCatchAll = isOptionalCatchAll(b);
94 | if (aOptionalCatchAll !== bOptionalCatchAll)
95 | return aOptionalCatchAll ? 1 : -1;
96 | return 0; // Same priority
97 | };
98 | // Get all Next.js dynamic routes
99 | const getAllNextRoutes = () => {
100 | const dir = './.next/server/pages';
101 | const allFiles = [];
102 | function traverse(subdir) {
103 | fs_1.default.readdirSync(subdir, { withFileTypes: true }).forEach(file => {
104 | const fullPath = path_1.default.join(subdir, file.name);
105 | if (file.isDirectory()) {
106 | traverse(fullPath);
107 | }
108 | else if (fullPath.endsWith('.js') && fullPath.includes('[')) {
109 | allFiles.push(fullPath);
110 | }
111 | });
112 | }
113 | traverse(dir);
114 | return allFiles.sort((a, b) => compareRoutes(a, b)).map(routeToRegex);
115 | };
116 | // Match a request path to a known Next.js dynamic route
117 | const matchRoute = (requestPath) => {
118 | const routes = getAllNextRoutes();
119 | showDebugLogs && console.debug('Discovered routes:', getAllNextRoutes());
120 | for (const { regex, paramNames, filePath } of routes) {
121 | showDebugLogs && console.debug({ requestPath, regex, paramNames, filePath });
122 | const match = requestPath.match(regex);
123 | if (match) {
124 | const params = paramNames.reduce((acc, param, i) => {
125 | const value = match[i + 1] ? match[i + 1].split('/') : undefined;
126 | if (value && value.length === 1) {
127 | acc[param] = value[0];
128 | }
129 | return acc;
130 | }, {});
131 | return { filePath, params };
132 | }
133 | }
134 | return null;
135 | };
136 | // Load getServerSideProps with fallback to dynamic routes
137 | const loadProps = (importPath) => __awaiter(void 0, void 0, void 0, function* () {
138 | try {
139 | const { getServerSideProps } = yield require(importPath);
140 | return { getServerSideProps };
141 | }
142 | catch (err) {
143 | showDebugLogs &&
144 | console.debug(`Failed to directly load ${importPath}, trying dynamic route match...`, err);
145 | // Extract the request path from the import path
146 | const requestPath = importPath
147 | .replace('./.next/server/pages', '')
148 | .replace(/\.js$/, '');
149 | // Try to match the request path dynamically
150 | const matchedRoute = matchRoute(requestPath);
151 | if (matchedRoute) {
152 | try {
153 | showDebugLogs &&
154 | console.debug(`Matched dynamic route: ${matchedRoute.filePath}`, {
155 | params: matchedRoute.params,
156 | });
157 | const { getServerSideProps } = yield require(matchedRoute.filePath);
158 | return { getServerSideProps, params: matchedRoute.params };
159 | }
160 | catch (fallbackErr) {
161 | showDebugLogs &&
162 | console.debug(`Fallback failed for ${matchedRoute.filePath}`, fallbackErr);
163 | }
164 | }
165 | showDebugLogs &&
166 | console.debug(`Failed to match dynamic route for ${requestPath}`);
167 | return { getServerSideProps: null };
168 | }
169 | });
170 | /**
171 | * Dynamically load server-side rendering logic based on the
172 | * requested URL path and returns the page props in a JSON response.
173 | * @param {ParsedEvent} event - An object that contains information
174 | * related to the incoming request triggering this function.
175 | * @returns Returns a response object with a status code of 200 and a body
176 | * containing the `pageProps` extracted from the custom response obtained by calling the
177 | * `getServerSideProps` function dynamically based on the requested URL path. The `pageProps` are
178 | * serialized into a JSON string before being returned.
179 | */
180 | const getProps = (event) => __awaiter(void 0, void 0, void 0, function* () {
181 | var _c, _d;
182 | const routePath = '/' +
183 | event.rawPath
184 | .replace('/_next/data/', '')
185 | .split('/')
186 | .slice(1)
187 | .join('/')
188 | .replace('.json', '');
189 | const path = './.next/server/pages' + routePath + '.js';
190 | const resolvedUrl = routePath.replace('/index', '/');
191 | showDebugLogs && console.debug({ routePath, path, resolvedUrl });
192 | /*
193 | * Dynamically import the module from the specified path and
194 | * extracts the `getServerSideProps` function from that module to load
195 | * the server-side rendering logic dynamically based on the requested URL path.
196 | */
197 | const { getServerSideProps, params } = yield loadProps(path);
198 | if (getServerSideProps === null) {
199 | return {
200 | statusCode: 500,
201 | body: JSON.stringify({ notFound: true }),
202 | };
203 | }
204 | // Provide a custom server-side rendering context for the server-side rendering.
205 | const customSsrContext = {
206 | req: event,
207 | query: (_c = event.queryStringParameters) !== null && _c !== void 0 ? _c : {},
208 | params,
209 | resolvedUrl,
210 | };
211 | showDebugLogs && console.debug({ customSsrContext });
212 | const customResponse = yield getServerSideProps(customSsrContext);
213 | showDebugLogs && console.debug({ customResponse });
214 | const redirectDestination = (_d = customResponse.redirect) === null || _d === void 0 ? void 0 : _d.destination;
215 | showDebugLogs && console.debug({ redirectDestination });
216 | // TODO: fix this
217 | if (redirectDestination) {
218 | return {
219 | statusCode: 500,
220 | body: JSON.stringify({ notFound: true }),
221 | };
222 | }
223 | const body = JSON.stringify(redirectDestination
224 | ? { __N_REDIRECT: redirectDestination, __N_SSP: true }
225 | : { pageProps: customResponse.props, __N_SSP: true });
226 | const response = {};
227 | response.statusCode = 200;
228 | response.body = body;
229 | response.headers = {
230 | 'Cache-Control': 'no-store',
231 | };
232 | showDebugLogs && console.debug({ response });
233 | return response;
234 | });
235 | // Creating the Next.js Server
236 | const nextServer = new next_server_1.default({
237 | hostname: 'localhost',
238 | port: 3000,
239 | dir: './',
240 | dev: false,
241 | conf: Object.assign({}, required_server_files_json_1.config),
242 | customServer: true,
243 | });
244 | // Creating the serverless wrapper using the `serverless-http` library.
245 | const main = (0, serverless_http_1.default)(nextServer.getRequestHandler(), {
246 | binary: ['*/*'],
247 | provider: 'aws',
248 | });
249 | /**
250 | * The handler function processes an event, checks if an image is requested, and either redirects to an
251 | * S3 bucket or calls another function based on custom server-side props.
252 | * @param {APIGatewayProxyEventV2} event - The `event` parameter typically contains information about the HTTP request
253 | * that triggered the Lambda function. This can include details such as headers, query parameters, path
254 | * parameters, request body, and more. In your code snippet, the `event` object is being used to
255 | * extract information like the path and headers of
256 | * @param {Context} context - The `context` parameter in the code snippet you provided is typically used to
257 | * provide information about the execution environment and runtime context of the function. It can
258 | * include details such as the AWS Lambda function name, version, memory limit, request ID, and more.
259 | * This information can be useful for understanding the context
260 | * @param {Callback} callback - The `callback` parameter in the `handler` function is a function that you
261 | * can call to send a response back to the caller. In this case, the response is an HTTP response
262 | * object that includes a status code and headers. When you call `callback(null, response)`, you are
263 | * indicating that
264 | * @returns The code is returning either the result of the `getProps(parsedEvent)` function if
265 | * `useCustomServerSidePropsHandler(parsedEvent.rawPath)` returns true, or the result of the
266 | * `main(parsedEvent, context)` function if `useCustomServerSidePropsHandler(parsedEvent.rawPath)`
267 | * returns false.
268 | */
269 | const handler = (event, context, callback) => {
270 | showDebugLogs && console.debug({ event });
271 | showDebugLogs && console.debug({ context });
272 | const parsedEvent = parseEvent(event);
273 | showDebugLogs && console.debug({ parsedEvent });
274 | /* If an image is requested, redirect to the corresponding S3 bucket. */
275 | if (imageTypes.some(type => parsedEvent.path.includes('.' + type))) {
276 | const response = {
277 | statusCode: 302,
278 | headers: {
279 | Location: 'https://' + parsedEvent.headers.host + '/assets' + parsedEvent.path,
280 | },
281 | };
282 | return callback(null, response);
283 | }
284 | const shouldUseCustomServerSidePropsHandler = useCustomServerSidePropsHandler(parsedEvent.rawPath);
285 | if (shouldUseCustomServerSidePropsHandler) {
286 | const rawCookies = event.cookies;
287 | Object.defineProperty(parsedEvent, 'cookies', {
288 | get: () => parseCookies(rawCookies),
289 | });
290 | showDebugLogs && console.debug({ parsedEvent });
291 | return getProps(parsedEvent);
292 | }
293 | return main(parsedEvent, context);
294 | };
295 | exports.handler = handler;
296 |
--------------------------------------------------------------------------------
/packages/ns-build/server.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import path from 'path'
3 | import { NextConfig } from 'next'
4 | import {
5 | APIGatewayProxyEventV2,
6 | APIGatewayProxyResultV2,
7 | Callback,
8 | Context,
9 | } from 'aws-lambda'
10 | import NextServer from 'next/dist/server/next-server'
11 | import serverless from 'serverless-http'
12 | // @ts-ignore
13 | import { config } from './.next/required-server-files.json'
14 |
15 | type ParsedEvent = APIGatewayProxyEventV2 & { path: string }
16 |
17 | const imageTypes = process.env.CUSTOM_IMAGE_TYPES?.split(',') ?? [
18 | 'webp',
19 | 'jpeg',
20 | 'jpg',
21 | 'png',
22 | 'gif',
23 | 'ico',
24 | 'svg',
25 | ]
26 |
27 | const showDebugLogs = process.env.SHOW_DEBUG_LOGS === 'true'
28 |
29 | // Check if the custom server-side props handler should be used.
30 | const useCustomServerSidePropsHandler = (path: string) =>
31 | process.env.DEFAULT_SS_PROPS_HANDLER !== 'true' &&
32 | path.includes('/_next/data/')
33 |
34 | const parseCookies = (cookies: string[] = []) => {
35 | const parsedCookies: Record = {}
36 |
37 | for (const cookie of cookies) {
38 | const parts = cookie.split(';')
39 |
40 | for (const part of parts) {
41 | const [key, value] = part.split('=')
42 |
43 | if (key && value) {
44 | parsedCookies[key.trim()] = decodeURIComponent(value.trim())
45 | }
46 | }
47 | }
48 |
49 | return parsedCookies
50 | }
51 |
52 | // Modify the event object to match the one expected by Next.JS
53 | const parseEvent = (event: APIGatewayProxyEventV2): ParsedEvent => {
54 | const parsedEvent: ParsedEvent = Object.assign(event)
55 |
56 | parsedEvent.path = parsedEvent.rawPath
57 | parsedEvent.headers.host = parsedEvent.headers['x-forwarded-host']
58 |
59 | return parsedEvent
60 | }
61 |
62 | // Convert route file path to regex
63 | const routeToRegex = (filePath: string) => {
64 | const relativePath = filePath
65 | .replace('.next/server/pages', '')
66 | .replace(/\.js$/, '')
67 | const regexPattern = relativePath
68 | .replace(/\/\[\[\.\.\.(\w+)\]\]/g, '(?:/(.*))?') // Handle [[...param]] correctly (no extra slash)
69 | .replace(/\[\.\.\.(\w+)\]/g, '/(.*)') // Handle [...param]
70 | .replace(/\/?\[(\w+)\]/g, '/([^/]+)') // Handle [param]
71 |
72 | return {
73 | regex: new RegExp('^' + regexPattern + '$'),
74 | paramNames: [
75 | ...relativePath.matchAll(/\[\[?\.\.\.(\w+)\]\]?|\[(\w+)\]/g),
76 | ].map(m => m[1] || m[2]),
77 | filePath,
78 | }
79 | }
80 |
81 | const depth = (path: string) => path.split('/').length // Count path depth
82 | const dynamicCount = (path: string) =>
83 | (path.match(/\[([^\]]+)\]/g) || []).length // Count dynamic segments
84 | const isCatchAll = (path: string) => path.includes('[...')
85 | const isOptionalCatchAll = (path: string) => path.includes('[[...')
86 |
87 | const compareRoutes = (a: string, b: string) => {
88 | // 1. Sort by absolute path depth (more nested = higher priority)
89 | const depthDiff = depth(b) - depth(a) // Reverse order (deeper first)
90 | if (depthDiff !== 0) return depthDiff
91 |
92 | // 2. Fewer dynamic segments take priority
93 | const dynamicDiff = dynamicCount(a) - dynamicCount(b)
94 | if (dynamicDiff !== 0) return dynamicDiff
95 |
96 | // 3. Catch-all `[...param]` has lower priority than `[param]`
97 | const aCatchAll = isCatchAll(a)
98 | const bCatchAll = isCatchAll(b)
99 | if (aCatchAll !== bCatchAll) return aCatchAll ? 1 : -1
100 |
101 | // 4. Optional catch-all `[[...param]]` has the lowest priority
102 | const aOptionalCatchAll = isOptionalCatchAll(a)
103 | const bOptionalCatchAll = isOptionalCatchAll(b)
104 | if (aOptionalCatchAll !== bOptionalCatchAll) return aOptionalCatchAll ? 1 : -1
105 |
106 | return 0 // Same priority
107 | }
108 |
109 | // Get all Next.js dynamic routes
110 | const getAllNextRoutes = () => {
111 | const dir = './.next/server/pages'
112 | const allFiles: string[] = []
113 |
114 | function traverse(subdir: string) {
115 | fs.readdirSync(subdir, { withFileTypes: true }).forEach(file => {
116 | const fullPath = path.join(subdir, file.name)
117 |
118 | if (file.isDirectory()) {
119 | traverse(fullPath)
120 | } else if (fullPath.endsWith('.js') && fullPath.includes('[')) {
121 | allFiles.push(fullPath)
122 | }
123 | })
124 | }
125 |
126 | traverse(dir)
127 |
128 | return allFiles.sort((a, b) => compareRoutes(a, b)).map(routeToRegex)
129 | }
130 |
131 | // Match a request path to a known Next.js dynamic route
132 | const matchRoute = (requestPath: string) => {
133 | const routes = getAllNextRoutes()
134 | showDebugLogs && console.debug('Discovered routes:', getAllNextRoutes())
135 |
136 | for (const { regex, paramNames, filePath } of routes) {
137 | showDebugLogs && console.debug({ requestPath, regex, paramNames, filePath })
138 | const match = requestPath.match(regex)
139 | if (match) {
140 | const params = paramNames.reduce((acc, param, i) => {
141 | const value = match[i + 1] ? match[i + 1].split('/') : undefined
142 |
143 | if (value && value.length === 1) {
144 | acc[param] = value[0]
145 | }
146 |
147 | return acc
148 | }, {} as Record)
149 |
150 | return { filePath, params }
151 | }
152 | }
153 | return null
154 | }
155 |
156 | // Load getServerSideProps with fallback to dynamic routes
157 | const loadProps = async (importPath: string) => {
158 | try {
159 | const { getServerSideProps } = await require(importPath)
160 | return { getServerSideProps }
161 | } catch (err) {
162 | showDebugLogs &&
163 | console.debug(
164 | `Failed to directly load ${importPath}, trying dynamic route match...`,
165 | err
166 | )
167 | // Extract the request path from the import path
168 | const requestPath = importPath
169 | .replace('./.next/server/pages', '')
170 | .replace(/\.js$/, '')
171 | // Try to match the request path dynamically
172 | const matchedRoute = matchRoute(requestPath)
173 | if (matchedRoute) {
174 | try {
175 | showDebugLogs &&
176 | console.debug(`Matched dynamic route: ${matchedRoute.filePath}`, {
177 | params: matchedRoute.params,
178 | })
179 | const { getServerSideProps } = await require(matchedRoute.filePath)
180 | return { getServerSideProps, params: matchedRoute.params }
181 | } catch (fallbackErr) {
182 | showDebugLogs &&
183 | console.debug(
184 | `Fallback failed for ${matchedRoute.filePath}`,
185 | fallbackErr
186 | )
187 | }
188 | }
189 |
190 | showDebugLogs &&
191 | console.debug(`Failed to match dynamic route for ${requestPath}`)
192 | return { getServerSideProps: null }
193 | }
194 | }
195 |
196 | /**
197 | * Dynamically load server-side rendering logic based on the
198 | * requested URL path and returns the page props in a JSON response.
199 | * @param {ParsedEvent} event - An object that contains information
200 | * related to the incoming request triggering this function.
201 | * @returns Returns a response object with a status code of 200 and a body
202 | * containing the `pageProps` extracted from the custom response obtained by calling the
203 | * `getServerSideProps` function dynamically based on the requested URL path. The `pageProps` are
204 | * serialized into a JSON string before being returned.
205 | */
206 | const getProps = async (event: ParsedEvent) => {
207 | const routePath =
208 | '/' +
209 | event.rawPath
210 | .replace('/_next/data/', '')
211 | .split('/')
212 | .slice(1)
213 | .join('/')
214 | .replace('.json', '')
215 | const path = './.next/server/pages' + routePath + '.js'
216 | const resolvedUrl = routePath.replace('/index', '/')
217 | showDebugLogs && console.debug({ routePath, path, resolvedUrl })
218 |
219 | /*
220 | * Dynamically import the module from the specified path and
221 | * extracts the `getServerSideProps` function from that module to load
222 | * the server-side rendering logic dynamically based on the requested URL path.
223 | */
224 | const { getServerSideProps, params } = await loadProps(path)
225 | if (getServerSideProps === null) {
226 | return {
227 | statusCode: 500,
228 | body: JSON.stringify({ notFound: true }),
229 | }
230 | }
231 |
232 | // Provide a custom server-side rendering context for the server-side rendering.
233 | const customSsrContext = {
234 | req: event,
235 | query: event.queryStringParameters ?? {},
236 | params,
237 | resolvedUrl,
238 | }
239 | showDebugLogs && console.debug({ customSsrContext })
240 |
241 | const customResponse = await getServerSideProps(customSsrContext)
242 | showDebugLogs && console.debug({ customResponse })
243 |
244 | const redirectDestination = customResponse.redirect?.destination
245 | showDebugLogs && console.debug({ redirectDestination })
246 | // TODO: fix this
247 | if (redirectDestination) {
248 | return {
249 | statusCode: 500,
250 | body: JSON.stringify({ notFound: true }),
251 | }
252 | }
253 |
254 | const body = JSON.stringify(
255 | redirectDestination
256 | ? { __N_REDIRECT: redirectDestination, __N_SSP: true }
257 | : { pageProps: customResponse.props, __N_SSP: true }
258 | )
259 |
260 | const response: APIGatewayProxyResultV2 = {}
261 | response.statusCode = 200
262 | response.body = body
263 | response.headers = {
264 | 'Cache-Control': 'no-store',
265 | }
266 | showDebugLogs && console.debug({ response })
267 |
268 | return response
269 | }
270 |
271 | // Creating the Next.js Server
272 | const nextServer = new NextServer({
273 | hostname: 'localhost',
274 | port: 3000,
275 | dir: './',
276 | dev: false,
277 | conf: {
278 | ...(config as NextConfig),
279 | },
280 | customServer: true,
281 | })
282 |
283 | // Creating the serverless wrapper using the `serverless-http` library.
284 | const main = serverless(nextServer.getRequestHandler(), {
285 | binary: ['*/*'],
286 | provider: 'aws',
287 | })
288 |
289 | /**
290 | * The handler function processes an event, checks if an image is requested, and either redirects to an
291 | * S3 bucket or calls another function based on custom server-side props.
292 | * @param {APIGatewayProxyEventV2} event - The `event` parameter typically contains information about the HTTP request
293 | * that triggered the Lambda function. This can include details such as headers, query parameters, path
294 | * parameters, request body, and more. In your code snippet, the `event` object is being used to
295 | * extract information like the path and headers of
296 | * @param {Context} context - The `context` parameter in the code snippet you provided is typically used to
297 | * provide information about the execution environment and runtime context of the function. It can
298 | * include details such as the AWS Lambda function name, version, memory limit, request ID, and more.
299 | * This information can be useful for understanding the context
300 | * @param {Callback} callback - The `callback` parameter in the `handler` function is a function that you
301 | * can call to send a response back to the caller. In this case, the response is an HTTP response
302 | * object that includes a status code and headers. When you call `callback(null, response)`, you are
303 | * indicating that
304 | * @returns The code is returning either the result of the `getProps(parsedEvent)` function if
305 | * `useCustomServerSidePropsHandler(parsedEvent.rawPath)` returns true, or the result of the
306 | * `main(parsedEvent, context)` function if `useCustomServerSidePropsHandler(parsedEvent.rawPath)`
307 | * returns false.
308 | */
309 | export const handler = (
310 | event: APIGatewayProxyEventV2,
311 | context: Context,
312 | callback: Callback
313 | ) => {
314 | showDebugLogs && console.debug({ event })
315 | showDebugLogs && console.debug({ context })
316 |
317 | const parsedEvent = parseEvent(event)
318 | showDebugLogs && console.debug({ parsedEvent })
319 |
320 | /* If an image is requested, redirect to the corresponding S3 bucket. */
321 | if (imageTypes.some(type => parsedEvent.path.includes('.' + type))) {
322 | const response = {
323 | statusCode: 302,
324 | headers: {
325 | Location:
326 | 'https://' + parsedEvent.headers.host + '/assets' + parsedEvent.path,
327 | },
328 | }
329 |
330 | return callback(null, response)
331 | }
332 |
333 | const shouldUseCustomServerSidePropsHandler = useCustomServerSidePropsHandler(
334 | parsedEvent.rawPath
335 | )
336 | if (shouldUseCustomServerSidePropsHandler) {
337 | const rawCookies = event.cookies
338 | Object.defineProperty(parsedEvent, 'cookies', {
339 | get: () => parseCookies(rawCookies),
340 | })
341 | showDebugLogs && console.debug({ parsedEvent })
342 |
343 | return getProps(parsedEvent)
344 | }
345 |
346 | return main(parsedEvent, context)
347 | }
348 |
--------------------------------------------------------------------------------
/packages/ns-build/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Enable incremental compilation */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16 | // "jsx": "preserve", /* Specify what JSX code is generated. */
17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25 |
26 | /* Modules */
27 | "module": "commonjs" /* Specify what module code is generated. */,
28 | // "rootDir": "./", /* Specify the root folder within your source files. */
29 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
30 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
34 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
36 | // "resolveJsonModule": true, /* Enable importing .json files */
37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */
38 |
39 | /* JavaScript Support */
40 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
43 |
44 | /* Emit */
45 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
46 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
47 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
48 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
49 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
50 | // "outDir": "./", /* Specify an output folder for all emitted files. */
51 | // "removeComments": true, /* Disable emitting comments. */
52 | // "noEmit": true, /* Disable emitting files from a compilation. */
53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
61 | // "newLine": "crlf", /* Set the newline character for emitting files. */
62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
67 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
68 |
69 | /* Interop Constraints */
70 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
71 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
72 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
73 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
74 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
75 |
76 | /* Type Checking */
77 | "strict": true /* Enable all strict type-checking options. */,
78 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
79 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
80 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
81 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
82 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
83 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
84 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
85 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
86 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
87 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
88 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
89 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
90 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
91 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
92 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
93 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
94 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
95 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
96 |
97 | /* Completeness */
98 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
99 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/packages/ns-img-opt/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | the copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor to discuss and improve the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by the Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributors that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, the Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assuming any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | Copyright 2024\
179 | Eleftherios Myriounis
180 |
181 | Licensed under the Apache License, Version 2.0 (the "License");
182 | you may not use this file except in compliance with the License.
183 | You may obtain a copy of the License at
184 |
185 | http://www.apache.org/licenses/LICENSE-2.0
186 |
187 | Unless required by applicable law or agreed to in writing, software
188 | distributed under the License is distributed on an "AS IS" BASIS,
189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190 | See the License for the specific language governing permissions and
191 | limitations under the License.
--------------------------------------------------------------------------------
/packages/ns-img-opt/README.md:
--------------------------------------------------------------------------------
1 | ## image-optimization lambda
2 |
3 | ### Build
4 |
5 | 1. Edit the source code
6 |
7 | 2. Run `npm run prepare-lambda` to build source.zip
8 |
--------------------------------------------------------------------------------
/packages/ns-img-opt/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ns-img-opt",
3 | "version": "1.8.1",
4 | "description": "Image Optimization Lambda",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "tsc",
8 | "pre-publish": "bash scripts/pre-publish.sh"
9 | },
10 | "license": "Apache-2.0",
11 | "devDependencies": {
12 | "@types/node": "^20.8.4",
13 | "ts-node": "^10.9.1",
14 | "typescript": "^5.2.2"
15 | },
16 | "dependencies": {
17 | "@aws-sdk/client-s3": "^3.433.0",
18 | "sharp": "^0.32.6"
19 | },
20 | "files": [
21 | "source.zip"
22 | ],
23 | "repository": {
24 | "type": "git",
25 | "url": "git+https://github.com/emyriounis/terraform-aws-nextjs-serverless.git"
26 | },
27 | "author": "Eleftherios Myriounis",
28 | "bugs": {
29 | "url": "https://github.com/emyriounis/terraform-aws-nextjs-serverless/issues"
30 | },
31 | "homepage": "https://github.com/emyriounis/terraform-aws-nextjs-serverless#readme"
32 | }
--------------------------------------------------------------------------------
/packages/ns-img-opt/scripts/pre-publish.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # build
4 | rm -r build/
5 | npm run build
6 |
7 | # add node_modules
8 | rm -r node_modules/
9 | npm install --arch=x64 --platform=linux --omit=dev
10 | cp -r node_modules/ build/node_modules/
11 |
12 | # zip
13 | cd build/
14 | rm -r ../source.zip
15 | zip -r -q ../source.zip *
16 | cd ..
17 |
18 | # cleanup
19 | rm -r build/
20 | npm i
21 |
--------------------------------------------------------------------------------
/packages/ns-img-opt/src/constants.ts:
--------------------------------------------------------------------------------
1 | export const defaults = {
2 | width: 256,
3 | quality: 75,
4 | }
5 |
--------------------------------------------------------------------------------
/packages/ns-img-opt/src/helpers.ts:
--------------------------------------------------------------------------------
1 | import { HeadObjectCommand, S3Client } from "@aws-sdk/client-s3"
2 |
3 | /**
4 | * The function `redirectTo` is used to create a redirect response with a specified URL.
5 | * @param {string} url - The `url` parameter is a string that represents the URL to which you want to
6 | * redirect the user.
7 | * @param {any} callback - The `callback` parameter is a function that is used to return the response
8 | * to the caller. It takes two arguments: an error object (if any) and the response object. In this
9 | * case, the response object is an HTTP response with a status code of 302 (Redirect) and a `
10 | * @returns a callback function with two arguments: null and an object representing a response.
11 | */
12 | export const redirectTo = (url: string, callback: any) => {
13 | const response = {
14 | status: 302,
15 | statusDescription: 'Redirect',
16 | headers: {
17 | location: [
18 | {
19 | key: 'Location',
20 | value: url,
21 | },
22 | ],
23 | 'cache-control': [
24 | {
25 | key: 'Cache-Control',
26 | value: 'public, max-age=600, stale-while-revalidate=2592000', // Serve cached content up to 30 days old while revalidating it after 10 minutes
27 | },
28 | ],
29 | },
30 | }
31 |
32 | return callback(null, response)
33 | }
34 |
35 | export const isVersionStored = async (s3Client: S3Client, bucket: string, key: string): Promise => {
36 | try {
37 | const command = new HeadObjectCommand({
38 | Bucket: bucket,
39 | Key: key,
40 | })
41 |
42 | // will throw error if it's not found
43 | await s3Client.send(command)
44 | return true
45 | } catch (error) {
46 | console.warn(`${key} is not stored yet`, error);
47 | return false
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/packages/ns-img-opt/src/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | GetObjectCommand,
3 | PutObjectCommand,
4 | S3Client,
5 | } from '@aws-sdk/client-s3'
6 | import sharp from 'sharp'
7 |
8 | import { defaults } from './constants'
9 | import { isVersionStored, redirectTo } from './helpers'
10 |
11 | /**
12 | * This TypeScript function is a CloudFront function that resizes and compresses images based on the
13 | * request URI and returns the resized image as a base64 encoded string.
14 | * @param {any} event - The `event` parameter is an object that contains information about the event
15 | * that triggered the Lambda function. In this case, it contains the CloudFront event data, which
16 | * includes details about the request and configuration.
17 | * @param {any} _context - The `_context` parameter is a context object that contains information about
18 | * the execution environment and runtime. It is typically not used in this code snippet, so it can be
19 | * ignored for now.
20 | * @param {any} callback - The `callback` parameter is a function that is used to send the response
21 | * back to the caller. It takes two arguments: an error object (or null if there is no error) and the
22 | * response object. The response object should contain the status code, status description, headers,
23 | * body encoding, and
24 | * @returns The code is returning a response object with the following properties:
25 | */
26 | export const handler = async (event: any, _context: any, callback: any) => {
27 | try {
28 | /* Extract the `request` and `config` properties. */
29 | const { request, config } = event?.Records?.[0]?.cf
30 | /* Construct the base URL for the image assets. */
31 | const baseUrl = `https://${config?.distributionDomainName}/`
32 |
33 | /* The S3 region. */
34 | const s3Region =
35 | request?.origin?.custom?.customHeaders?.['s3-region']?.[0]?.value
36 | /* The public_assets_bucket name. */
37 | const publicAssetsBucket =
38 | request?.origin?.custom?.customHeaders?.['public-assets-bucket']?.[0]
39 | ?.value
40 |
41 | /* Extracting the relevant information from the request URI. */
42 | const queryString = (request?.uri as string)
43 | ?.replace('/_next/image/', '')
44 | ?.split('/')
45 | // Map required info
46 | const width = parseInt(queryString?.[0] || defaults.width.toString())
47 | const type = queryString?.[1]
48 | const filename = queryString?.slice(2)?.join('/').replace('%2F', '/')
49 |
50 | /* The S3 Client. */
51 | const s3 = new S3Client({ region: s3Region })
52 | const resizedImageFilename = `resized-assets/${width}/${type}/${filename}`
53 |
54 | const isVersionAlreadyResized = await isVersionStored(s3, publicAssetsBucket, resizedImageFilename)
55 | if (isVersionAlreadyResized) {
56 | return redirectTo(baseUrl + resizedImageFilename, callback)
57 | }
58 |
59 | // The url where the image is stored
60 | const imageUrl = baseUrl + 'assets/' + filename
61 | // The options for image transformation
62 | const options = { quality: defaults.quality }
63 |
64 | /* Build the s3 command. */
65 | const s3GetObjectCommand = new GetObjectCommand({
66 | Bucket: publicAssetsBucket,
67 | Key: 'assets/' + filename,
68 | })
69 |
70 | /* The body of the S3 object. */
71 | const { Body } = await s3.send(s3GetObjectCommand)
72 | /* Transforming the body of the S3 object into a byte array. */
73 | const s3Object = await Body.transformToByteArray()
74 |
75 | /* Resize and compress the image. */
76 | const resizedImage = sharp(s3Object).resize({ width })
77 |
78 | let newContentType: string | null = null
79 | /* Apply the corresponding image type transformation. */
80 | switch (type) {
81 | case 'webp':
82 | resizedImage.webp(options)
83 | newContentType = 'image/webp'
84 | break
85 | case 'jpeg':
86 | resizedImage.jpeg(options)
87 | newContentType = 'image/jpeg'
88 | break
89 | case 'png':
90 | resizedImage.png(options)
91 | newContentType = 'image/png'
92 | break
93 | // case 'gif':
94 | // // resizedImage.gif(options)
95 | // resizedImage.gif()
96 | // newContentType = 'image/gif'
97 | // break
98 | // case 'apng':
99 | // // resizedImage.apng(options)
100 | // resizedImage.png(options)
101 | // newContentType = 'image/apng'
102 | // break
103 | // case 'avif':
104 | // resizedImage.avif(options)
105 | // newContentType = 'image/avif'
106 | // break
107 | // // case 'svg+xml':
108 | // // resizedImage.svg(options)
109 | // // newContentType = 'image/svg+xml'
110 | // // break
111 |
112 | default:
113 | return redirectTo(imageUrl, callback)
114 | }
115 |
116 | /* Converting the resized image into a buffer. */
117 | const resizedImageBuffer = await resizedImage.toBuffer()
118 | /* Store the resized image */
119 | const s3PutObjectCommand = new PutObjectCommand({
120 | Bucket: publicAssetsBucket,
121 | Key: resizedImageFilename,
122 | Body: resizedImageBuffer,
123 | ContentType: newContentType,
124 | })
125 | await s3.send(s3PutObjectCommand)
126 |
127 | return redirectTo(baseUrl + resizedImageFilename, callback)
128 | } catch (error) {
129 | console.error('An unexpected occured', error)
130 |
131 | return callback(null, {
132 | status: 403, // to not leak data
133 | })
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/packages/ns-img-opt/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES6",
4 | "module": "commonjs",
5 | "strict": true,
6 | "strictNullChecks": false,
7 | "esModuleInterop": true,
8 | "skipLibCheck": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "outDir": "./build",
11 | "rootDir": "./src"
12 | },
13 | "include": ["src/**/*.ts"],
14 | "exclude": ["node_modules"]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/ns-img-rdr/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | the copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor to discuss and improve the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by the Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributors that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, the Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assuming any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | Copyright 2024\
179 | Eleftherios Myriounis
180 |
181 | Licensed under the Apache License, Version 2.0 (the "License");
182 | you may not use this file except in compliance with the License.
183 | You may obtain a copy of the License at
184 |
185 | http://www.apache.org/licenses/LICENSE-2.0
186 |
187 | Unless required by applicable law or agreed to in writing, software
188 | distributed under the License is distributed on an "AS IS" BASIS,
189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190 | See the License for the specific language governing permissions and
191 | limitations under the License.
--------------------------------------------------------------------------------
/packages/ns-img-rdr/README.md:
--------------------------------------------------------------------------------
1 | ## image-optimization lambda
2 |
3 | ### Build
4 |
5 | 1. Edit the source code
6 |
7 | 2. Run `npm run prepare-lambda` to build source.zip
8 |
--------------------------------------------------------------------------------
/packages/ns-img-rdr/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ns-img-rdr",
3 | "version": "1.8.1",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "ns-img-rdr",
9 | "version": "1.8.1",
10 | "license": "Apache-2.0",
11 | "devDependencies": {
12 | "@types/node": "^20.8.4",
13 | "ts-node": "^10.9.1",
14 | "typescript": "^5.2.2"
15 | }
16 | },
17 | "node_modules/@cspotcode/source-map-support": {
18 | "version": "0.8.1",
19 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
20 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
21 | "dev": true,
22 | "dependencies": {
23 | "@jridgewell/trace-mapping": "0.3.9"
24 | },
25 | "engines": {
26 | "node": ">=12"
27 | }
28 | },
29 | "node_modules/@jridgewell/resolve-uri": {
30 | "version": "3.1.1",
31 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
32 | "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
33 | "dev": true,
34 | "engines": {
35 | "node": ">=6.0.0"
36 | }
37 | },
38 | "node_modules/@jridgewell/sourcemap-codec": {
39 | "version": "1.4.15",
40 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
41 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
42 | "dev": true
43 | },
44 | "node_modules/@jridgewell/trace-mapping": {
45 | "version": "0.3.9",
46 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
47 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
48 | "dev": true,
49 | "dependencies": {
50 | "@jridgewell/resolve-uri": "^3.0.3",
51 | "@jridgewell/sourcemap-codec": "^1.4.10"
52 | }
53 | },
54 | "node_modules/@tsconfig/node10": {
55 | "version": "1.0.9",
56 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
57 | "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
58 | "dev": true
59 | },
60 | "node_modules/@tsconfig/node12": {
61 | "version": "1.0.11",
62 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
63 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
64 | "dev": true
65 | },
66 | "node_modules/@tsconfig/node14": {
67 | "version": "1.0.3",
68 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
69 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
70 | "dev": true
71 | },
72 | "node_modules/@tsconfig/node16": {
73 | "version": "1.0.4",
74 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
75 | "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
76 | "dev": true
77 | },
78 | "node_modules/@types/node": {
79 | "version": "20.8.6",
80 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.6.tgz",
81 | "integrity": "sha512-eWO4K2Ji70QzKUqRy6oyJWUeB7+g2cRagT3T/nxYibYcT4y2BDL8lqolRXjTHmkZCdJfIPaY73KbJAZmcryxTQ==",
82 | "dev": true,
83 | "dependencies": {
84 | "undici-types": "~5.25.1"
85 | }
86 | },
87 | "node_modules/acorn": {
88 | "version": "8.10.0",
89 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
90 | "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
91 | "dev": true,
92 | "bin": {
93 | "acorn": "bin/acorn"
94 | },
95 | "engines": {
96 | "node": ">=0.4.0"
97 | }
98 | },
99 | "node_modules/acorn-walk": {
100 | "version": "8.2.0",
101 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
102 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
103 | "dev": true,
104 | "engines": {
105 | "node": ">=0.4.0"
106 | }
107 | },
108 | "node_modules/arg": {
109 | "version": "4.1.3",
110 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
111 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
112 | "dev": true
113 | },
114 | "node_modules/create-require": {
115 | "version": "1.1.1",
116 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
117 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
118 | "dev": true
119 | },
120 | "node_modules/diff": {
121 | "version": "4.0.2",
122 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
123 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
124 | "dev": true,
125 | "engines": {
126 | "node": ">=0.3.1"
127 | }
128 | },
129 | "node_modules/make-error": {
130 | "version": "1.3.6",
131 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
132 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
133 | "dev": true
134 | },
135 | "node_modules/ts-node": {
136 | "version": "10.9.1",
137 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
138 | "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
139 | "dev": true,
140 | "dependencies": {
141 | "@cspotcode/source-map-support": "^0.8.0",
142 | "@tsconfig/node10": "^1.0.7",
143 | "@tsconfig/node12": "^1.0.7",
144 | "@tsconfig/node14": "^1.0.0",
145 | "@tsconfig/node16": "^1.0.2",
146 | "acorn": "^8.4.1",
147 | "acorn-walk": "^8.1.1",
148 | "arg": "^4.1.0",
149 | "create-require": "^1.1.0",
150 | "diff": "^4.0.1",
151 | "make-error": "^1.1.1",
152 | "v8-compile-cache-lib": "^3.0.1",
153 | "yn": "3.1.1"
154 | },
155 | "bin": {
156 | "ts-node": "dist/bin.js",
157 | "ts-node-cwd": "dist/bin-cwd.js",
158 | "ts-node-esm": "dist/bin-esm.js",
159 | "ts-node-script": "dist/bin-script.js",
160 | "ts-node-transpile-only": "dist/bin-transpile.js",
161 | "ts-script": "dist/bin-script-deprecated.js"
162 | },
163 | "peerDependencies": {
164 | "@swc/core": ">=1.2.50",
165 | "@swc/wasm": ">=1.2.50",
166 | "@types/node": "*",
167 | "typescript": ">=2.7"
168 | },
169 | "peerDependenciesMeta": {
170 | "@swc/core": {
171 | "optional": true
172 | },
173 | "@swc/wasm": {
174 | "optional": true
175 | }
176 | }
177 | },
178 | "node_modules/typescript": {
179 | "version": "5.2.2",
180 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
181 | "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
182 | "dev": true,
183 | "bin": {
184 | "tsc": "bin/tsc",
185 | "tsserver": "bin/tsserver"
186 | },
187 | "engines": {
188 | "node": ">=14.17"
189 | }
190 | },
191 | "node_modules/undici-types": {
192 | "version": "5.25.3",
193 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz",
194 | "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==",
195 | "dev": true
196 | },
197 | "node_modules/v8-compile-cache-lib": {
198 | "version": "3.0.1",
199 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
200 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
201 | "dev": true
202 | },
203 | "node_modules/yn": {
204 | "version": "3.1.1",
205 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
206 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
207 | "dev": true,
208 | "engines": {
209 | "node": ">=6"
210 | }
211 | }
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/packages/ns-img-rdr/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ns-img-rdr",
3 | "version": "1.8.1",
4 | "description": "Image Redirection Lambda",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "tsc",
8 | "pre-publish": "bash scripts/pre-publish.sh"
9 | },
10 | "license": "Apache-2.0",
11 | "devDependencies": {
12 | "@types/node": "^20.8.4",
13 | "ts-node": "^10.9.1",
14 | "typescript": "^5.2.2"
15 | },
16 | "files": [
17 | "source.zip"
18 | ],
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/emyriounis/terraform-aws-nextjs-serverless.git"
22 | },
23 | "author": "Eleftherios Myriounis",
24 | "bugs": {
25 | "url": "https://github.com/emyriounis/terraform-aws-nextjs-serverless/issues"
26 | },
27 | "homepage": "https://github.com/emyriounis/terraform-aws-nextjs-serverless#readme"
28 | }
--------------------------------------------------------------------------------
/packages/ns-img-rdr/scripts/pre-publish.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # build
4 | rm -r build/
5 | npm run build
6 |
7 | # zip
8 | cd build/
9 | rm -r ../source.zip
10 | zip -r -q ../source.zip *
11 | cd ..
12 |
--------------------------------------------------------------------------------
/packages/ns-img-rdr/src/index.ts:
--------------------------------------------------------------------------------
1 | /* A list of supported image types.
2 | It contains the MIME types for various image formats.
3 | These image types are prioritized in the array, with the most preferred format at the beginning. */
4 | const imageTypes = [
5 | 'image/webp',
6 | // 'image/avif',
7 | 'image/jpeg',
8 | 'image/png',
9 | // 'image/svg+xml',
10 | // 'image/gif',
11 | // 'image/apng',
12 | ]
13 |
14 | /* A list of specific image widths. These values represent different width
15 | dimensions that are commonly used for images. */
16 | const imageWidths = [16, 32, 64, 128, 256, 512, 1024]
17 |
18 | /**
19 | * Returns the closest image width from a predefined list that is greater
20 | * than or equal to the requested width, with a maximum limit of 1024.
21 | * @param {number} requestedWidth - The width of an image that a user is requesting.
22 | * @returns The first width in the `imageWidths` array that is greater than or equal to the `requestedWidth`.
23 | * If no such width is found, it will return 1024 as a fallback value to avoid exceeding cloudfront limits.
24 | */
25 | const mapImageWidth = (requestedWidth: string): string => {
26 | const requestedWidthInt = parseInt(requestedWidth)
27 |
28 | for (const width of imageWidths) {
29 | if (width < requestedWidthInt) continue
30 |
31 | return width.toString()
32 | }
33 |
34 | return '1024' // avoid bigger widths, they'll hit cloudfront limits
35 | }
36 |
37 | /**
38 | * The function `redirectTo` is used to create a redirect response with a specified URL.
39 | * @param {string} url - The `url` parameter is a string that represents the URL to which you want to
40 | * redirect the user.
41 | * @param {any} callback - The `callback` parameter is a function that is used to return the response
42 | * to the caller. It takes two arguments: an error object (if any) and the response object. In this
43 | * case, the response object is an HTTP response with a status code of 302 (Redirect) and a `
44 | * @returns a callback function with two arguments: null and an object representing a response.
45 | */
46 | const redirectTo = (url: string, callback: any) => {
47 | const response = {
48 | status: 302,
49 | statusDescription: 'Redirect',
50 | headers: {
51 | location: [
52 | {
53 | key: 'Location',
54 | value: url,
55 | },
56 | ],
57 | 'cache-control': [
58 | {
59 | key: 'Cache-Control',
60 | value: 'public, max-age=600, stale-while-revalidate=2592000', // Serve cached content up to 30 days old while revalidating it after 10 minutes
61 | },
62 | ],
63 | },
64 | }
65 |
66 | return callback(null, response)
67 | }
68 |
69 | /**
70 | * This TypeScript function handles image requests and redirects them to the appropriate image URL
71 | * based on the request parameters and headers.
72 | * @param {any} event - The `event` parameter is an object that contains information about the event
73 | * that triggered the Lambda function. In this case, it is expected to have a `Records` property, which
74 | * is an array of records. Each record represents a CloudFront event and contains information about the
75 | * request and configuration.
76 | * @param {any} _context - The `_context` parameter is a context object that contains information about
77 | * the execution environment and runtime. It is typically used to access information such as the AWS
78 | * Lambda function name, version, and memory limit. In this code snippet, the `_context` parameter is
79 | * not used, so it can be safely ignored
80 | * @param {any} callback - The `callback` parameter is a function that you can use to send a response
81 | * back to the caller. It takes two arguments: an error object (or null if there is no error) and a
82 | * response object. The response object should contain the necessary information to return a response
83 | * to the caller, such
84 | * @returns The code is returning a redirect response to a specified URL.
85 | */
86 | export const handler = async (event: any, _context: any, callback: any) => {
87 | try {
88 | /* Extract the `request` and `config` properties. */
89 | const { request, config } = event?.Records?.[0]?.cf
90 |
91 | /* This forms the base URL for the redirect. */
92 | const baseUrl = '/_next/image'
93 | /* Parsing the query string from the request URL and converting it into an object. */
94 | const query: Record = request?.querystring
95 | ?.split('&')
96 | .map((q: string) => q.split('='))
97 | .reduce(
98 | (acc: Record, q: string) => ({
99 | ...acc,
100 | [q[0]]: q[1],
101 | }),
102 | {}
103 | )
104 |
105 | // Return original image if it's remote image
106 | if (/^(http|https)%3A%2F%2F/.test(query.url)) {
107 | /* The URL for the original image. */
108 | const imageUrl = query.url.replace(/%3A/g, ':').replace(/%2F/g, '/')
109 | console.log({ imageUrl })
110 | return redirectTo(imageUrl, callback)
111 | }
112 |
113 | // Return original image if it's static image
114 | if (/_next/.test(query.url)) {
115 | /* The URL for the original image. */
116 | const imageUrl =
117 | 'https://' +
118 | config?.distributionDomainName +
119 | query.url.replace(/%2F/g, '/')
120 | return redirectTo(imageUrl, callback)
121 | }
122 |
123 | // Return original image if it's image/gif or image/svg+xml
124 | // OR is the feature is disabled
125 | const isFeatureEnabled =
126 | request?.origin?.custom?.customHeaders?.['enable-image-optimization']?.[0]?.value === 'true'
127 | const regex = /\.(gif|svg|xml)$/
128 | if (regex.test(query.url) || !isFeatureEnabled) {
129 | /* The URL for the original image. */
130 | const imageUrl =
131 | 'https://' +
132 | config?.distributionDomainName +
133 | '/assets' +
134 | query.url.replace(/%2F/g, '/')
135 | return redirectTo(imageUrl, callback)
136 | }
137 |
138 | /* Extract the value of the "accept" header from the request headers. */
139 | const acceptHeader: string = request?.headers?.accept?.find(
140 | (item: Record) => item.key === 'accept'
141 | )?.value
142 | /* Create a list with accepted image types. */
143 | const acceptedTypes = acceptHeader
144 | ?.split(',')
145 | ?.filter((type: string) => type.startsWith('image/'))
146 |
147 | /* Default value in case none of the accepted image types match the supported image types. */
148 | let requestType = imageTypes[0]
149 | /* Find a prefered type that is accepted */
150 | for (const type of imageTypes) {
151 | if (acceptedTypes.includes(type)) {
152 | requestType = type
153 | break
154 | }
155 | }
156 |
157 | const { w: requestedWidth, url } = query
158 | const width = mapImageWidth(requestedWidth)
159 | /* Creating a URL string for the redirect. */
160 | const redirectToUrl = [
161 | baseUrl,
162 | width,
163 | requestType.replace('image/', ''),
164 | url.replace('%2F', ''),
165 | ]
166 | .join('/')
167 | .replace(/%2F/g, '/')
168 | .replace('/assets', '')
169 |
170 | return redirectTo(redirectToUrl, callback)
171 | } catch (error) {
172 | console.error({ error })
173 |
174 | return callback(null, {
175 | status: 403, // to not leak data
176 | })
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/packages/ns-img-rdr/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES6",
4 | "module": "commonjs",
5 | "strict": true,
6 | "strictNullChecks": false,
7 | "esModuleInterop": true,
8 | "skipLibCheck": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "outDir": "./build",
11 | "rootDir": "./src"
12 | },
13 | "include": ["src/**/*.ts"],
14 | "exclude": ["node_modules"]
15 | }
16 |
--------------------------------------------------------------------------------
/variables.tf:
--------------------------------------------------------------------------------
1 | variable "deployment_name" {
2 | description = "The name that will be used in the resources, must be unique. We recommend to use up to 20 characters"
3 | type = string
4 | }
5 |
6 | variable "region" {
7 | description = "The AWS region you wish to deploy your resources (ex. eu-central-1)"
8 | type = string
9 | }
10 |
11 | variable "base_dir" {
12 | description = "The base directory of the next.js app"
13 | type = string
14 | default = "./"
15 | }
16 |
17 | variable "cloudfront_acm_certificate_arn" {
18 | description = "The certificate ARN for the cloudfront_aliases (CloudFront works only with certs stored in us-east-1)"
19 | type = string
20 | default = null
21 | }
22 |
23 | # If you need a wildcard domain(ex: *.example.com), you can add it like this:
24 | # aliases = [var.custom_domain, "*.${var.custom_domain}"]
25 | variable "cloudfront_aliases" {
26 | description = "A list of custom domain for the cloudfront distribution, e.g. www.my-nextjs-app.com"
27 | type = list(string)
28 | default = []
29 | }
30 |
31 | variable "cloudfront_price_class" {
32 | description = "Price class for the CloudFront distribution. Options: PriceClass_All, PriceClass_200, PriceClass_100"
33 | type = string
34 | default = "PriceClass_100"
35 | }
36 |
37 | # Example:
38 | # next_lambda_env_vars = {
39 | # BACKEND_VIRTUAL_DOMAIN = "backend.example.com"
40 | # NEXT_PUBLIC_RECAPTCHA_KEY = "recaptcha-key"
41 | # }
42 | variable "next_lambda_env_vars" {
43 | description = "Map of environment variables that you want to pass to the lambda"
44 | type = map(any)
45 | default = {}
46 | }
47 |
48 | variable "custom_image_types" {
49 | description = "List of image file extentions that you store in the public/ directory. Defaults to ('webp', 'jpeg', 'jpg', 'png', 'gif', 'ico', 'svg')"
50 | type = list(string)
51 | default = ["webp", "jpeg", "jpg", "png", "gif", "ico", "svg"]
52 | }
53 |
54 | variable "next_lambda_policy_statements" {
55 | description = "Map of dynamic policy statements to attach to Lambda Function role"
56 | type = map(any)
57 | default = {}
58 | }
59 |
60 | variable "next_lambda_memory_size" {
61 | description = "The memory size for the server side rendering Lambda (Set memory to between 128 MB and 10240 MB)"
62 | type = number
63 | default = 4096
64 | }
65 |
66 | variable "next_lambda_runtime" {
67 | description = "The runtime for the next lambda (nodejs16.x or nodejs20.x)"
68 | type = string
69 | default = "nodejs20.x"
70 | }
71 |
72 | variable "next_lambda_logs_retention" {
73 | description = "The number of days that cloudwatch logs of next lambda should be retained (Possible values are: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, and 3653)"
74 | type = number
75 | default = 30
76 | }
77 |
78 | variable "next_lambda_ephemeral_storage_size" {
79 | description = "Amount of ephemeral storage (/tmp) in MB the next lambda can use at runtime. Valid value between 512 MB to 10240 MB"
80 | type = number
81 | default = 512
82 | }
83 |
84 | variable "api_gateway_log_format" {
85 | description = "Default stage's single line format of the access logs of data, as specified by selected $context variables"
86 | type = string
87 | default = "sourceIp: $context.identity.sourceIp, $context.domainName $context.requestTime \"$context.httpMethod $context.path $context.routeKey $context.protocol\" path: $context.customDomain.basePathMatched resp_status: $context.status integrationLatency: $context.integrationLatency responseLatency: $context.responseLatency requestId: $context.requestId Error: $context.integrationErrorMessage rawRequestPayloadSize: $input.body.size() rawRequestPayload: $input.body" # https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-logging-variables.html
88 | }
89 |
90 | variable "enable_image_optimization" {
91 | description = "Boolean to disable image optimization feature"
92 | type = bool
93 | default = true
94 | }
95 |
96 | variable "image_optimization_runtime" {
97 | description = "The runtime for the image optimization Lambdas (nodejs16.x or nodejs20.x)"
98 | type = string
99 | default = "nodejs20.x"
100 | }
101 |
102 | variable "image_optimization_lambda_memory_size" {
103 | description = "The memory size for the image optimization Lambda (Set memory to between 128 MB and 10240 MB)"
104 | type = number
105 | default = 2048
106 | }
107 |
108 | variable "image_optimization_logs_retention" {
109 | description = "The number of days that cloudwatch logs of image optimization lambdas should be retained (Possible values are: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, and 3653)"
110 | type = number
111 | default = 30
112 | }
113 |
114 | variable "image_optimization_ephemeral_storage_size" {
115 | description = "Amount of ephemeral storage (/tmp) in MB the image optimization lambdas can use at runtime. Valid value between 512 MB to 10240 MB"
116 | type = number
117 | default = 512
118 | }
119 |
120 | variable "cloudfront_cached_paths" {
121 | description = "An object containing a list of paths to cache and min, default and max TTL values"
122 | type = object({
123 | paths = list(string)
124 | min_ttl = number
125 | default_ttl = number
126 | max_ttl = number
127 | })
128 | default = {
129 | paths = []
130 | min_ttl = 0
131 | default_ttl = 0
132 | max_ttl = 0
133 | }
134 | }
135 |
136 | variable "custom_cache_policy_id" {
137 | description = "The ID of CloudFront cache policy"
138 | type = string
139 | default = null
140 | }
141 |
142 | variable "cloudfront_cache_default_ttl" {
143 | description = "Default TTL in seconds for ordered cache behaviors"
144 | type = number
145 | default = 86400
146 | }
147 |
148 | variable "cloudfront_cache_max_ttl" {
149 | description = "Maximum TTL in seconds for ordered cache behaviors"
150 | type = number
151 | default = 31536000
152 | }
153 |
154 | variable "cloudfront_cache_min_ttl" {
155 | description = "Minimum TTL in seconds for ordered cache behaviors"
156 | type = number
157 | default = 1
158 | }
159 |
160 | variable "cloudfront_function_associations" {
161 | description = "List of CloudFront functions, to associate them with the defaulf distribution"
162 | type = list(object({
163 | event_type = string
164 | function_arn = string
165 | }))
166 | default = []
167 | }
168 |
169 | variable "create_cloudfront_invalidation" {
170 | description = "Boolean to disable the trigger for cloudfront invalidation after every deployment"
171 | type = bool
172 | default = true
173 | }
174 |
175 | variable "wait_for_distribution_deployment" {
176 | description = "If enabled, the resource will wait for the distribution status to change from `InProgress` to `Deployed`"
177 | type = bool
178 | default = true
179 | }
180 |
181 | variable "use_default_server_side_props_handler" {
182 | description = "Boolean to enabled usage of the default server side props handler, instead of the our custom one"
183 | type = bool
184 | default = false
185 | }
186 |
187 | variable "show_debug_logs" {
188 | description = "Boolean to enabled debug logs"
189 | type = bool
190 | default = false
191 | }
192 |
193 | variable "pre_resize_images" {
194 | description = "Boolean to enabled the resizing of public images, after each deployment. Enabling this might increase the AWS bill"
195 | type = bool
196 | default = false
197 | }
198 |
199 | variable "delete_resized_versions" {
200 | description = "Boolean to disable the trigger for deleting old resized versions of public images"
201 | type = bool
202 | default = true
203 | }
204 |
--------------------------------------------------------------------------------
/versions.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = ">= 1.6.3"
3 |
4 | required_providers {
5 | aws = {
6 | source = "hashicorp/aws"
7 | version = "~> 5.0"
8 | configuration_aliases = [aws.global_region]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/visuals/cache.drawio:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/visuals/cache.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emyriounis/terraform-aws-nextjs-serverless/f2a298c8509861e9976752cc9a52401a808075fc/visuals/cache.webp
--------------------------------------------------------------------------------
/visuals/distribution.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emyriounis/terraform-aws-nextjs-serverless/f2a298c8509861e9976752cc9a52401a808075fc/visuals/distribution.webp
--------------------------------------------------------------------------------
/visuals/module.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emyriounis/terraform-aws-nextjs-serverless/f2a298c8509861e9976752cc9a52401a808075fc/visuals/module.webp
--------------------------------------------------------------------------------