Skip to content

Commit 3a1c20c

Browse files
chore(open-next): apply various improvements (#8304)
* bump wrangler and open-next adapter * for the open-next incremental cache use R2 with regional cache instead of kv * add open-next DO queue * enable open-next cache interception * add open-next custom image loader * add inline comment for regional cache * define `OPEN_NEXT_CLOUDFLARE` variable in `next.consstants.mjs` * avoid ternaries in `next.config.mjs` * update cloudflare-build-and-deployment * move image-loader file inside cloudflare directory * add missing parenthesis * add env variables for R2 cache batch uploads * remove extra backtick * allow the open-next version of the site to be built using turbo and remove the `build:default` script * Bump `@opennextjs/cloudflare` to `1.13.1` and remove no-longer needed R2 env variables * update image-loader code as suggested * use arrow functions * move getImagesConfig to its own file * add mapping from string urls to URL objects * move cloudflare constant in `next.constants.cloudflare.mjs` * add brief documentation regarding the cloudflare image loader * Update apps/site/next.config.mjs Co-authored-by: Claudio Wunder <[email protected]> Signed-off-by: Dario Piotrowicz <[email protected]> * fix linting issues --------- Signed-off-by: Dario Piotrowicz <[email protected]> Co-authored-by: Claudio Wunder <[email protected]>
1 parent 563f9eb commit 3a1c20c

File tree

10 files changed

+276
-196
lines changed

10 files changed

+276
-196
lines changed

.github/workflows/tmp-cloudflare-open-next-deploy.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,4 @@ jobs:
6464
env:
6565
CF_WORKERS_SCRIPTS_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
6666
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
67+
CLOUDFLARE_ACCOUNT_ID: fb4a2d0f103c6ff38854ac69eb709272
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { ImageLoaderProps } from 'next/image';
2+
3+
const normalizeSrc = (src: string) => {
4+
return src.startsWith('/') ? src.slice(1) : src;
5+
};
6+
7+
export default function cloudflareLoader({
8+
src,
9+
width,
10+
quality,
11+
}: ImageLoaderProps) {
12+
if (process.env.NODE_ENV === 'development') {
13+
// Serve the original image when using `next dev`
14+
return src;
15+
}
16+
17+
// Sets the requested width by next/image
18+
const params = new Map<string, string>();
19+
20+
if (width > 0) {
21+
params.set('width', `${width}`);
22+
}
23+
24+
if (quality && quality > 0) {
25+
params.set('quality', `${quality}`);
26+
}
27+
28+
const pathParams = [...params]
29+
.map(([key, value]) => `${key}=${value}`)
30+
.join(',');
31+
32+
return `/cdn-cgi/image/${pathParams}/${normalizeSrc(src)}`;
33+
}

apps/site/next.config.mjs

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
'use strict';
22
import createNextIntlPlugin from 'next-intl/plugin';
33

4+
import { OPEN_NEXT_CLOUDFLARE } from './next.constants.cloudflare.mjs';
45
import { BASE_PATH, ENABLE_STATIC_EXPORT } from './next.constants.mjs';
6+
import { getImagesConfig } from './next.image.config.mjs';
57
import { redirects, rewrites } from './next.rewrites.mjs';
68

9+
const getDeploymentId = async () => {
10+
if (OPEN_NEXT_CLOUDFLARE) {
11+
// If we're building for the Cloudflare deployment we want to set
12+
// an appropriate deploymentId (needed for skew protection)
13+
const openNextAdapter = await import('@opennextjs/cloudflare');
14+
15+
return openNextAdapter.getDeploymentId();
16+
}
17+
18+
return undefined;
19+
};
20+
721
/** @type {import('next').NextConfig} */
822
const nextConfig = {
923
allowedDevOrigins: ['10.1.1.232'],
@@ -13,19 +27,7 @@ const nextConfig = {
1327
// is being built on a subdirectory (e.g. /nodejs-website)
1428
basePath: BASE_PATH,
1529
// Vercel/Next.js Image Optimization Settings
16-
images: {
17-
// We disable image optimisation during static export builds
18-
unoptimized: ENABLE_STATIC_EXPORT,
19-
// We add it to the remote pattern for the static images we use from multiple sources
20-
// to be marked as safe sources (these come from Markdown files)
21-
remotePatterns: [
22-
new URL('https://avatars.githubusercontent.com/**'),
23-
new URL('https://bestpractices.coreinfrastructure.org/**'),
24-
new URL('https://raw.githubusercontent.com/nodejs/**'),
25-
new URL('https://user-images.githubusercontent.com/**'),
26-
new URL('https://website-assets.oramasearch.com/**'),
27-
],
28-
},
30+
images: getImagesConfig(),
2931
serverExternalPackages: ['twoslash'],
3032
outputFileTracingIncludes: {
3133
// Twoslash needs TypeScript declarations to function, and, by default, Next.js
@@ -81,16 +83,7 @@ const nextConfig = {
8183
'shiki',
8284
],
8385
},
84-
// If we're building for the Cloudflare deployment we want to set
85-
// an appropriate deploymentId (needed for skew protection)
86-
// TODO: The `OPEN_NEXT_CLOUDFLARE` environment variable is being
87-
// defined in the worker building script, ideally the open-next
88-
// adapter should set it itself when it invokes the Next.js build
89-
// process, onces it does that remove the manual `OPEN_NEXT_CLOUDFLARE`
90-
// definition in the package.json script.
91-
deploymentId: process.env.OPEN_NEXT_CLOUDFLARE
92-
? (await import('@opennextjs/cloudflare')).getDeploymentId()
93-
: undefined,
86+
deploymentId: await getDeploymentId(),
9487
};
9588

9689
const withNextIntl = createNextIntlPlugin('./i18n.tsx');
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict';
2+
3+
/**
4+
* Whether the build process is targeting the Cloudflare open-next build or not.
5+
*
6+
* TODO: The `OPEN_NEXT_CLOUDFLARE` environment variable is being
7+
* defined in the worker building script, ideally the open-next
8+
* adapter should set it itself when it invokes the Next.js build
9+
* process, once it does that remove the manual `OPEN_NEXT_CLOUDFLARE`
10+
* definition in the package.json script.
11+
*/
12+
export const OPEN_NEXT_CLOUDFLARE = process.env.OPEN_NEXT_CLOUDFLARE;

apps/site/next.image.config.mjs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { OPEN_NEXT_CLOUDFLARE } from './next.constants.cloudflare.mjs';
2+
import { ENABLE_STATIC_EXPORT } from './next.constants.mjs';
3+
4+
const remotePatterns = [
5+
'https://avatars.githubusercontent.com/**',
6+
'https://bestpractices.coreinfrastructure.org/**',
7+
'https://raw.githubusercontent.com/nodejs/**',
8+
'https://user-images.githubusercontent.com/**',
9+
'https://website-assets.oramasearch.com/**',
10+
];
11+
12+
export const getImagesConfig = () => {
13+
if (OPEN_NEXT_CLOUDFLARE) {
14+
// If we're building for the Cloudflare deployment we want to use the custom cloudflare image loader
15+
//
16+
// Important: The custom loader ignores `remotePatterns` as those are configured as allowed source origins
17+
// (https://developers.cloudflare.com/images/transform-images/sources/)
18+
// in the Cloudflare dashboard itself instead (to the exact same values present in `remotePatterns` above).
19+
//
20+
return {
21+
loader: 'custom',
22+
loaderFile: './cloudflare/image-loader.ts',
23+
};
24+
}
25+
26+
return {
27+
// We disable image optimisation during static export builds
28+
unoptimized: ENABLE_STATIC_EXPORT,
29+
// We add it to the remote pattern for the static images we use from multiple sources
30+
// to be marked as safe sources (these come from Markdown files)
31+
remotePatterns: remotePatterns.map(url => new URL(url)),
32+
};
33+
};

apps/site/open-next.config.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
import { defineCloudflareConfig } from '@opennextjs/cloudflare';
2-
import incrementalCache from '@opennextjs/cloudflare/overrides/incremental-cache/kv-incremental-cache';
2+
import r2IncrementalCache from '@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache';
3+
import { withRegionalCache } from '@opennextjs/cloudflare/overrides/incremental-cache/regional-cache';
4+
import doQueue from '@opennextjs/cloudflare/overrides/queue/do-queue';
35

46
import type { OpenNextConfig } from '@opennextjs/cloudflare';
57

6-
const cloudflareConfig = defineCloudflareConfig({ incrementalCache });
8+
const cloudflareConfig = defineCloudflareConfig({
9+
/**
10+
* The regional cache implementation with R2 (instead of a KV one) is is chosen here
11+
* for both R2's strong consistency alongside the regional cache performance gains.
12+
* @see https://opennext.js.org/cloudflare/caching
13+
*/
14+
incrementalCache: withRegionalCache(r2IncrementalCache, {
15+
mode: 'long-lived',
16+
}),
17+
queue: doQueue,
18+
enableCacheInterception: true,
19+
});
720

821
const openNextConfig: OpenNextConfig = {
922
...cloudflareConfig,
10-
buildCommand: 'pnpm build:default',
1123
cloudflare: {
1224
skewProtection: {
1325
enabled: true,

apps/site/package.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33
"type": "module",
44
"scripts": {
55
"prebuild": "node --run build:blog-data",
6-
"build": "node --run build:default -- --turbo",
6+
"build": "cross-env NODE_NO_WARNINGS=1 next build --turbo",
77
"build:blog-data": "cross-env NODE_NO_WARNINGS=1 node ./scripts/blog-data/index.mjs",
88
"build:blog-data:watch": "node --watch --watch-path=pages/en/blog ./scripts/blog-data/index.mjs",
9-
"build:default": "cross-env NODE_NO_WARNINGS=1 next build",
109
"cloudflare:build:worker": "OPEN_NEXT_CLOUDFLARE=true opennextjs-cloudflare build",
1110
"cloudflare:deploy": "opennextjs-cloudflare deploy",
1211
"cloudflare:preview": "wrangler dev",
1312
"predeploy": "node --run build:blog-data",
14-
"deploy": "cross-env NEXT_PUBLIC_STATIC_EXPORT=true node --run build:default -- --turbo",
13+
"deploy": "cross-env NEXT_PUBLIC_STATIC_EXPORT=true node --run build",
1514
"predev": "node --run build:blog-data",
1615
"dev": "cross-env NODE_NO_WARNINGS=1 next dev --turbo",
1716
"lint": "node --run lint:js && node --run lint:css && node --run lint:md",
@@ -83,7 +82,7 @@
8382
"@flarelabs-net/wrangler-build-time-fs-assets-polyfilling": "^0.0.1",
8483
"@next/eslint-plugin-next": "15.5.4",
8584
"@node-core/remark-lint": "workspace:*",
86-
"@opennextjs/cloudflare": "^1.6.4",
85+
"@opennextjs/cloudflare": "^1.13.1",
8786
"@playwright/test": "^1.56.1",
8887
"@testing-library/user-event": "~14.6.1",
8988
"@types/mdast": "^4.0.4",
@@ -108,7 +107,7 @@
108107
"typescript": "catalog:",
109108
"typescript-eslint": "~8.45.0",
110109
"user-agent-data-types": "0.4.2",
111-
"wrangler": "^4.33.1"
110+
"wrangler": "^4.45.3"
112111
},
113112
"imports": {
114113
"#site/*": [

apps/site/wrangler.jsonc

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,24 @@
3333
"fs": "./.wrangler/fs-assets-polyfilling/polyfills/node/fs.ts",
3434
"fs/promises": "./.wrangler/fs-assets-polyfilling/polyfills/node/fs/promises.ts",
3535
},
36-
"kv_namespaces": [
36+
"r2_buckets": [
3737
{
38-
"binding": "NEXT_INC_CACHE_KV",
39-
"id": "69b7422d56dd4244bc0127b69ecdc36f",
38+
"binding": "NEXT_INC_CACHE_R2_BUCKET",
39+
"bucket_name": "next-cache-r2-for-open-next-website",
40+
},
41+
],
42+
"durable_objects": {
43+
"bindings": [
44+
{
45+
"name": "NEXT_CACHE_DO_QUEUE",
46+
"class_name": "DOQueueHandler",
47+
},
48+
],
49+
},
50+
"migrations": [
51+
{
52+
"tag": "v1",
53+
"new_sqlite_classes": ["DOQueueHandler"],
4054
},
4155
],
4256
}

docs/cloudflare-build-and-deployment.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ Key configurations include:
1919
- This is currently set to `fb4a2d0f103c6ff38854ac69eb709272`, which is the ID of a Cloudflare account controlled by Node.js, and used for testing.
2020
- `build`: Defines the build command to generate the Node.js filesystem polyfills required for the application to run on Cloudflare Workers. This uses the [`@flarelabs/wrangler-build-time-fs-assets-polyfilling`](https://github.com/flarelabs-net/wrangler-build-time-fs-assets-polyfilling) package.
2121
- `alias`: Maps aliases for the Node.js filesystem polyfills generated during the build process.
22-
- `kv_namespaces`: Contains a single KV binding definition for `NEXT_INC_CACHE_KV`. This is used to implement the Next.js incremental cache.
23-
- This is currently set up to a KV in the aforementioned Cloudflare testing account.
22+
- `r2_buckets`: Contains a single R2 binding definition for `NEXT_INC_CACHE_R2_BUCKET`. This is used to implement the Next.js incremental cache.
23+
- This is currently set up to a R2 bucket in the aforementioned Cloudflare testing account.
24+
- `durable_objects`: Contains a single DurableObject binding definition for `NEXT_CACHE_DO_QUEUE`. This is used to implement the Open-next cache queue.
2425

2526
### OpenNext Configuration
2627

@@ -45,6 +46,14 @@ The OpenNext skew protection requires the following environment variables to be
4546

4647
Additionally, when deploying, an extra `CF_WORKERS_SCRIPTS_API_TOKEN` environment variable needs to be set to an API token that has the `Workers Scripts:Read` permission available on the Worker's account.
4748

49+
### Image loader
50+
51+
When deployed on the Cloudflare network a custom image loader is required. We set such loader in the Next.js config file when the `OPEN_NEXT_CLOUDFLARE` environment variable is set (which indicates that we're building the application for the Cloudflare deployment).
52+
53+
The custom loader can be found at [`site/cloudflare/image-loader.ts`](../apps/site/cloudflare/image-loader.ts).
54+
55+
For more details on this see: https://developers.cloudflare.com/images/transform-images/integrate-with-frameworks/#global-loader
56+
4857
## Scripts
4958

5059
Preview and deployment of the website targeting the Cloudflare network is implemented via the following two commands:

0 commit comments

Comments
 (0)