diff --git a/packages/next/src/cli/next-dev-args.ts b/packages/next/src/cli/next-dev-args.ts index 436685cdf81f6..7d89d04b41940 100644 --- a/packages/next/src/cli/next-dev-args.ts +++ b/packages/next/src/cli/next-dev-args.ts @@ -9,6 +9,7 @@ export const validArgs: arg.Spec = { '--experimental-https': Boolean, '--experimental-https-key': String, '--experimental-https-cert': String, + '--experimental-https-ca': String, '--experimental-test-proxy': Boolean, '--experimental-upload-trace': String, diff --git a/packages/next/src/cli/next-dev.ts b/packages/next/src/cli/next-dev.ts index 96fb97384cb44..98f053a7f617e 100644 --- a/packages/next/src/cli/next-dev.ts +++ b/packages/next/src/cli/next-dev.ts @@ -15,7 +15,10 @@ import loadConfig, { getEnabledExperimentalFeatures } from '../server/config' import { findPagesDir } from '../lib/find-pages-dir' import { fileExists, FileType } from '../lib/file-exists' import { getNpxCommand } from '../lib/helpers/get-npx-command' -import { createSelfSignedCertificate } from '../lib/mkcert' +import { + SelfSignedCertificate, + createSelfSignedCertificate, +} from '../lib/mkcert' import uploadTrace from '../trace/upload-trace' import { initialEnv, loadEnvConfig } from '@next/env' import { trace } from '../trace' @@ -249,6 +252,7 @@ const nextDev: CliCommand = async (args) => { ...((initialEnv || process.env) as typeof process.env), TURBOPACK: process.env.TURBOPACK, NEXT_PRIVATE_WORKER: '1', + NODE_EXTRA_CA_CERTS: options.selfSignedCertificate?.rootCA, }, }) @@ -282,15 +286,17 @@ const nextDev: CliCommand = async (args) => { 'Self-signed certificates are currently an experimental feature, use at your own risk.' ) - let certificate: { key: string; cert: string } | undefined + let certificate: SelfSignedCertificate | undefined + + const key = args['--experimental-https-key'] + const cert = args['--experimental-https-cert'] + const rootCA = args['--experimental-https-ca'] - if ( - args['--experimental-https-key'] && - args['--experimental-https-cert'] - ) { + if (key && cert) { certificate = { - key: path.resolve(args['--experimental-https-key']), - cert: path.resolve(args['--experimental-https-cert']), + key: path.resolve(key), + cert: path.resolve(cert), + rootCA: rootCA ? path.resolve(rootCA) : undefined, } } else { certificate = await createSelfSignedCertificate(host) diff --git a/packages/next/src/lib/mkcert.ts b/packages/next/src/lib/mkcert.ts index 53f6b34fa3b81..d1200a1787c0e 100644 --- a/packages/next/src/lib/mkcert.ts +++ b/packages/next/src/lib/mkcert.ts @@ -10,6 +10,12 @@ const { fetch } = require('next/dist/compiled/undici') as { const MKCERT_VERSION = 'v1.4.4' +export interface SelfSignedCertificate { + key: string + cert: string + rootCA?: string +} + function getBinaryName() { const platform = process.platform const arch = process.arch === 'x64' ? 'amd64' : process.arch @@ -66,7 +72,7 @@ async function downloadBinary() { export async function createSelfSignedCertificate( host?: string, certDir: string = 'certificates' -) { +): Promise { try { const binaryPath = await downloadBinary() if (!binaryPath) throw new Error('missing mkcert binary') @@ -98,7 +104,7 @@ export async function createSelfSignedCertificate( { stdio: 'ignore' } ) - const caLocation = execSync(`${binaryPath} -CAROOT`).toString() + const caLocation = execSync(`${binaryPath} -CAROOT`).toString().trim() if (!fs.existsSync(keyPath) || !fs.existsSync(certPath)) { throw new Error('Certificate files not found') @@ -121,6 +127,7 @@ export async function createSelfSignedCertificate( return { key: keyPath, cert: certPath, + rootCA: `${caLocation}/rootCA.pem`, } } catch (err) { Log.error( diff --git a/packages/next/src/server/lib/start-server.ts b/packages/next/src/server/lib/start-server.ts index 88de4cf513520..afa14c4dadb9b 100644 --- a/packages/next/src/server/lib/start-server.ts +++ b/packages/next/src/server/lib/start-server.ts @@ -3,6 +3,7 @@ import '../node-polyfill-fetch' import '../require-hook' import type { IncomingMessage, ServerResponse } from 'http' +import type { SelfSignedCertificate } from '../../lib/mkcert' import fs from 'fs' import path from 'path' @@ -38,10 +39,7 @@ export interface StartServerOptions { envInfo?: string[] expFeatureInfo?: string[] // this is dev-server only - selfSignedCertificate?: { - key: string - cert: string - } + selfSignedCertificate?: SelfSignedCertificate isExperimentalTestProxy?: boolean } diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index 0271395ba1d13..e57d0fd189fa8 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -530,9 +530,9 @@ export default class NextNodeServer extends BaseServer { if (this.isRenderWorker) { const invokeRes = await invokeRequest( - `http://${this.fetchHostname || 'localhost'}:${this.port}${ - newReq.url || '' - }`, + `${getRequestMeta(req, '_protocol')}://${ + this.fetchHostname || 'localhost' + }:${this.port}${newReq.url || ''}`, { method: newReq.method || 'GET', headers: newReq.headers, diff --git a/test/e2e/app-dir/next-image/next-image-https.test.ts b/test/e2e/app-dir/next-image/next-image-https.test.ts new file mode 100644 index 0000000000000..fd97a867d8cb0 --- /dev/null +++ b/test/e2e/app-dir/next-image/next-image-https.test.ts @@ -0,0 +1,46 @@ +import { createNextDescribe } from '../../../lib/e2e-utils' + +createNextDescribe( + 'app dir next-image (with https)', + { + files: __dirname, + skipDeployment: true, + startCommand: `yarn next dev --experimental-https`, + }, + ({ next }) => { + if (!process.env.CI) { + console.warn('only runs on CI as it requires administrator privileges') + it('only runs on CI as it requires administrator privileges', () => {}) + return + } + + it('loads images without any errors', async () => { + let failCount = 0 + const browser = await next.browser('/', { + beforePageLoad(page) { + page.on('response', (response) => { + const url = response.url() + if (!url.includes('/_next/image')) return + + const status = response.status() + + console.log(`URL: ${url} Status: ${status}`) + + if (!response.ok()) { + console.log(`Request failed: ${url}`) + failCount++ + } + }) + }, + }) + const image = browser.elementByCss('#app-page') + const src = await image.getAttribute('src') + + expect(src).toContain( + '/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=90' + ) + + expect(failCount).toBe(0) + }) + } +)