diff --git a/packages/next/src/client/app-bootstrap.ts b/packages/next/src/client/app-bootstrap.ts
index f422b50c5e695..1811f9fa115ca 100644
--- a/packages/next/src/client/app-bootstrap.ts
+++ b/packages/next/src/client/app-bootstrap.ts
@@ -5,6 +5,8 @@
* - next/script with `beforeInteractive` strategy
*/
+import { setAttributesFromProps } from './set-attributes-from-props'
+
const version = process.env.__NEXT_VERSION
window.next = {
@@ -27,11 +29,7 @@ function loadScriptsInSequence(
const el = document.createElement('script')
if (props) {
- for (const key in props) {
- if (key !== 'children') {
- el.setAttribute(key, props[key])
- }
- }
+ setAttributesFromProps(el, props)
}
if (src) {
diff --git a/test/e2e/app-dir/script-before-interactive/app/layout.tsx b/test/e2e/app-dir/script-before-interactive/app/layout.tsx
new file mode 100644
index 0000000000000..dbce4ea8e3aeb
--- /dev/null
+++ b/test/e2e/app-dir/script-before-interactive/app/layout.tsx
@@ -0,0 +1,11 @@
+export default function RootLayout({
+ children,
+}: {
+ children: React.ReactNode
+}) {
+ return (
+
+
{children}
+
+ )
+}
diff --git a/test/e2e/app-dir/script-before-interactive/app/multiple/page.tsx b/test/e2e/app-dir/script-before-interactive/app/multiple/page.tsx
new file mode 100644
index 0000000000000..b7d081ca9c9ea
--- /dev/null
+++ b/test/e2e/app-dir/script-before-interactive/app/multiple/page.tsx
@@ -0,0 +1,34 @@
+import Script from 'next/script'
+
+export default function MultiplePage() {
+ return (
+
+
Multiple beforeInteractive Scripts Test
+
+
+
+ This page tests multiple beforeInteractive scripts with CSS classes.
+
+
+ )
+}
diff --git a/test/e2e/app-dir/script-before-interactive/app/page.tsx b/test/e2e/app-dir/script-before-interactive/app/page.tsx
new file mode 100644
index 0000000000000..f155719f8bb68
--- /dev/null
+++ b/test/e2e/app-dir/script-before-interactive/app/page.tsx
@@ -0,0 +1,23 @@
+import Script from 'next/script'
+
+export default function Page() {
+ return (
+
+
Script beforeInteractive Test
+
+
+ This page tests the beforeInteractive script strategy with CSS classes.
+
+
+ )
+}
diff --git a/test/e2e/app-dir/script-before-interactive/next.config.js b/test/e2e/app-dir/script-before-interactive/next.config.js
new file mode 100644
index 0000000000000..767719fc4fba5
--- /dev/null
+++ b/test/e2e/app-dir/script-before-interactive/next.config.js
@@ -0,0 +1,4 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {}
+
+module.exports = nextConfig
diff --git a/test/e2e/app-dir/script-before-interactive/script-before-interactive.test.ts b/test/e2e/app-dir/script-before-interactive/script-before-interactive.test.ts
new file mode 100644
index 0000000000000..fdab98c38c336
--- /dev/null
+++ b/test/e2e/app-dir/script-before-interactive/script-before-interactive.test.ts
@@ -0,0 +1,61 @@
+import { nextTestSetup } from 'e2e-utils'
+
+describe('Script component with beforeInteractive strategy CSS class rendering', () => {
+ const { next } = nextTestSetup({
+ files: __dirname,
+ })
+
+ it('should render script tag with correct class attribute instead of classname', async () => {
+ const browser = await next.browser('/')
+
+ // Wait for the page to fully load
+ await browser.waitForElementByCss('#example-script')
+
+ // Get the HTML content to check the actual rendered attributes
+ const html = await browser.eval(() => document.documentElement.innerHTML)
+
+ // Check that the script tag has 'class' attribute, not 'classname'
+ expect(html).toContain('class="example-class"')
+ expect(html).not.toContain('classname="example-class"')
+
+ // Also verify the script element directly
+ const scriptElement = await browser.elementByCss('#example-script')
+ const className = await scriptElement.getAttribute('class')
+
+ expect(className).toBe('example-class')
+ })
+
+ it('should execute beforeInteractive script correctly', async () => {
+ const browser = await next.browser('/')
+
+ // Check that the script executed by looking for its side effects
+ const hasExecuted = await browser.eval(() => {
+ return (window as any).beforeInteractiveExecuted === true
+ })
+
+ expect(hasExecuted).toBe(true)
+ })
+
+ it('should render script in document head with beforeInteractive strategy', async () => {
+ const browser = await next.browser('/')
+
+ // Check that the script is in the head section
+ const scriptInHead = await browser.eval(() => {
+ return document.head.querySelector('#example-script') !== null
+ })
+
+ expect(scriptInHead).toBe(true)
+ })
+
+ it('should render multiple beforeInteractive scripts with correct class attributes', async () => {
+ const browser = await next.browser('/multiple')
+
+ const html = await browser.eval(() => document.documentElement.innerHTML)
+
+ // Check that both scripts have correct class attributes
+ expect(html).toContain('class="first-script"')
+ expect(html).toContain('class="second-script"')
+ expect(html).not.toContain('classname="first-script"')
+ expect(html).not.toContain('classname="second-script"')
+ })
+})