Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6a8f860
bump wrangler and open-next adapter
dario-piotrowicz Oct 31, 2025
89b6b2f
for the open-next incremental cache use R2 with regional cache instea…
dario-piotrowicz Oct 31, 2025
670e686
add open-next DO queue
dario-piotrowicz Oct 31, 2025
1ef4730
enable open-next cache interception
dario-piotrowicz Oct 31, 2025
6cb2454
add open-next custom image loader
dario-piotrowicz Oct 31, 2025
7436b9f
add inline comment for regional cache
dario-piotrowicz Nov 4, 2025
d5fffab
define `OPEN_NEXT_CLOUDFLARE` variable in `next.consstants.mjs`
dario-piotrowicz Nov 4, 2025
12ede14
avoid ternaries in `next.config.mjs`
dario-piotrowicz Nov 4, 2025
f137edb
update cloudflare-build-and-deployment
dario-piotrowicz Nov 4, 2025
ee80f76
move image-loader file inside cloudflare directory
dario-piotrowicz Nov 4, 2025
b114d77
add missing parenthesis
dario-piotrowicz Nov 4, 2025
b889f9d
add env variables for R2 cache batch uploads
dario-piotrowicz Nov 4, 2025
10a5ae6
remove extra backtick
dario-piotrowicz Nov 4, 2025
9626161
allow the open-next version of the site to be built using turbo and r…
dario-piotrowicz Nov 11, 2025
9d63850
Bump `@opennextjs/cloudflare` to `1.13.1` and remove no-longer needed…
dario-piotrowicz Nov 21, 2025
5134a76
update image-loader code as suggested
dario-piotrowicz Nov 25, 2025
2a984f8
use arrow functions
dario-piotrowicz Nov 25, 2025
d4d116a
move getImagesConfig to its own file
dario-piotrowicz Nov 25, 2025
22df719
add mapping from string urls to URL objects
dario-piotrowicz Nov 25, 2025
5bbe6f7
move cloudflare constant in `next.constants.cloudflare.mjs`
dario-piotrowicz Nov 25, 2025
dbf3b69
add brief documentation regarding the cloudflare image loader
dario-piotrowicz Nov 25, 2025
75fad0b
Update apps/site/next.config.mjs
dario-piotrowicz Nov 25, 2025
8b26595
fix linting issues
dario-piotrowicz Nov 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/tmp-cloudflare-open-next-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,4 @@ jobs:
env:
CF_WORKERS_SCRIPTS_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: fb4a2d0f103c6ff38854ac69eb709272
33 changes: 33 additions & 0 deletions apps/site/cloudflare/image-loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { ImageLoaderProps } from 'next/image';

const normalizeSrc = (src: string) => {
return src.startsWith('/') ? src.slice(1) : src;
};

export default function cloudflareLoader({
src,
width,
quality,
}: ImageLoaderProps) {
if (process.env.NODE_ENV === 'development') {
// Serve the original image when using `next dev`
return src;
}

// Sets the requested width by next/image
const params = new Map<string, string>();

if (width > 0) {
params.set('width', `${width}`);
}

if (quality && quality > 0) {
params.set('quality', `${quality}`);
}

const pathParams = [...params]
.map(([key, value]) => `${key}=${value}`)
.join(',');

return `/cdn-cgi/image/${pathParams}/${normalizeSrc(src)}`;
}
39 changes: 16 additions & 23 deletions apps/site/next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
'use strict';
import createNextIntlPlugin from 'next-intl/plugin';

import { OPEN_NEXT_CLOUDFLARE } from './next.constants.cloudflare.mjs';
import { BASE_PATH, ENABLE_STATIC_EXPORT } from './next.constants.mjs';
import { getImagesConfig } from './next.image.config.mjs';
import { redirects, rewrites } from './next.rewrites.mjs';

const getDeploymentId = async () => {
if (OPEN_NEXT_CLOUDFLARE) {
// If we're building for the Cloudflare deployment we want to set
// an appropriate deploymentId (needed for skew protection)
const openNextAdapter = await import('@opennextjs/cloudflare');

return openNextAdapter.getDeploymentId();
}

return undefined;
};

