Skip to content

Commit d7b6e7e

Browse files
committed
refactor: consolidate platform detection into NEXT_PUBLIC_DEPLOY_TARGET
Platform detection in apps/site was spread across three different mechanisms: `VERCEL_ENV`, `OPEN_NEXT_CLOUDFLARE`, and a runtime `'Cloudflare' in global` check in the MDX plugin. Each one carried its own caveats (the global check blocked tree-shaking; the two env vars couldn't be compared to "is this build neutral / Vercel / Cloudflare?" uniformly), and Vercel analytics were imported eagerly in the root layout even on Cloudflare builds. Replace all three with a single `NEXT_PUBLIC_DEPLOY_TARGET` env var set by each deployment wrapper (`vercel.json` -> `vercel`, `open-next.config.ts` -> `cloudflare`). The `NEXT_PUBLIC_` prefix lets Next.js inline the value at build time so platform-specific branches are dead-code-eliminated from non-matching bundles. Extract the Vercel Analytics + SpeedInsights injection into a `platform/body-end` slot. The core layout renders `<BodyEnd />` and the slot's dynamic import resolves only on Vercel builds, so the Vercel modules no longer ship to Cloudflare.
1 parent 524e64b commit d7b6e7e

14 files changed

Lines changed: 65 additions & 50 deletions

apps/site/app/[locale]/layout.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import { availableLocales, defaultLocale } from '@node-core/website-i18n';
2-
import { Analytics } from '@vercel/analytics/react';
3-
import { SpeedInsights } from '@vercel/speed-insights/next';
42
import classNames from 'classnames';
53
import { NextIntlClientProvider } from 'next-intl';
64

75
import BaseLayout from '#site/layouts/Base';
8-
import { VERCEL_ENV } from '#site/next.constants.mjs';
96
import { IBM_PLEX_MONO, OPEN_SANS } from '#site/next.fonts';
7+
import BodyEnd from '#site/platform/body-end';
108
import { ThemeProvider } from '#site/providers/themeProvider';
119

1210
import type { FC, PropsWithChildren } from 'react';
@@ -46,12 +44,7 @@ const RootLayout: FC<RootLayoutProps> = async ({ children, params }) => {
4644
href="https://social.lfx.dev/@nodejs"
4745
/>
4846

49-
{VERCEL_ENV && (
50-
<>
51-
<Analytics />
52-
<SpeedInsights />
53-
</>
54-
)}
47+
<BodyEnd />
5548
</body>
5649
</html>
5750
);

apps/site/instrumentation.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
export async function register() {
2-
if (!('Cloudflare' in globalThis)) {
3-
// Note: we don't need to set up the Vercel OTEL if the application is running on Cloudflare
2+
if (process.env.NEXT_PUBLIC_DEPLOY_TARGET === 'vercel') {
43
const { registerOTel } = await import('@vercel/otel');
54
registerOTel({ serviceName: 'nodejs-org' });
65
}

apps/site/mdx/plugins.mjs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,6 @@ import readingTime from 'remark-reading-time';
99

1010
import remarkTableTitles from '../util/table';
1111

12-
// TODO(@avivkeller): When available, use `OPEN_NEXT_CLOUDFLARE` environment
13-
// variable for detection instead of current method, which will enable better
14-
// tree-shaking.
15-
// Reference: https://github.com/nodejs/nodejs.org/pull/7896#issuecomment-3009480615
16-
const OPEN_NEXT_CLOUDFLARE = 'Cloudflare' in global;
17-
1812
// Shiki is created out here to avoid an async rehype plugin
1913
const singletonShiki = await rehypeShikiji({
2014
// We use the faster WASM engine on the server instead of the web-optimized version.
@@ -23,10 +17,10 @@ const singletonShiki = await rehypeShikiji({
2317
// on Cloudflare workers because `shiki/wasm` requires loading via
2418
// `WebAssembly.instantiate` with custom imports, which Cloudflare doesn't support
2519
// for security reasons.
26-
wasm: !OPEN_NEXT_CLOUDFLARE,
20+
wasm: process.env.NEXT_PUBLIC_DEPLOY_TARGET !== 'cloudflare',
2721

2822
// TODO(@avivkeller): Find a way to enable Twoslash w/ a VFS on Cloudflare
29-
twoslash: !OPEN_NEXT_CLOUDFLARE,
23+
twoslash: process.env.NEXT_PUBLIC_DEPLOY_TARGET !== 'cloudflare',
3024
});
3125

3226
/**

apps/site/next.config.mjs

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

4-
import { OPEN_NEXT_CLOUDFLARE } from './next.constants.cloudflare.mjs';
5-
import { BASE_PATH, ENABLE_STATIC_EXPORT } from './next.constants.mjs';
4+
import {
5+
BASE_PATH,
6+
DEPLOY_TARGET,
7+
ENABLE_STATIC_EXPORT,
8+
} from './next.constants.mjs';
69
import { getImagesConfig } from './next.image.config.mjs';
710
import { redirects, rewrites } from './next.rewrites.mjs';
811

912
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();
13+
if (DEPLOY_TARGET !== 'cloudflare') {
14+
return undefined;
1615
}
1716

18-
return undefined;
17+
// If we're building for the Cloudflare deployment we want to set
18+
// an appropriate deploymentId (needed for skew protection)
19+
const openNextAdapter = await import('@opennextjs/cloudflare');
20+
21+
return openNextAdapter.getDeploymentId();
1922
};
2023

2124
/** @type {import('next').NextConfig} */

apps/site/next.constants.cloudflare.mjs

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

apps/site/next.constants.mjs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,18 @@
66
export const IS_DEV_ENV = process.env.NODE_ENV === 'development';
77

88
/**
9-
* This is used for telling Next.js if the Website is deployed on Vercel
9+
* Identifies the deployment platform the site is being built for.
1010
*
11-
* Can be used for conditionally enabling features that we know are Vercel only
11+
* Set by the deployment wrapper at build time: `vercel.json`'s `buildCommand`
12+
* sets `vercel`, `open-next.config.ts`'s `buildCommand` sets `cloudflare`.
13+
* Unset for standalone builds (local dev, static export).
1214
*
13-
* @see https://vercel.com/docs/projects/environment-variables/system-environment-variables#VERCEL_ENV
15+
* The `NEXT_PUBLIC_` prefix makes Next.js inline the value at build time,
16+
* enabling dead-code elimination of platform-specific branches.
17+
*
18+
* @type {'vercel' | 'cloudflare' | undefined}
1419
*/
15-
export const VERCEL_ENV = process.env.VERCEL_ENV || undefined;
20+
export const DEPLOY_TARGET = process.env.NEXT_PUBLIC_DEPLOY_TARGET;
1621

1722
/**
1823
* This is used for telling Next.js to do a Static Export Build of the Website

apps/site/next.image.config.mjs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { OPEN_NEXT_CLOUDFLARE } from './next.constants.cloudflare.mjs';
2-
import { ENABLE_STATIC_EXPORT } from './next.constants.mjs';
1+
import { DEPLOY_TARGET, ENABLE_STATIC_EXPORT } from './next.constants.mjs';
32

43
const remotePatterns = [
54
'https://avatars.githubusercontent.com/**',
@@ -10,7 +9,7 @@ const remotePatterns = [
109
];
1110

1211
export const getImagesConfig = () => {
13-
if (OPEN_NEXT_CLOUDFLARE) {
12+
if (DEPLOY_TARGET === 'cloudflare') {
1413
// If we're building for the Cloudflare deployment we want to use the custom cloudflare image loader
1514
//
1615
// Important: The custom loader ignores `remotePatterns` as those are configured as allowed source origins

apps/site/open-next.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ const cloudflareConfig = defineCloudflareConfig({
2020

2121
const openNextConfig: OpenNextConfig = {
2222
...cloudflareConfig,
23-
buildCommand: 'pnpm build --webpack',
23+
buildCommand:
24+
'cross-env NEXT_PUBLIC_DEPLOY_TARGET=cloudflare pnpm build --webpack',
2425
cloudflare: {
2526
skewProtection: { enabled: true },
2627
},

apps/site/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"build": "cross-env NODE_NO_WARNINGS=1 next build",
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-
"cloudflare:build:worker": "OPEN_NEXT_CLOUDFLARE=true opennextjs-cloudflare build",
9+
"cloudflare:build:worker": "opennextjs-cloudflare build",
1010
"cloudflare:deploy": "opennextjs-cloudflare deploy",
1111
"cloudflare:preview": "wrangler dev",
1212
"predeploy": "node --run build:blog-data",

apps/site/platform/body-end.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* Per-platform "body end" slot. Deployment targets can inject DOM at the
3+
* end of the document body (analytics, tracking scripts, etc.) without
4+
* adding platform-specific imports to the core layout.
5+
*
6+
* `NEXT_PUBLIC_DEPLOY_TARGET` is inlined by Next.js at build time, so on
7+
* non-matching platforms the dynamic import is unreachable and tree-shaken
8+
* out of the bundle — the Vercel modules never ship to Cloudflare builds.
9+
*/
10+
export default async function BodyEnd() {
11+
if (process.env.NEXT_PUBLIC_DEPLOY_TARGET === 'vercel') {
12+
const { default: VercelBodyEnd } = await import('./body-end.vercel');
13+
return <VercelBodyEnd />;
14+
}
15+
16+
return null;
17+
}

0 commit comments

Comments
 (0)