Pass props and keeping the DOM neat in a React Isomorphic App

by Roland Castillo - 14 Sep 2016

Since we adopted React here at Zalando, I have always been on the look out to make the most out of it. While working with server side rendering, I noticed the way we were passing our props down the pipeline wasn't as clean as I liked it to be. So I thought: "There must be another way".

The usual and most common way of passing props from the server side rendering to the client in a React Isomorphic app is through data attributes, which is a very straightforward and easy strategy. This technique isn’t without its downsides, as by doing so we end up with a bloated DOM. Something like the following:

null

Ugly, right? You can barely see the actual HTML – you only see data, and even though they’re also important to inspect, there are better tools for that job (i.e. Redux DevTools). When you debug your HTML code with the inspector, you want to see HTML. By bloating your DOM, you only make it harder to browse the generated HTML.

So, why not have a leaner DOM that keeps this lengthy data out of sight? React doesn't really care how you pass your props, as long as they are accessible in the document. Let's wrap our props as CDATA instead of data attributes.

First of all, on your server side rendering file, instead of using data attributes:

export default function render(state) {
const props = {model: state};
const dataProps = escape(JSON.stringify(props));
return (
`<div data-props='${dataProps}'>` +
ReactDOM.renderToString(<div><Router {...props}/></div>) +
`</div>`
);
}

Use CDATA:

export default function render(state) {
const props = {model: state};
const dataProps = JSON.stringify(props);
return (
`<div>` +
`<script id='app-props' type='application/json'>` +
`<![CDATA[${dataProps}]]>` +
`</script>` +
`<div>` + ReactDOM.renderToString(<div><MyComponent {...props}/></div>) + `</div>` +
`</div>`
);
}

Then process them on your client side file as follows:

let props = document.getElementById('app-props').textContent;
props = props.replace("<![CDATA[", "").replace("]]>", "");
const data = JSON.parse(props);
ReactDOM.render(<MyComponent {...data}/>, yourDomContainerNode);

You could use either innerText or textContent. Just keep in mind the browser support. innertText has good Internet Explorer support but it is only supported from Firefox 45 onwards. On the other hand, textContent has very good Firefox support but is only supported from IE9 onwards.

The result? A nicer DOM in the inspector:

null

Here are some of the advantages of using this strategy:

  • Decrease document size: It is not necessary to escape the data since you passed your props to the client as CDATA. Depending on the size of your data, you save tremendous amounts of bytes by not escaping special characters.
  • Clean DOM: The output in the browser inspector doesn't contain lengthy data.
  • Easier DOM inspection: The inspection of your DOM becomes more efficient since you don't have to scroll through all those lengthy data attributes.
  • And works with Flux!

Do not hesitate to drop me a line at roland.castillo@zalando.de in case you have comments, suggestions, feedback, etc.

Similar blog posts