Fix before interactive incorrectly render css (#81146)
Fixing a bug
- Related issues linked using fixes #81130
- Tests added. See:
https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md
What?
This PR fixes a bug where Next.js Script components with
strategy="beforeInteractive" incorrectly render classname attribute
instead of the HTML standard class attribute.
Why?
The beforeInteractive script strategy in App Router bypassed the
existing setAttributesFromProps utility function that properly maps
React prop names to DOM attribute names. This caused className props to
be directly set as classname attributes in the HTML, which is
non-standard and breaks CSS styling.
Before (broken):
```
<script classname="example-class" id="example">...</script>
```
After (fixed):
```
<script class="example-class" id="example">...</script>
```
How?
1. Identified the root cause: In
/packages/next/src/client/app-bootstrap.ts, the loadScriptsInSequence
function manually set attributes using a simple loop that didn't handle
DOM attribute name mapping.
2. Applied the fix:
- Imported the existing setAttributesFromProps utility function
- Replaced the manual attribute setting loop with the proper utility
function call
- This ensures className → class mapping works correctly
3. Added comprehensive tests: Created e2e tests in
/test/e2e/app-dir/script-before-interactive/ that verify:
- Single scripts render with correct class attribute
- Multiple scripts work correctly
- Script execution remains unaffected
- Scripts are properly placed in document head
Code changes:
```
// Before (buggy)
if (props) {
for (const key in props) {
if (key !== 'children') {
el.setAttribute(key, props[key]) // ❌ className becomes classname
}
}
}
```
// After (fixed)
```
if (props) {
setAttributesFromProps(el, props) // ✅ className becomes class
}
```
Fixes #81130
---------
Co-authored-by: JJ Kasper <jj@jjsweb.site>