Skip to content

Commit 274826c

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 b551775 commit 274826c

16 files changed

Lines changed: 4760 additions & 973 deletions

File tree

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,11 @@ cache
3535
tsconfig.tsbuildinfo
3636

3737
dist/
38+
39+
# Ignore the blog-data json that we generate during dev and build
40+
apps/site/public/blog-data.json
41+
42+
# Cloudflare Build Output
43+
apps/site/.open-next
44+
apps/site/.wrangler
45+

CONTRIBUTING.md

Lines changed: 37 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+
- [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)
@@ -165,6 +166,42 @@ This repository contains several scripts and commands for performing numerous ta
165166

166167
</details>
167168

169+
## Cloudflare Deployment
170+
171+
The Node.js Website can be deployed to the [Cloudflare](https://www.cloudflare.com) network using [Cloudflare Workers](https://www.cloudflare.com/en-gb/developer-platform/products/workers/) and the [OpenNext Cloudflare adapter](https://opennext.js.org/cloudflare). This section provides the necessary details for testing and deploying the website on Cloudflare.
172+
173+
### Scripts
174+
175+
Preview and deployment of the website targeting the Cloudflare network is implemented via the following two scripts:
176+
177+
- `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/))
178+
- `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/))
179+
180+
### Configurations
181+
182+
There are two key configuration files related to Cloudflare deployment.
183+
184+
#### Wrangler Configuration
185+
186+
This file defines the settings for the Cloudflare Worker, which serves the website.
187+
188+
For more details, refer to the [Wrangler documentation](https://developers.cloudflare.com/workers/wrangler/configuration/).
189+
190+
Key configurations include:
191+
192+
- `main`: Points to the worker generated by the OpenNext adapter.
193+
- `account_id`: Specifies the Cloudflare account ID. This is not required for local previews but is necessary for deployments. You can obtain an account ID for free by signing up at [dash.cloudflare.com](https://dash.cloudflare.com/login).
194+
- `build`: Defines the build command to generate Node.js filesystem polyfills required for the application to run on Cloudflare Workers. This uses the [`@flarelabs/wrangler-build-time-fs-assets-polyfilling`](https://www.npmjs.com/package/@flarelabs-net/wrangler-build-time-fs-assets-polyfilling) package.
195+
- `alias`: Maps aliases for the Node.js filesystem polyfills generated during the build process.
196+
- `kv_namespaces`: Contains a single KV binding definition for `NEXT_CACHE_WORKERS_KV`. This is used to implement the Next.js incremental cache. For deployments, you can create a new KV namespace in the Cloudflare dashboard and update the binding ID accordingly.
197+
198+
#### OpenNext Configuration
199+
200+
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).
201+
202+
The configuration present here is very standard and simply sets up incremental cache via the KV binding
203+
defined in the wrangler configuration file.
204+
168205
## Commit Guidelines
169206

170207
This project follows the [Conventional Commits][] specification.

apps/site/.stylelintignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ lcov.info
1313

1414
# Old Styles
1515
styles/old
16+
17+
# Cloudflare Build Output
18+
.open-next
19+
.wrangler

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 & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,16 @@
1-
import {
2-
ENABLE_STATIC_EXPORT,
3-
NEXT_DATA_URL,
4-
IS_NOT_VERCEL_RUNTIME_ENV,
5-
} from '@/next.constants.mjs';
61
import type { BlogCategory, BlogPostsRSC } from '@/types';
72

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

25-
// This data cannot be cached because it is continuously updated. Caching it would lead to
26-
// outdated information being shown to the user.
27-
return fetch(fetchURL)
28-
.then(response => response.text())
29-
.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);
3014
};
3115

3216
export default getBlogData;

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
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 } = blogData;
8+
const posts = blogData.posts.map(post => ({
9+
...post,
10+
date: new Date(post.date),
11+
}));
812

913
export const provideBlogCategories = cache(() => categories);
1014

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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { defineCloudflareConfig } from '@opennextjs/cloudflare';
2+
import incrementalCache from '@opennextjs/cloudflare/overrides/incremental-cache/kv-incremental-cache';
3+
4+
export default defineCloudflareConfig({ incrementalCache });

apps/site/package.json

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
"name": "@node-core/website",
33
"type": "module",
44
"scripts": {
5-
"build": "cross-env NODE_NO_WARNINGS=1 next build --turbopack",
5+
"prebuild": "pnpm build-blog-data",
6+
"build": "cross-env NODE_NO_WARNINGS=1 next build",
67
"check-types": "tsc --noEmit",
78
"deploy": "cross-env NEXT_PUBLIC_STATIC_EXPORT=true NODE_NO_WARNINGS=1 next build",
9+
"predev": "pnpm build-blog-data",
810
"dev": "cross-env NODE_NO_WARNINGS=1 next dev",
911
"lint": "turbo run lint:md lint:snippets lint:js lint:css",
1012
"lint:css": "stylelint \"**/*.css\" --allow-empty-input --cache --cache-strategy=content --cache-location=.stylelintcache",
@@ -18,7 +20,12 @@
1820
"sync-orama": "node ./scripts/orama-search/sync-orama-cloud.mjs",
1921
"test": "turbo test:unit",
2022
"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.*",
21-
"test:unit:watch": "cross-env NODE_OPTIONS=\"--watch\" pnpm test:unit"
23+
"test:unit:watch": "cross-env NODE_OPTIONS=\"--watch\" pnpm test:unit",
24+
"build-blog-data": "node ./scripts/blog-data/generate.mjs",
25+
"build-blog-data:watch": "node --watch --watch-path=pages/en/blog ./scripts/blog-data/generate.mjs",
26+
"cloudflare:build:worker": "opennextjs-cloudflare build",
27+
"cloudflare:preview": "pnpm run cloudflare:build:worker && wrangler dev",
28+
"cloudflare:deploy": "pnpm run cloudflare:build:worker && wrangler deploy"
2229
},
2330
"dependencies": {
2431
"@heroicons/react": "~2.2.0",
@@ -107,6 +114,12 @@
107114
"typescript": "~5.8.2",
108115
"typescript-eslint": "~8.31.1",
109116
"unified": "^11.0.5",
110-
"user-agent-data-types": "0.4.2"
117+
"user-agent-data-types": "0.4.2",
118+
"@opennextjs/cloudflare": "^1.0.0-beta.3",
119+
"wrangler": "^4.13.0",
120+
"@flarelabs-net/wrangler-build-time-fs-assets-polyfilling": "^0.0.0"
121+
},
122+
"optionalDependencies": {
123+
"@tailwindcss/oxide-linux-x64-gnu": "~4.1.4"
111124
}
112125
}

0 commit comments

Comments
 (0)