diff --git a/test/e2e/app-dir/app/app/bootstrap/[[...*]]/ClientComponent.js b/test/e2e/app-dir/app/app/bootstrap/[[...*]]/ClientComponent.js
new file mode 100644
index 0000000000000..6cbcf95b770f1
--- /dev/null
+++ b/test/e2e/app-dir/app/app/bootstrap/[[...*]]/ClientComponent.js
@@ -0,0 +1,11 @@
+'use client'
+
+import { useState, useEffect } from 'react'
+
+export function ClientComponent() {
+ const [val, setVal] = useState('initial')
+ useEffect(() => {
+ setVal('[[updated]]')
+ }, [])
+ return {val}
+}
diff --git a/test/e2e/app-dir/app/app/bootstrap/[[...*]]/page.js b/test/e2e/app-dir/app/app/bootstrap/[[...*]]/page.js
new file mode 100644
index 0000000000000..4e53effb26f73
--- /dev/null
+++ b/test/e2e/app-dir/app/app/bootstrap/[[...*]]/page.js
@@ -0,0 +1,15 @@
+import { ClientComponent } from './ClientComponent'
+
+export default async function Page() {
+ return (
+ <>
+
+ This fixture is to assert where the bootstrap scripts and other required
+ scripts emit during SSR
+
+
+
+
+ >
+ )
+}
diff --git a/test/e2e/app-dir/app/app/bootstrap/page.js b/test/e2e/app-dir/app/app/bootstrap/page.js
deleted file mode 100644
index d83b335ed8698..0000000000000
--- a/test/e2e/app-dir/app/app/bootstrap/page.js
+++ /dev/null
@@ -1,8 +0,0 @@
-export default async function Page() {
- return (
-
- This fixture is to assert where the bootstrap scripts and other required
- scripts emit during SSR
-
- )
-}
diff --git a/test/e2e/app-dir/app/index.test.ts b/test/e2e/app-dir/app/index.test.ts
index c0b21cbe03ab9..0c5bae1ab70bb 100644
--- a/test/e2e/app-dir/app/index.test.ts
+++ b/test/e2e/app-dir/app/index.test.ts
@@ -1373,8 +1373,9 @@ createNextDescribe(
// Find all the script tags without src attributes.
const elements = $('script[src]')
- // Expect there to be at least 1 script tag without a src attribute.
- expect(elements.length).toBeGreaterThan(0)
+ // Expect there to be at least 2 script tag with a src attribute.
+ // The main chunk and the webpack runtime.
+ expect(elements.length).toBeGreaterThan(1)
// Expect all inline scripts to have the nonce value.
elements.each((i, el) => {
@@ -1899,6 +1900,39 @@ createNextDescribe(
expect($('script[async]').length).toBeGreaterThan(1)
expect($('body').find('script[async]').length).toBe(1)
})
+
+ if (!isDev) {
+ it('should successfully bootstrap even when using CSP', async () => {
+ // This path has a nonce applied in middleware
+ const browser = await next.browser('/bootstrap/with-nonce')
+ const response = await next.fetch('/bootstrap/with-nonce')
+ // We expect this page to response with CSP headers requiring a nonce for scripts
+ expect(response.headers.get('content-security-policy')).toContain(
+ "script-src 'nonce"
+ )
+ // We expect to find the updated text which demonstrates our app
+ // was able to bootstrap successfully (scripts run)
+ expect(
+ await browser.eval('document.getElementById("val").textContent')
+ ).toBe('[[updated]]')
+ })
+ } else {
+ it('should fail to bootstrap when using CSP in Dev due to eval', async () => {
+ // This test is here to ensure that we don't accidentally turn CSP off
+ // for the prod version.
+ const browser = await next.browser('/bootstrap/with-nonce')
+ const response = await next.fetch('/bootstrap/with-nonce')
+ // We expect this page to response with CSP headers requiring a nonce for scripts
+ expect(response.headers.get('content-security-policy')).toContain(
+ "script-src 'nonce"
+ )
+ // We expect our app to fail to bootstrap due to invalid eval use in Dev.
+ // We assert the html is in it's SSR'd state.
+ expect(
+ await browser.eval('document.getElementById("val").textContent')
+ ).toBe('initial')
+ })
+ }
})
}
)
diff --git a/test/e2e/app-dir/app/middleware.js b/test/e2e/app-dir/app/middleware.js
index efd1bf33146e5..2f7830a92a708 100644
--- a/test/e2e/app-dir/app/middleware.js
+++ b/test/e2e/app-dir/app/middleware.js
@@ -3,9 +3,9 @@ import { NextResponse } from 'next/server'
/**
* @param {import('next/server').NextRequest} request
- * @returns {NextResponse | undefined}
+ * @returns {Promise}
*/
-export function middleware(request) {
+export async function middleware(request) {
if (request.nextUrl.pathname === '/searchparams-normalization-bug') {
const headers = new Headers(request.headers)
headers.set('test', request.nextUrl.searchParams.get('val') || '')
@@ -25,6 +25,17 @@ export function middleware(request) {
return NextResponse.rewrite(new URL('/dashboard', request.url))
}
+ // In dev this route will fail to bootstrap because webpack uses eval which is dissallowed by
+ // this policy. In production this route will work
+ if (request.nextUrl.pathname === '/bootstrap/with-nonce') {
+ const nonce = crypto.randomUUID()
+ return NextResponse.next({
+ headers: {
+ 'Content-Security-Policy': `script-src 'nonce-${nonce}' 'strict-dynamic';`,
+ },
+ })
+ }
+
if (request.nextUrl.pathname.startsWith('/internal/test')) {
const method = request.nextUrl.pathname.endsWith('rewrite')
? 'rewrite'