Repository navigation
component v1.2.0
Minor Changes
-
Render under a nonce-based Content Security Policy with the
nonceoption onrenderToStream()andrenderToString(). Generated import maps, module preloads, and CSS mixin styles receive the nonce; explicitly authored<ImportMap>nonces are preserved. (#12098)+let nonce = crypto.getRandomValues(new Uint8Array(16)).toBase64() + let stream = renderToStream(<App />, { frameSrc: request.url, signal: request.signal, + nonce, })
Client navigation preserves the document nonce across frame and island updates. See CSP integration for nested rendering and custom resolver setup.
Patch Changes
-
Fix plain data in
clientEntryprops being treated as JSX elements, frames, fragments, orcss()mixins during hydration when it contains keys Remix uses for serialization, such as$rmxor$rmxMixin. These objects now reach the client entry unchanged at any nesting level, including the top-level props object. Own__proto__properties are kept as data instead of changing the object's prototype. (#12151)Only elements created with JSX or
createElement()now render. Objects built by hand or parsed from JSON with the same shape as an element, such as{ $rmx: true, type: 'div', props: { innerHTML } }, are no longer rendered as elements and throwInvalid child nodewhen passed as children. This keeps data from a database or API from injecting markup. Spread copies of elements, such as{ ...element }, still render. -
Apps that render with
createRoot()orcreateRangeRoot()and never use<Frame>no longer load the frame runtime, which includes frame streaming, DOM diffing for frame reloads, and import map handling. Their component runtime is about 40% smaller. The frame runtime still loads when an app importsFrameor callsrun(), and frames behave as before. (#12161) -
Fix
css()applying the wrong styles when two style objects contain the same properties in a different order. Generated classes ignored key order, socss({ marginTop: '5px', margin: 0 })could reuse the class generated forcss({ margin: 0, marginTop: '5px' })and render a 5px top margin instead of 0. Each order now gets its own class with its declarations in the order written. (#12176)Generated classes also no longer depend on the default locale. Style keys were sorted with locale-aware comparison, so a browser using a locale like Estonian, which sorts
zbeforet, could generate a different class than the server for styles like{ top: 0, zIndex: 1 }and insert a duplicate rule at a different position in the cascade during hydration.Generated
rmxc-*class names change with this release, so update any tests or selectors that hard-code them. -
css()is faster. In a benchmark of 200 product cards with eightcss()styles each, rerenders take about 50% less time, first renders about 20% less, and server rendering about 27% less. (#12176)- A style that was already generated is looked up by its contents instead of being hashed again.
- Elements no longer create
beforeUpdateandcommitevents when none of their mixins listen for them. This also speeds up other mixins that don't listen, such asattrs()andlink().
-
Preserve literal doctype-looking text in scripts, styles, textareas, attributes, and comments when parsing frame HTML in the browser, including full-document reloads and streamed frame templates. (#12107)
-
Fix flashes of unstyled content during client navigation when page-specific
<head>links are added or removed ahead of keyed stylesheets. DOM diffs now move only the elements whose order changed, so keyed elements that keep their order, such as<link data-rmx-key rel="stylesheet">or keyed iframes, are no longer detached and re-inserted. Sibling reordering respectsdata-rmx-preserve-dom, keeping preserved elements connected while moving other siblings around them. Unkeyed<link>elements are also no longer repurposed for a link with a differentrelorhref, which could turn amodulepreloadinto analternatelink or swap the stylesheet an existing link loads (#12064). (#12128) -
Keep keyed
<head>stylesheets in place on the first client navigation of a server-rendered document. The server renders the managed import map as the first child of<head>, and the head diff used to move every other head node in front of it. Moving a<link rel="stylesheet">made the browser drop and reload it, which caused a flash of unstyled content. (#12127) -
Fix Safari restoring a shorter scroll position than the visitor left on a back or forward navigation. Safari restored the position against the page as it was before the restored content rendered, so a page taller than the one the visitor came back from scrolled only as far as that one allowed. (#12162)
-
Fix hydration of
clientEntrycomponents whose props contain JSX elements styled withcss(). Hydration used to fail withFramework invariant: Invalid mix prop, and the client entry never became interactive.css()mixins in client entry props, including arrays likemix={[css(base), css(active)]}, now reach the browser with their styles, and content from those props that the server didn't render is styled when the client entry renders it later. Styles withundefinedproperties keep the same generated classes and cascade order through hydration. (#12125)Other mixins, such as
on(),ref(), and custom mixins fromcreateMixin(), are not supported in serialized client entry props. When a client entry renders an element from its props that uses one, it now reports an error explaining that onlycss()mixins can be serialized and that those elements should be rendered inside the client entry instead, rather than failing withInvalid mix prop. -
The browser runtime is about 10% smaller when minified, with no behavior changes. Duplicated logic is merged, dead code is removed, and small internal modules are folded into their owners so the asset server serves fewer modules. (#12192)
-
Boolean
aria-*anddata-*values now render the same way on the server and in the browser. Server rendering previously dropped them:<a aria-current={true} aria-hidden={false}>rendered as<a aria-current>, which assistive technology reads asaria-current="false". They now render as"true"and"false".data-rmx-documentanddata-rmx-preserve-domare the exception:truerenders an empty attribute andfalseomits it. Previously the browser renderedfalseas"false", which still turned document navigation or DOM preservation on. (#12152)MathML boolean attributes such as
displaystyle,stretchy, andlargeopnow render as"true"and"false"on the server and in the browser, preserving their layout behavior during hydration and updates. -
Fix how
css()and thestyleprop serialize property names and numeric values. Previouslycss({ WebkitLineClamp: 4 })rendered the invalid-webkit-line-clamp: 4px, and thewebkitLineClampkey suggested by the DOM types renderedwebkit-line-clamp, which browsers ignore. (#12121)- Numeric values stay unitless wherever the supported CSS grammar accepts a bare number, including vendor-prefixed forms, mask borders, hyphenation limits,
shapeImageThreshold,readingOrder, and numeric shorthands such asanimationandborderImage. The list covers current and draft grammars as well as legacy properties; browser support still depends on the property. - Vendor-prefixed camelCase keys get their leading dash, so
webkitLineClampandmsFlexrender as-webkit-line-clampand-ms-flex. - Custom property names keep their casing, so
'--gapSize'renders as--gapSizeinstead of--gap-size. css()dropsNaNandInfinityvalues instead of renderingNaNpxorInfinitypx, matching thestyleprop.
Behavior change: these fixes change some existing output. Apps that relied on the old output need to update:
-
Custom property names written in camelCase are no longer converted to kebab case. If a stylesheet or
var()reads the kebab-case name, write the property the way you reference it:-css({ '--gapSize': '8px', gap: 'var(--gap-size)' }) +css({ '--gap-size': '8px', gap: 'var(--gap-size)' })
-
css()addspxto plain numbers, but it also added it toflex, where a number means a grow factor.flex: 2rendered asflex: 2px, which grows likeflex: 1with a2pxbasis, so numbers other than1had no effect on growth.flexnumbers are now grow factors, andflex: 1renders1 1 0%instead of1 1 1px. -
tabSize: 4now means four spaces instead of4px. Numbers forborderImageWidth,borderImageOutset,maskBorderWidth, andmaskBorderOutsetmultiply the border width instead of being pixels; the WebKit mask-box-image aliases follow the same convention. -
Numeric shorthands select their unitless meaning:
animation: 2sets two iterations, andborderImage: 2/maskBorder: 2set the slice. Use strings with units for durations and angles, such as'200ms'and'45deg'. -
To request pixels explicitly, use a string:
-css({ tabSize: 4, borderImageWidth: 2 }) +css({ tabSize: '4px', borderImageWidth: '2px' })
- Numeric values stay unitless wherever the supported CSS grammar accepts a bare number, including vendor-prefixed forms, mask borders, hyphenation limits,