/** @type {import('next').NextConfig} */
const nextConfig = {
allowedDevOrigins: ['10.1.1.232'],
Expand All @@ -13,19 +27,7 @@ const nextConfig = {
// is being built on a subdirectory (e.g. /nodejs-website)
basePath: BASE_PATH,
// Vercel/Next.js Image Optimization Settings
images: {
// We disable image optimisation during static export builds
unoptimized: ENABLE_STATIC_EXPORT,
// We add it to the remote pattern for the static images we use from multiple sources
// to be marked as safe sources (these come from Markdown files)
remotePatterns: [
new URL('https://avatars.githubusercontent.com/**'),
new URL('https://bestpractices.coreinfrastructure.org/**'),
new URL('https://raw.githubusercontent.com/nodejs/**'),
new URL('https://user-images.githubusercontent.com/**'),
new URL('https://website-assets.oramasearch.com/**'),
],
},
images: getImagesConfig(),
serverExternalPackages: ['twoslash'],
outputFileTracingIncludes: {
// Twoslash needs TypeScript declarations to function, and, by default, Next.js
Expand Down Expand Up @@ -81,16 +83,7 @@ const nextConfig = {
'shiki',
],
},
// If we're building for the Cloudflare deployment we want to set
// an appropriate deploymentId (needed for skew protection)
// TODO: The `OPEN_NEXT_CLOUDFLARE` environment variable is being
// defined in the worker building script, ideally the open-next
// adapter should set it itself when it invokes the Next.js build
// process, onces it does that remove the manual `OPEN_NEXT_CLOUDFLARE`
// definition in the package.json script.
deploymentId: process.env.OPEN_NEXT_CLOUDFLARE
? (await import('@opennextjs/cloudflare')).getDeploymentId()
: undefined,
deploymentId: await getDeploymentId(),
};

const withNextIntl = createNextIntlPlugin('./i18n.tsx');
Expand Down
12 changes: 12 additions & 0 deletions apps/site/next.constants.cloudflare.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use strict';

/**
* Whether the build process is targeting the Cloudflare open-next build or not.
*
* TODO: The `OPEN_NEXT_CLOUDFLARE` environment variable is being
* defined in the worker building script, ideally the open-next
* adapter should set it itself when it invokes the Next.js build
* process, once it does that remove the manual `OPEN_NEXT_CLOUDFLARE`
* definition in the package.json script.
*/
export const OPEN_NEXT_CLOUDFLARE = process.env.OPEN_NEXT_CLOUDFLARE;
33 changes: 33 additions & 0 deletions apps/site/next.image.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { OPEN_NEXT_CLOUDFLARE } from './next.constants.cloudflare.mjs';
import { ENABLE_STATIC_EXPORT } from './next.constants.mjs';

const remotePatterns = [
'https://avatars.githubusercontent.com/**',
'https://bestpractices.coreinfrastructure.org/**',
'https://raw.githubusercontent.com/nodejs/**',
'https://user-images.githubusercontent.com/**',
'https://website-assets.oramasearch.com/**',
];

export const getImagesConfig = () => {
if (OPEN_NEXT_CLOUDFLARE) {
// If we're building for the Cloudflare deployment we want to use the custom cloudflare image loader
//
// Important: The custom loader ignores `remotePatterns` as those are configured as allowed source origins
// (https://developers.cloudflare.com/images/transform-images/sources/)
// in the Cloudflare dashboard itself instead (to the exact same values present in `remotePatterns` above).
//
return {
loader: 'custom',
loaderFile: './cloudflare/image-loader.ts',
};
}

return {
// We disable image optimisation during static export builds
unoptimized: ENABLE_STATIC_EXPORT,
// We add it to the remote pattern for the static images we use from multiple sources
// to be marked as safe sources (these come from Markdown files)
remotePatterns: remotePatterns.map(url => new URL(url)),
};
};
18 changes: 15 additions & 3 deletions apps/site/open-next.config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import { defineCloudflareConfig } from '@opennextjs/cloudflare';
import incrementalCache from '@opennextjs/cloudflare/overrides/incremental-cache/kv-incremental-cache';
import r2IncrementalCache from '@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache';
import { withRegionalCache } from '@opennextjs/cloudflare/overrides/incremental-cache/regional-cache';
import doQueue from '@opennextjs/cloudflare/overrides/queue/do-queue';

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

const cloudflareConfig = defineCloudflareConfig({ incrementalCache });
const cloudflareConfig = defineCloudflareConfig({
/**
* The regional cache implementation with R2 (instead of a KV one) is is chosen here
* for both R2's strong consistency alongside the regional cache performance gains.
* @see https://opennext.js.org/cloudflare/caching
*/
incrementalCache: withRegionalCache(r2IncrementalCache, {
mode: 'long-lived',
}),
queue: doQueue,
enableCacheInterception: true,
});

const openNextConfig: OpenNextConfig = {
...cloudflareConfig,
buildCommand: 'pnpm build:default',
cloudflare: {
skewProtection: {
enabled: true,
Expand Down
9 changes: 4 additions & 5 deletions apps/site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
"type": "module",
"scripts": {
"prebuild": "node --run build:blog-data",
"build": "node --run build:default -- --turbo",
"build": "cross-env NODE_NO_WARNINGS=1 next build --turbo",
"build:blog-data": "cross-env NODE_NO_WARNINGS=1 node ./scripts/blog-data/index.mjs",
"build:blog-data:watch": "node --watch --watch-path=pages/en/blog ./scripts/blog-data/index.mjs",
"build:default": "cross-env NODE_NO_WARNINGS=1 next build",
"cloudflare:build:worker": "OPEN_NEXT_CLOUDFLARE=true opennextjs-cloudflare build",
"cloudflare:deploy": "opennextjs-cloudflare deploy",
"cloudflare:preview": "wrangler dev",
"predeploy": "node --run build:blog-data",
"deploy": "cross-env NEXT_PUBLIC_STATIC_EXPORT=true node --run build:default -- --turbo",
"deploy": "cross-env NEXT_PUBLIC_STATIC_EXPORT=true node --run build",
"predev": "node --run build:blog-data",
"dev": "cross-env NODE_NO_WARNINGS=1 next dev --turbo",
"lint": "node --run lint:js && node --run lint:css && node --run lint:md",
Expand Down Expand Up @@ -83,7 +82,7 @@
"@flarelabs-net/wrangler-build-time-fs-assets-polyfilling": "^0.0.1",
"@next/eslint-plugin-next": "15.5.4",
"@node-core/remark-lint": "workspace:*",
"@opennextjs/cloudflare": "^1.6.4",
"@opennextjs/cloudflare": "^1.13.1",
"@playwright/test": "^1.56.1",
"@testing-library/user-event": "~14.6.1",
"@types/mdast": "^4.0.4",
Expand All @@ -108,7 +107,7 @@
"typescript": "catalog:",
"typescript-eslint": "~8.45.0",
"user-agent-data-types": "0.4.2",
"wrangler": "^4.33.1"
"wrangler": "^4.45.3"
},
"imports": {
"#site/*": [
Expand Down
20 changes: 17 additions & 3 deletions apps/site/wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,24 @@
"fs": "./.wrangler/fs-assets-polyfilling/polyfills/node/fs.ts",
"fs/promises": "./.wrangler/fs-assets-polyfilling/polyfills/node/fs/promises.ts",
},
"kv_namespaces": [
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_KV",
"id": "69b7422d56dd4244bc0127b69ecdc36f",
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "next-cache-r2-for-open-next-website",
},
],
"durable_objects": {
"bindings": [
{
"name": "NEXT_CACHE_DO_QUEUE",
"class_name": "DOQueueHandler",
},
],
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["DOQueueHandler"],
},
],
}
13 changes: 11 additions & 2 deletions docs/cloudflare-build-and-deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ Key configurations include:
- This is currently set to `fb4a2d0f103c6ff38854ac69eb709272`, which is the ID of a Cloudflare account controlled by Node.js, and used for testing.
- `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.
- `alias`: Maps aliases for the Node.js filesystem polyfills generated during the build process.
- `kv_namespaces`: Contains a single KV binding definition for `NEXT_INC_CACHE_KV`. This is used to implement the Next.js incremental cache.
- This is currently set up to a KV in the aforementioned Cloudflare testing account.
- `r2_buckets`: Contains a single R2 binding definition for `NEXT_INC_CACHE_R2_BUCKET`. This is used to implement the Next.js incremental cache.
- This is currently set up to a R2 bucket in the aforementioned Cloudflare testing account.
- `durable_objects`: Contains a single DurableObject binding definition for `NEXT_CACHE_DO_QUEUE`. This is used to implement the Open-next cache queue.

### OpenNext Configuration

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

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.

### Image loader

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).

The custom loader can be found at [`site/cloudflare/image-loader.ts`](../apps/site/cloudflare/image-loader.ts).

For more details on this see: https://developers.cloudflare.com/images/transform-images/integrate-with-frameworks/#global-loader

## Scripts

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