Skip to content

Commit c4b0a78

Browse files
authored
add: s3-lite override for imageLoader (#750)
* add: s3 lite for imageLoader * lint * fix * review * fix changeset * fallback region * changeset * review
1 parent 9f3720f commit c4b0a78

File tree

3 files changed

+106
-1
lines changed

3 files changed

+106
-1
lines changed

.changeset/lucky-horses-worry.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
"@opennextjs/aws": patch
3+
---
4+
5+
add: s3 lite override for loading images in the image optimization server
6+
7+
`s3-lite` override for image loading. Uses `aws4fetch` to get the objects from your s3 bucket. This will make the image optimization server work without the aws s3 sdk. This override introduces a new environment variable called `BUCKET_REGION`. It will fallback to `AWS_REGION` ?? `AWS_DEFAULT_REGION` if undefined. This will require no additional change in IAC for most users.
8+
9+
```ts
10+
import type { OpenNextConfig } from '@opennextjs/aws/types/open-next';
11+
const config = {
12+
default: {},
13+
imageOptimization: {
14+
loader: 's3-lite',
15+
},
16+
} satisfies OpenNextConfig;
17+
18+
export default config;
19+
```
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { Readable } from "node:stream";
2+
import type { ReadableStream } from "node:stream/web";
3+
import { AwsClient } from "aws4fetch";
4+
5+
import type { ImageLoader } from "types/overrides";
6+
import { FatalError } from "utils/error";
7+
8+
let awsClient: AwsClient | null = null;
9+
10+
const { BUCKET_NAME, BUCKET_KEY_PREFIX } = process.env;
11+
// https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime
12+
// If AWS_REGION is defined it will override AWS_DEFAULT_REGION
13+
const BUCKET_REGION =
14+
process.env.BUCKET_REGION ??
15+
process.env.AWS_REGION ??
16+
process.env.AWS_DEFAULT_REGION;
17+
18+
const getAwsClient = () => {
19+
if (awsClient) {
20+
return awsClient;
21+
}
22+
awsClient = new AwsClient({
23+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
24+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
25+
sessionToken: process.env.AWS_SESSION_TOKEN,
26+
region: BUCKET_REGION,
27+
});
28+
return awsClient;
29+
};
30+
31+
function ensureEnvExists() {
32+
if (!(BUCKET_NAME || BUCKET_KEY_PREFIX || BUCKET_REGION)) {
33+
throw new FatalError("Bucket name, region and key prefix must be defined!");
34+
}
35+
}
36+
37+
const awsFetch = async (key: string, options: RequestInit) => {
38+
const client = getAwsClient();
39+
const url = `https://${BUCKET_NAME}.s3.${BUCKET_REGION}.amazonaws.com/${key}`;
40+
return client.fetch(url, options);
41+
};
42+
43+
const s3Loader: ImageLoader = {
44+
name: "s3-lite",
45+
load: async (key: string) => {
46+
ensureEnvExists();
47+
const keyPrefix = BUCKET_KEY_PREFIX?.replace(/^\/|\/$/g, "");
48+
const response = await awsFetch(
49+
keyPrefix
50+
? `${keyPrefix}/${key.replace(/^\//, "")}`
51+
: key.replace(/^\//, ""),
52+
{
53+
method: "GET",
54+
},
55+
);
56+
57+
if (response.status === 404) {
58+
throw new FatalError("The specified key does not exist.");
59+
}
60+
if (response.status !== 200) {
61+
throw new FatalError(
62+
`Failed to get image. Status code: ${response.status}`,
63+
);
64+
}
65+
66+
if (!response.body) {
67+
throw new FatalError("No body in aws4fetch s3 response");
68+
}
69+
70+
// We need to cast it else there will be a TypeError: o.pipe is not a function
71+
const body = Readable.fromWeb(response.body as ReadableStream<Uint8Array>);
72+
73+
return {
74+
body: body,
75+
contentType: response.headers.get("content-type") ?? undefined,
76+
cacheControl: response.headers.get("cache-control") ?? undefined,
77+
};
78+
},
79+
};
80+
81+
export default s3Loader;

packages/open-next/src/types/open-next.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,12 @@ export type IncludedTagCache =
151151
| "fs-dev"
152152
| "dummy";
153153

154-
export type IncludedImageLoader = "s3" | "host" | "fs-dev" | "dummy";
154+
export type IncludedImageLoader =
155+
| "s3"
156+
| "s3-lite"
157+
| "host"
158+
| "fs-dev"
159+
| "dummy";
155160

156161
export type IncludedOriginResolver = "pattern-env" | "dummy";
157162

0 commit comments

Comments
 (0)