Skip to content

Commit fa44d2e

Browse files
authored
Adds ability to use images from public folder (#6)
1 parent 6a6340d commit fa44d2e

36 files changed

+321
-54
lines changed

.vscode/launch.json

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"--inspect-brk",
2323
"${workspaceRoot}/node_modules/.bin/jest",
2424
"--runInBand",
25+
"--testTimeout=60000",
2526
"${file}"
2627
],
2728
"console": "integratedTerminal",

README.md

+72
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,78 @@ This module is currently under active development.
1818

1919
## Usage
2020

21+
### 1. Deploy the module to AWS
22+
23+
Initialize the module by creating a `main.tf` file with the following content (you can place the file in the same directory where your Next.js project is located):
24+
25+
```tf
26+
# main.tf
27+
28+
terraform {
29+
required_providers {
30+
aws = {
31+
source = "hashicorp/aws"
32+
version = "~> 3.0"
33+
}
34+
}
35+
}
36+
37+
# Main AWS region where the resources should be created in
38+
# Should be close to where your Next.js deployment is located
39+
provider "aws" {
40+
region = "us-east-1"
41+
}
42+
43+
module "next_image_optimizer" {
44+
source = "dealmore/next-js-image-optimization/aws"
45+
46+
domains = ["example.com", "sub.example.com"]
47+
}
48+
49+
output "domain" {
50+
value = module.next_image_optimizer.cloudfront_domain_name
51+
}
52+
```
53+
54+
Then run Terraform to deploy the image optimiziation module to your AWS account:
55+
56+
```sh
57+
terraform init # Only needed on the first time running Terraform
58+
59+
terraform plan # (Optional) See what resources Terraform will create
60+
terraform apply # Deploy the image optimizer module to your AWS account
61+
```
62+
63+
After Terraform has successfully created all resources in your AWS account, you should see the following output on the terminal:
64+
65+
```sh
66+
> Apply complete!
67+
>
68+
> Outputs:
69+
>
70+
> domain = "<distribution-id>.cloudfront.net"
71+
```
72+
73+
You should save the `<distribution-id>.cloudfront.net` output somewhere since you need it in the next step.
74+
75+
### 2. Adjust Next.js config
76+
77+
In your Next.js project, open or create the `next.config.js` file and add the following lines (Remember to replace `<distribution-id>` with the output from the previous step):
78+
79+
```diff
80+
// next.config.js
81+
82+
module.exports = {
83+
+ images: {
84+
+ path: 'https://<distribution-id>.cloudfront.net/_next/image'
85+
+ },
86+
}
87+
```
88+
89+
## Examples
90+
91+
- [Next.js + Vercel](https://github.com/dealmore/terraform-aws-next-js-image-optimization/tree/main/examples/with-next-js) - Use the image optimizer together with a Next.js app deployed on Vercel.
92+
2193
## Versioning
2294

2395
We internally rely on the Next.js image optimizer, so every version we publish follows the versioning schema of the [Next.js package](https://www.npmjs.com/package/next).

examples/with-next-js/README.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,18 @@ After Terraform has successfully created all resources in your AWS account, you
5656
> domain = "<distribution-id>.cloudfront.net"
5757
```
5858

59-
You should save the `<distribution-id>.cloudfront.net` output somewhere since you will need it in the next step.
59+
You should save the `<distribution-id>.cloudfront.net` output somewhere since you need it in the next step.
6060

6161
## 2. Adjust Next.js config
6262

6363
In your Next.js project, open or create the `next.config.js` file and add the following lines (Remember to replace `<distribution-id>` with the output from the previous step):
6464

6565
```diff
66+
// next.config.js
67+
6668
module.exports = {
6769
+ images: {
68-
+ path: 'https://<distribution-id>.cloudfront.net/_next/image/'
70+
+ path: 'https://<distribution-id>.cloudfront.net/_next/image'
6971
+ },
7072
}
7173
```
@@ -85,4 +87,4 @@ vercel
8587
---
8688

8789
Now you are all set!
88-
You can now visit your Next.js app in browser and the assets should be delivered by the CloudFront distribution.
90+
You can now visit your Next.js app in browser and the images should be delivered by the CloudFront distribution.

examples/with-next-js/next.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module.exports = {
22
images: {
33
domains: ['assets.vercel.com'],
4-
path: 'https://<distribution-id>.cloudfront.net/_next/image/',
4+
path: 'https://<distribution-id>.cloudfront.net/_next/image',
55
},
66
};

lib/handler.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import { Writable } from 'stream';
1111

1212
import { imageOptimizer } from './image-optimizer';
13+
import { normalizeHeaders } from './normalized-headers';
1314

1415
let domains = [];
1516
try {
@@ -28,7 +29,7 @@ export async function handler(
2829
event: APIGatewayProxyEventV2
2930
): Promise<APIGatewayProxyStructuredResultV2> {
3031
const reqMock: any = {
31-
headers: event.headers,
32+
headers: normalizeHeaders(event.headers),
3233
method: event.requestContext.http.method,
3334
url: `/?${event.rawQueryString}`,
3435
};

lib/image-optimizer.ts

+35-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { IncomingMessage, ServerResponse } from 'http';
22
import { imageOptimizer as nextImageOptimizer } from 'next/dist/next-server/server/image-optimizer';
33
import { ImageConfig } from 'next/dist/next-server/server/image-config';
44
import nodeFetch, { RequestInfo, RequestInit } from 'node-fetch';
5-
65
import { UrlWithParsedQuery } from 'url';
76
import Server from 'next/dist/next-server/server/next-server';
87

@@ -34,8 +33,41 @@ async function imageOptimizer(
3433
images: imageConfig,
3534
},
3635
distDir: '/tmp',
37-
getRequestHandler: () => () => {
38-
// TODO
36+
getRequestHandler: () => async (
37+
{ headers }: IncomingMessage,
38+
res: ServerResponse,
39+
url: UrlWithParsedQuery
40+
) => {
41+
// TODO: When deployed together with Terraform Next.js we can use
42+
// AWS SDK here to fetch the image directly from S3 instead of
43+
// using node - fetch
44+
45+
if (headers.referer) {
46+
let upstreamBuffer: Buffer;
47+
let upstreamType: string | null;
48+
49+
const { referer } = headers;
50+
const trimmedReferer = referer.endsWith('/')
51+
? referer.substring(0, referer.length - 1)
52+
: referer;
53+
const origin = `${trimmedReferer}${url.href}`;
54+
const upstreamRes = await nodeFetch(origin);
55+
56+
if (!upstreamRes.ok) {
57+
throw new Error(`Could not fetch image from ${origin}`);
58+
}
59+
60+
res.statusCode = upstreamRes.status;
61+
upstreamBuffer = Buffer.from(await upstreamRes.arrayBuffer());
62+
upstreamType = upstreamRes.headers.get('Content-Type');
63+
originCacheControl = upstreamRes.headers.get('Cache-Control');
64+
65+
if (upstreamType) {
66+
res.setHeader('Content-Type', upstreamType);
67+
}
68+
69+
res.end(upstreamBuffer);
70+
}
3971
},
4072
} as unknown) as Server;
4173

lib/normalized-headers.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { IncomingHttpHeaders } from 'http';
2+
3+
/**
4+
* Normalizes the headers from API Gateway 2.0 format
5+
*/
6+
export function normalizeHeaders(headers: Record<string, string>) {
7+
const _headers: IncomingHttpHeaders = {};
8+
9+
for (const [key, value] of Object.entries(headers)) {
10+
_headers[key.toLocaleLowerCase()] = value;
11+
}
12+
13+
return _headers;
14+
}

main.tf

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ module "api_gateway" {
6464
create_api_domain_name = false
6565

6666
integrations = {
67-
"GET /_next/image" = {
67+
"GET /_next/{proxy+}" = {
6868
lambda_arn = module.image_optimizer.this_lambda_function_arn
6969
payload_format_version = "2.0"
7070
timeout_milliseconds = var.lambda_timeout * 1000

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"test:e2e": "jest --testTimeout=60000 e2e.*"
1414
},
1515
"devDependencies": {
16-
"@dealmore/sammy": "^1.0.0",
16+
"@dealmore/sammy": "^1.1.0",
1717
"@types/jest": "^26.0.20",
1818
"@types/mime": "^2.0.3",
1919
"@types/node": "^12.0.0",
Binary file not shown.
Loading
Loading
Loading
Loading
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading
Loading
Loading
Loading
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)