Description
SSR v2 (and v1, for that matter) makes use of getReadOnlyProxy
from observable-membrane
to ensure that child components do not try to mutate their parent components' props.
@api props
connectedCallback() {
this.props.foo = 'bar' // throws in SSR
}
Note
Note this is not exactly how engine-dom
works – in that world, a child trying to modify its parent's props ends up doing a no-op. (This is why component authors often do JSON.parse(JSON.stringify())
to clone their props.) The original intention was to avoid two-way data flow – e.g. a parent rendering <div>{props.foo}</div>
, where props.foo
could have been mutated by a child.
In SSR, the read-only proxy is a nice guardrail. However, it comes with a fairly substantial perf cost: ~44% in one benchmark (PoC: ab3e2f9):

To be fair: there is less risk in SSR of a child truly mutating its parent, since rendering is one-time and top-down. There is still, however, potential for mischief if a child ends up modifying its siblings (e.g. because the parent passes in a prop and that prop is shared among siblings).
Thoughts:
- Should we put this proxying behind a flag like
process.env.NODE_ENV !== 'production'
? - Should we just remove it entirely?
The downside of removing it entirely is that we would potentially be looser on the server than on the client. This could actually matter for SSR-only cases, where we don't have the extra validation of running components client-side (where children-mutating-parents is already disallowed). But maybe it's still worth it?
Activity