Skip to content

Commit f4145f2

Browse files
make site work with the Cloudflare OpenNext adapter
update the site application so that it can be build using the Cloudflare OpenNext adapter (`@opennextjs/cloudflare`) and thus deployed on Cloudflare Workers
1 parent d5ecfaa commit f4145f2

14 files changed

Lines changed: 14857 additions & 8343 deletions

File tree

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,11 @@ cache
3131
tsconfig.tsbuildinfo
3232

3333
dist/
34+
35+
# Ignore the blog-data json that we generate during dev and build
36+
apps/site/public/blog-data.json
37+
38+
# Ignore worker artifacts
39+
apps/site/.open-next
40+
apps/site/.wrangler
41+

CONTRIBUTING.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Thank you for your interest in contributing to the Node.js Website. Before you p
77
- [Becoming a collaborator](#becoming-a-collaborator)
88
- [Getting started](#getting-started)
99
- [CLI Commands](#cli-commands)
10+
- [Experimental Cloudflare Deployment](#cloudflare-deployment)
1011
- [Commit Guidelines](#commit-guidelines)
1112
- [Pull Request Policy](#pull-request-policy)
1213
- [Developer's Certificate of Origin 1.1](#developers-certificate-of-origin-11)
@@ -164,6 +165,48 @@ This repository contains several scripts and commands for performing numerous ta
164165

165166
</details>
166167

168+
## Experimental Cloudflare Deployment
169+
170+
The website is in the process of being fully migrated and served from the [Cloudflare](https://www.cloudflare.com) network via [Cloudflare Workers](https://www.cloudflare.com/en-gb/developer-platform/products/workers/) using the [OpenNext Cloudflare adapter](https://opennext.js.org/cloudflare).
171+
172+
Such migration is still in its experimental phase and is being gradually rolled out.
173+
174+
### Scripts
175+
176+
For the time being preview and deployment of the website targeting the Cloudflare network
177+
is implemented via the following two scripts:
178+
179+
- `npx turbo cloudflare:preview` builds the website using the OpenNext Cloudflare adapter and runs the website locally in a server simulating the Cloudflare hosting (using the [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/))
180+
- `npx turbo cloudflare:deploy` builds the website using the OpenNext Cloudflare adapter and deploys the website to the Cloudflare network (using the [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/))
181+
182+
### Configurations
183+
184+
There are two types of configurations that relate to Cloudflare.
185+
186+
#### Wrangler Configuration
187+
188+
This is the configuration for the Cloudflare Worker that refers to the artifacts being
189+
built by the OpenNext adapter. For more details on such configuration please refer to
190+
the [official Cloudflare documentation](https://developers.cloudflare.com/workers/wrangler/configuration/).
191+
192+
The the most important configurations to keep in mind are:
193+
194+
- `main` points to the worker generated by the OpenNext adapter
195+
- `account_id` ID of the accounts to use to deploy the application
196+
(this account ID doesn't matter for local development, for deployment external contributors can sign up for free at [dash.cloudflare.com](https://dash.cloudflare.com/login) and obtain an account id that they can use to deploy their own fork of the website)
197+
- `build` build command used to generate Node.js fs polyfills needed for the application to run in a Cloudflare Worker (using the [`@flarelabs/wrangler-build-time-fs-assets-polyfilling`](https://www.npmjs.com/package/@flarelabs-net/wrangler-build-time-fs-assets-polyfilling) package)
198+
- `alias` map of aliases for the Node.js fs polyfills generated by the `build` configuration
199+
- `kv_namespaces` array containing a single KV binding definition for `NEXT_CACHE_WORKERS_KV`
200+
this is a [KV binding](https://developers.cloudflare.com/kv/concepts/kv-bindings/) used to implement the Next.js incremental cache (again, external contributors can replace the KV ID with their own
201+
that they can set up for free at [dash.cloudflare.com/kv/namespaces](https://dash.cloudflare.com/?to=/:account/workers/kv/namespaces))
202+
203+
#### OpenNext Configuration
204+
205+
This is the configuration for the OpenNext Cloudflare adapter, for more details on such configuration please refer to the [official OpenNext documentation](https://opennext.js.org/cloudflare/get-started#4-add-an-open-nextconfigts-file).
206+
207+
The configuration present here is very standard and simply sets up incremental cache via the KV binding
208+
defined in the wrangler configuration file.
209+
167210
## Commit Guidelines
168211
169212
This project follows the [Conventional Commits][] specification.

apps/site/app/[locale]/next-data/blog-data/[category]/[page]/route.ts

Lines changed: 0 additions & 48 deletions
This file was deleted.

apps/site/layouts/Blog.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ import type { BlogCategory } from '@/types';
1111

1212
import styles from './layouts.module.css';
1313

14-
const getBlogCategory = async (pathname: string) => {
14+
const getBlogCategory = (pathname: string) => {
1515
// pathname format can either be: /en/blog/{category}
1616
// or /en/blog/{category}/page/{page}
1717
// hence we attempt to interpolate the full /en/blog/{category}/page/{page}
1818
// and in case of course no page argument is provided we define it to 1
1919
// note that malformed routes can't happen as they are all statically generated
2020
const [, , category = 'all', , page = 1] = pathname.split('/');
2121

22-
const { posts, pagination } = await getBlogData(
22+
const { posts, pagination } = getBlogData(
2323
category as BlogCategory,
2424
Number(page)
2525
);
@@ -38,7 +38,7 @@ const BlogLayout: FC = async () => {
3838
link: `/blog/${category}`,
3939
}));
4040

41-
const blogData = await getBlogCategory(pathname);
41+
const blogData = getBlogCategory(pathname);
4242

4343
return (
4444
<>

apps/site/next-data/blogData.ts

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,16 @@
1-
import {
2-
ENABLE_STATIC_EXPORT,
3-
IS_DEV_ENV,
4-
NEXT_DATA_URL,
5-
VERCEL_ENV,
6-
VERCEL_REGION,
7-
} from '@/next.constants.mjs';
81
import type { BlogCategory, BlogPostsRSC } from '@/types';
92

10-
const getBlogData = (
11-
cat: BlogCategory,
12-
page?: number
13-
): Promise<BlogPostsRSC> => {
14-
const IS_NOT_VERCEL_RUNTIME_ENV =
15-
(!IS_DEV_ENV && VERCEL_ENV && !VERCEL_REGION) ||
16-
(!IS_DEV_ENV && !VERCEL_ENV);
17-
18-
// When we're using Static Exports the Next.js Server is not running (during build-time)
19-
// hence the self-ingestion APIs will not be available. In this case we want to load
20-
// the data directly within the current thread, which will anyways be loaded only once
21-
// We use lazy-imports to prevent `provideBlogData` from executing on import
22-
if (ENABLE_STATIC_EXPORT || IS_NOT_VERCEL_RUNTIME_ENV) {
23-
return import('@/next-data/providers/blogData').then(
24-
({ provideBlogPosts, providePaginatedBlogPosts }) =>
25-
page ? providePaginatedBlogPosts(cat, page) : provideBlogPosts(cat)
26-
);
27-
}
28-
29-
const fetchURL = `${NEXT_DATA_URL}blog-data/${cat}/${page ?? 0}`;
3+
import {
4+
provideBlogPosts,
5+
providePaginatedBlogPosts,
6+
} from './providers/blogData';
307

31-
// This data cannot be cached because it is continuously updated. Caching it would lead to
32-
// outdated information being shown to the user.
33-
return fetch(fetchURL)
34-
.then(response => response.text())
35-
.then(JSON.parse);
8+
const getBlogData = (cat: BlogCategory, page?: number): BlogPostsRSC => {
9+
return page && page >= 1
10+
? // This allows us to blindly get all blog posts from a given category
11+
// if the page number is 0 or something smaller than 1
12+
providePaginatedBlogPosts(cat, page)
13+
: provideBlogPosts(cat);
3614
};
3715

3816
export default getBlogData;

apps/site/next-data/providers/blogData.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import { cache } from 'react';
22

3-
import generateBlogData from '@/next-data/generators/blogData.mjs';
43
import { BLOG_POSTS_PER_PAGE } from '@/next.constants.mjs';
4+
import { blogData } from '@/next.json.mjs';
55
import type { BlogCategory, BlogPostsRSC } from '@/types';
66

7-
const { categories, posts } = await generateBlogData();
7+
const { categories, posts } = {
8+
...blogData,
9+
posts: blogData.posts.map(post => ({
10+
...post,
11+
date: new Date(post.date),
12+
})),
13+
};
814

915
export const provideBlogCategories = cache(() => categories);
1016

apps/site/next.json.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import _authors from './authors.json' with { type: 'json' };
44
import _siteNavigation from './navigation.json' with { type: 'json' };
5+
import _blogData from './public/blog-data.json' with { type: 'json' };
56
import _siteRedirects from './redirects.json' with { type: 'json' };
67
import _siteConfig from './site.json' with { type: 'json' };
78

@@ -16,3 +17,6 @@ export const siteRedirects = _siteRedirects;
1617

1718
/** @type {import('./types').SiteConfig} */
1819
export const siteConfig = _siteConfig;
20+
21+
/** @type {import('./types').BlogData} */
22+
export const blogData = _blogData;

apps/site/open-next.config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { defineCloudflareConfig } from '@opennextjs/cloudflare';
2+
import kvIncrementalCache from '@opennextjs/cloudflare/overrides/incremental-cache/kv-incremental-cache';
3+
4+
export default defineCloudflareConfig({
5+
incrementalCache: kvIncrementalCache,
6+
});

apps/site/package.json

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
"type": "module",
44
"scripts": {
55
"scripts:release-post": "cross-env NODE_NO_WARNINGS=1 node scripts/release-post/index.mjs",
6-
"dev": "cross-env NODE_NO_WARNINGS=1 next dev",
7-
"serve": "npm run dev",
8-
"build": "cross-env NODE_NO_WARNINGS=1 next build --turbopack",
6+
"dev": "npm run build-blog-data:watch & sleep 1 && cross-env NODE_NO_WARNINGS=1 next dev",
7+
"serve": "npm run build-blog-data:watch & sleep 1 && npm run dev",
8+
"prebuild": "npm run build-blog-data",
9+
"build": "cross-env NODE_NO_WARNINGS=1 next build",
910
"start": "cross-env NODE_NO_WARNINGS=1 next start",
1011
"deploy": "cross-env NEXT_PUBLIC_STATIC_EXPORT=true NODE_NO_WARNINGS=1 next build",
1112
"check-types": "tsc --noEmit",
@@ -18,7 +19,12 @@
1819
"sync-orama": "node ./scripts/orama-search/sync-orama-cloud.mjs",
1920
"test:unit": "cross-env NODE_NO_WARNINGS=1 node --experimental-test-coverage --test-coverage-exclude=**/*.test.* --experimental-test-module-mocks --enable-source-maps --import=global-jsdom/register --import=tsx --import=tests/setup.jsx --test **/*.test.*",
2021
"test:unit:watch": "cross-env NODE_OPTIONS=\"--watch\" npm run test:unit",
21-
"test": "turbo test:unit"
22+
"test": "turbo test:unit",
23+
"build-blog-data": "node ./scripts/blog-data/generate.mjs",
24+
"build-blog-data:watch": "node --watch --watch-path=pages/en ./scripts/blog-data/generate.mjs",
25+
"cloudflare:build:worker": "npx @opennextjs/cloudflare build",
26+
"cloudflare:preview": "npm run cloudflare:build:worker && npx wrangler dev",
27+
"cloudflare:deploy": "npm run cloudflare:build:worker && npx wrangler deploy"
2228
},
2329
"dependencies": {
2430
"@heroicons/react": "~2.2.0",
@@ -70,7 +76,7 @@
7076
"@types/semver": "~7.7.0",
7177
"eslint-config-next": "15.2.4",
7278
"eslint-import-resolver-typescript": "~4.3.1",
73-
"eslint-plugin-mdx": "~3.3.1",
79+
"eslint-plugin-mdx": "~3.4.0",
7480
"eslint-plugin-react": "~7.37.4",
7581
"eslint-plugin-react-hooks": "5.2.0",
7682
"global-jsdom": "^26.0.0",
@@ -85,6 +91,9 @@
8591
"tsx": "^4.19.3",
8692
"typescript": "~5.8.2",
8793
"typescript-eslint": "~8.29.0",
88-
"user-agent-data-types": "0.4.2"
94+
"user-agent-data-types": "0.4.2",
95+
"@opennextjs/cloudflare": "^1.0.0-beta.3",
96+
"wrangler": "^4.13.0",
97+
"@flarelabs-net/wrangler-build-time-fs-assets-polyfilling": "^0.0.0"
8998
}
9099
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { writeFileSync } from 'node:fs';
2+
import { dirname } from 'node:path';
3+
import { fileURLToPath } from 'node:url';
4+
5+
import generateBlogData from '../../next-data/generators/blogData.mjs';
6+
7+
const blogData = await generateBlogData();
8+
9+
const __dirname = dirname(fileURLToPath(import.meta.url));
10+
writeFileSync(
11+
`${__dirname}/../../public/blog-data.json`,
12+
JSON.stringify(blogData),
13+
'utf8'
14+
);

0 commit comments

Comments
 (0)