Skip to content

Make site work with the Cloudflare OpenNext adapter #7383

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,11 @@ cache
tsconfig.tsbuildinfo

dist/

# Ignore the blog-data json that we generate during dev and build
apps/site/public/blog-data.json

# Ignore worker artifacts
apps/site/.open-next
apps/site/.wrangler

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

</details>

## Experimental Cloudflare Deployment

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

Such migration is still in its experimental phase and is being gradually rolled out.

### Scripts

For the time being preview and deployment of the website targeting the Cloudflare network
is implemented via the following two scripts:

- `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/))
- `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/))

### Configurations

There are two types of configurations that relate to Cloudflare.

#### Wrangler Configuration

This is the configuration for the Cloudflare Worker that refers to the artifacts being
built by the OpenNext adapter. For more details on such configuration please refer to
the [official Cloudflare documentation](https://developers.cloudflare.com/workers/wrangler/configuration/).

The the most important configurations to keep in mind are:

- `main` points to the worker generated by the OpenNext adapter
- `account_id` ID of the accounts to use to deploy the application
(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)
- `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)
- `alias` map of aliases for the Node.js fs polyfills generated by the `build` configuration
- `kv_namespaces` array containing a single KV binding definition for `NEXT_CACHE_WORKERS_KV`
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
that they can set up for free at [dash.cloudflare.com/kv/namespaces](https://dash.cloudflare.com/?to=/:account/workers/kv/namespaces))

#### OpenNext Configuration

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

The configuration present here is very standard and simply sets up incremental cache via the KV binding
defined in the wrangler configuration file.

## Commit Guidelines

This project follows the [Conventional Commits][] specification.
Expand Down

This file was deleted.

6 changes: 3 additions & 3 deletions apps/site/layouts/Blog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import type { BlogCategory } from '@/types';

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

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

const { posts, pagination } = await getBlogData(
const { posts, pagination } = getBlogData(
category as BlogCategory,
Number(page)
);
Expand All @@ -38,7 +38,7 @@ const BlogLayout: FC = async () => {
link: `/blog/${category}`,
}));

const blogData = await getBlogCategory(pathname);
const blogData = getBlogCategory(pathname);

return (
<>
Expand Down
42 changes: 10 additions & 32 deletions apps/site/next-data/blogData.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,16 @@
import {
ENABLE_STATIC_EXPORT,
IS_DEV_ENV,
NEXT_DATA_URL,
VERCEL_ENV,
VERCEL_REGION,
} from '@/next.constants.mjs';
import type { BlogCategory, BlogPostsRSC } from '@/types';

const getBlogData = (
cat: BlogCategory,
page?: number
): Promise<BlogPostsRSC> => {
const IS_NOT_VERCEL_RUNTIME_ENV =
(!IS_DEV_ENV && VERCEL_ENV && !VERCEL_REGION) ||
(!IS_DEV_ENV && !VERCEL_ENV);

// When we're using Static Exports the Next.js Server is not running (during build-time)
// hence the self-ingestion APIs will not be available. In this case we want to load
// the data directly within the current thread, which will anyways be loaded only once
// We use lazy-imports to prevent `provideBlogData` from executing on import
if (ENABLE_STATIC_EXPORT || IS_NOT_VERCEL_RUNTIME_ENV) {
return import('@/next-data/providers/blogData').then(
({ provideBlogPosts, providePaginatedBlogPosts }) =>
page ? providePaginatedBlogPosts(cat, page) : provideBlogPosts(cat)
);
}

const fetchURL = `${NEXT_DATA_URL}blog-data/${cat}/${page ?? 0}`;
import {
provideBlogPosts,
providePaginatedBlogPosts,
} from './providers/blogData';

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

export default getBlogData;
10 changes: 8 additions & 2 deletions apps/site/next-data/providers/blogData.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { cache } from 'react';

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

const { categories, posts } = await generateBlogData();
const { categories, posts } = {
...blogData,
posts: blogData.posts.map(post => ({
...post,
date: new Date(post.date),
})),
};

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

Expand Down
4 changes: 4 additions & 0 deletions apps/site/next.json.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import _authors from './authors.json' with { type: 'json' };
import _siteNavigation from './navigation.json' with { type: 'json' };
import _blogData from './public/blog-data.json' with { type: 'json' };
import _siteRedirects from './redirects.json' with { type: 'json' };
import _siteConfig from './site.json' with { type: 'json' };

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

/** @type {import('./types').SiteConfig} */
export const siteConfig = _siteConfig;

/** @type {import('./types').BlogData} */
export const blogData = _blogData;
6 changes: 6 additions & 0 deletions apps/site/open-next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { defineCloudflareConfig } from '@opennextjs/cloudflare';
import kvIncrementalCache from '@opennextjs/cloudflare/overrides/incremental-cache/kv-incremental-cache';

export default defineCloudflareConfig({
incrementalCache: kvIncrementalCache,
});
19 changes: 14 additions & 5 deletions apps/site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
"type": "module",
"scripts": {
"scripts:release-post": "cross-env NODE_NO_WARNINGS=1 node scripts/release-post/index.mjs",
"dev": "cross-env NODE_NO_WARNINGS=1 next dev",
"serve": "npm run dev",
"build": "cross-env NODE_NO_WARNINGS=1 next build --turbopack",
"dev": "npm run build-blog-data:watch & sleep 1 && cross-env NODE_NO_WARNINGS=1 next dev",
"serve": "npm run build-blog-data:watch & sleep 1 && npm run dev",
"prebuild": "npm run build-blog-data",
"build": "cross-env NODE_NO_WARNINGS=1 next build",
"start": "cross-env NODE_NO_WARNINGS=1 next start",
"deploy": "cross-env NEXT_PUBLIC_STATIC_EXPORT=true NODE_NO_WARNINGS=1 next build",
"check-types": "tsc --noEmit",
Expand All @@ -18,7 +19,12 @@
"sync-orama": "node ./scripts/orama-search/sync-orama-cloud.mjs",
"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.*",
"test:unit:watch": "cross-env NODE_OPTIONS=\"--watch\" npm run test:unit",
"test": "turbo test:unit"
"test": "turbo test:unit",
"build-blog-data": "node ./scripts/blog-data/generate.mjs",
"build-blog-data:watch": "node --watch --watch-path=pages/en ./scripts/blog-data/generate.mjs",
"cloudflare:build:worker": "npx @opennextjs/cloudflare build",
"cloudflare:preview": "npm run cloudflare:build:worker && npx wrangler dev",
"cloudflare:deploy": "npm run cloudflare:build:worker && npx wrangler deploy"
},
"dependencies": {
"@heroicons/react": "~2.2.0",
Expand Down Expand Up @@ -85,6 +91,9 @@
"tsx": "^4.19.3",
"typescript": "~5.8.2",
"typescript-eslint": "~8.29.0",
"user-agent-data-types": "0.4.2"
"user-agent-data-types": "0.4.2",
"@opennextjs/cloudflare": "^1.0.0-beta.3",
"wrangler": "^4.13.0",
"@flarelabs-net/wrangler-build-time-fs-assets-polyfilling": "^0.0.0"
}
}
14 changes: 14 additions & 0 deletions apps/site/scripts/blog-data/generate.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { writeFileSync } from 'node:fs';
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

import generateBlogData from '../../next-data/generators/blogData.mjs';

const blogData = await generateBlogData();

const __dirname = dirname(fileURLToPath(import.meta.url));
writeFileSync(
`${__dirname}/../../public/blog-data.json`,
JSON.stringify(blogData),
'utf8'
);
28 changes: 27 additions & 1 deletion apps/site/turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,39 @@
"cache": false
},
"test:unit": {
"dependsOn": ["build-blog-data"],
"inputs": [
"{app,components,hooks,i18n,layouts,middlewares,pages,providers,types,util}/**/*.{ts,tsx,mjs}",
"{app,components,layouts,pages,styles}/**/*.css",
"{next-data,scripts,i18n}/**/*.{mjs,json}",
"{app,pages}/**/*.{mdx,md}",
"*.{md,mdx,json,ts,tsx,mjs,yml}"
]
],
"outputs": ["coverage/**", "junit.xml"]
},
"build-blog-data": {
"inputs": ["{pages}/**/*.{mdx,md}"],
"outputs": ["public/blog-data.json"]
},
"cloudflare:preview": {
"inputs": [
"{app,components,hooks,i18n,layouts,middlewares,pages,providers,types,util}/**/*.{ts,tsx}",
"{app,components,layouts,pages,styles}/**/*.css",
"{next-data,scripts,i18n}/**/*.{mjs,json}",
"{app,pages}/**/*.{mdx,md}",
"*.{md,mdx,json,ts,tsx,mjs,yml}"
],
"outputs": [".open-next/**"]
},
"cloudflare:deploy": {
"inputs": [
"{app,components,hooks,i18n,layouts,middlewares,pages,providers,types,util}/**/*.{ts,tsx}",
"{app,components,layouts,pages,styles}/**/*.css",
"{next-data,scripts,i18n}/**/*.{mjs,json}",
"{app,pages}/**/*.{mdx,md}",
"*.{md,mdx,json,ts,tsx,mjs,yml}"
],
"outputs": [".open-next/**"]
}
}
}
32 changes: 32 additions & 0 deletions apps/site/wrangler.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"main": ".open-next/worker.js",
"name": "nodejs-website",
"compatibility_date": "2024-11-07",
"compatibility_flags": ["nodejs_compat"],
"account_id": "8ed4d03ac99f77561d0e8c9cbcc76cb6",
"minify": true,
"keep_names": false,
"assets": {
"directory": ".open-next/assets",
"binding": "ASSETS",
},
"observability": {
"enabled": true,
"head_sampling_rate": 1,
},
"build": {
"command": "wrangler-build-time-fs-assets-polyfilling --assets pages --assets snippets --assets-output-dir .open-next/assets",
},
"alias": {
"node:fs": "./.wrangler/fs-assets-polyfilling/polyfills/node/fs.ts",
"node:fs/promises": "./.wrangler/fs-assets-polyfilling/polyfills/node/fs/promises.ts",
"fs": "./.wrangler/fs-assets-polyfilling/polyfills/node/fs.ts",
"fs/promises": "./.wrangler/fs-assets-polyfilling/polyfills/node/fs/promises.ts",
},
"kv_namespaces": [
{
"binding": "NEXT_CACHE_WORKERS_KV",
"id": "32e8e26d2d2647fd96789baf83209fa9",
},
],
}
2 changes: 2 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export default [
'global.d.ts',
'junit.xml',
'storybook-static/**',
'**/.wrangler',
'**/.open-next',
],
},
{
Expand Down
Loading
Loading