diff --git a/package.json b/package.json index 6381f9f58e529..9f04265b61586 100644 --- a/package.json +++ b/package.json @@ -249,7 +249,7 @@ "@types/react-dom": "18.0.11" }, "engines": { - "node": ">=16" + "node": ">=16.8.0" }, "packageManager": "pnpm@7.24.3" } diff --git a/packages/next/package.json b/packages/next/package.json index f75842f08ec4a..4cc04866da146 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -315,6 +315,6 @@ "caniuse-lite": "1.0.30001406" }, "engines": { - "node": ">=16" + "node": ">=16.8.0" } } diff --git a/packages/next/src/build/utils.ts b/packages/next/src/build/utils.ts index e74266ca8d870..6ca131c882c1d 100644 --- a/packages/next/src/build/utils.ts +++ b/packages/next/src/build/utils.ts @@ -16,6 +16,7 @@ import type { StaticGenerationAsyncStorage } from '../client/components/static-g import '../server/require-hook' import '../server/node-polyfill-fetch' +import '../server/node-polyfill-crypto' import chalk from 'next/dist/compiled/chalk' import getGzipSize from 'next/dist/compiled/gzip-size' import textTable from 'next/dist/compiled/text-table' diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index c9dc34c6ecc43..37135f0f6b8c5 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -3,6 +3,7 @@ import './require-hook' import './node-polyfill-fetch' import './node-polyfill-form' import './node-polyfill-web-streams' +import './node-polyfill-crypto' import type { TLSSocket } from 'tls' import type { Route, RouterOptions } from './router' diff --git a/packages/next/src/server/next.ts b/packages/next/src/server/next.ts index af997f4f227f6..867999a701714 100644 --- a/packages/next/src/server/next.ts +++ b/packages/next/src/server/next.ts @@ -5,6 +5,7 @@ import type { NextConfigComplete } from './config-shared' import './require-hook' import './node-polyfill-fetch' +import './node-polyfill-crypto' import { default as Server } from './next-server' import * as log from '../build/output/log' import loadConfig from './config' diff --git a/packages/next/src/server/node-polyfill-crypto.ts b/packages/next/src/server/node-polyfill-crypto.ts new file mode 100644 index 0000000000000..204f165872fee --- /dev/null +++ b/packages/next/src/server/node-polyfill-crypto.ts @@ -0,0 +1,12 @@ +// Polyfill crypto() in the Node.js environment + +if (!(global as any).crypto) { + function getCryptoImpl() { + return require('node:crypto').webcrypto + } + Object.defineProperty(global, 'crypto', { + get() { + return getCryptoImpl() + }, + }) +} diff --git a/test/e2e/app-dir/crypto-globally-available/app/handler/route.ts b/test/e2e/app-dir/crypto-globally-available/app/handler/route.ts new file mode 100644 index 0000000000000..d9e7a3fbea0e4 --- /dev/null +++ b/test/e2e/app-dir/crypto-globally-available/app/handler/route.ts @@ -0,0 +1,9 @@ +export function GET() { + return new Response( + typeof globalThis.crypto === 'object' + ? 'crypto is available' + : 'crypto is not available' + ) +} + +export const runtime = 'nodejs' diff --git a/test/e2e/app-dir/crypto-globally-available/app/layout.tsx b/test/e2e/app-dir/crypto-globally-available/app/layout.tsx new file mode 100644 index 0000000000000..e7077399c03ce --- /dev/null +++ b/test/e2e/app-dir/crypto-globally-available/app/layout.tsx @@ -0,0 +1,7 @@ +export default function Root({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ) +} diff --git a/test/e2e/app-dir/crypto-globally-available/app/page.tsx b/test/e2e/app-dir/crypto-globally-available/app/page.tsx new file mode 100644 index 0000000000000..2aba2a95f19d9 --- /dev/null +++ b/test/e2e/app-dir/crypto-globally-available/app/page.tsx @@ -0,0 +1,11 @@ +export default function Page() { + return ( +

+ {typeof globalThis.crypto === 'object' + ? 'crypto is available' + : 'crypto is not available'} +

+ ) +} + +export const runtime = 'nodejs' diff --git a/test/e2e/app-dir/crypto-globally-available/crypto-globally-available.test.ts b/test/e2e/app-dir/crypto-globally-available/crypto-globally-available.test.ts new file mode 100644 index 0000000000000..007c61273676d --- /dev/null +++ b/test/e2e/app-dir/crypto-globally-available/crypto-globally-available.test.ts @@ -0,0 +1,22 @@ +import { createNextDescribe } from 'e2e-utils' + +createNextDescribe( + 'Web Crypto API is available globally', + { + files: __dirname, + }, + ({ next }) => { + // Recommended for tests that need a full browser + it('should be available in Server Components', async () => { + const browser = await next.browser('/') + expect(await browser.elementByCss('p').text()).toBe('crypto is available') + }) + + // In case you need to test the response object + it('should be available in Route Handlers', async () => { + const res = await next.fetch('/handler') + const html = await res.text() + expect(html).toContain('crypto is available') + }) + } +) diff --git a/test/e2e/app-dir/crypto-globally-available/next.config.js b/test/e2e/app-dir/crypto-globally-available/next.config.js new file mode 100644 index 0000000000000..bf49894afd400 --- /dev/null +++ b/test/e2e/app-dir/crypto-globally-available/next.config.js @@ -0,0 +1,8 @@ +/** + * @type {import('next').NextConfig} + */ +const nextConfig = { + experimental: { appDir: true }, +} + +module.exports = nextConfig