Hello React - server and clientside rendering
Contents
In this chapter you will be introduced to how XP controllers may use TSX/JSX templates for both server-side and client-side rendering
Basic usage
The following files are involved in this example, each file will be described in details below
/src/main/resources/
controllers/hello-react.ts
react4xp/entries/hello-react.tsx
site/site.xml
React template
Start off by adding the following React component (tsx) file to your project:
import React from 'react';
function HelloReact({displayName}) { (1)
let bottleCount = 99;
function dropBottle() {
bottleCount--;
console.log(bottleCount, 'bottles of beer on the wall.');
(document.getElementById('counter') as HTMLElement).innerText = bottleCount.toString();
};
return (
<div onClick={dropBottle}>
<h1>Hello {displayName}!</h1>
<button><span id="counter">{bottleCount}</span> bottles of beer</button>
</div>
);
}
export default (props) => <HelloReact {...props}/>; (2)
1 | React component that will render a heading, and a button that will count down when clicked. |
2 | The export default line is required, it may take a props object and must return a react component. More on this later |
This component will be rendered server-side, and then hydrated
so it will also work on the client-side, for an amusing beer bottle countdown. The actual updating step is done with vanilla JS instead of actual react, just to keep things as simple as possible. We’ll look at stateful/dynamic components later.
XP controller
To trigger the rendering, we’ll use a regular Enonic controller (ts). Add the following file to your app:
import {render} from '/lib/enonic/react4xp';
import {getContent} from '/lib/xp/portal';
export function get(request) { (1)
const {displayName} = getContent();
const props = {displayName};
const react4xpId = 'react4xpApp'; (2)
const component = 'hello-react'; (3)
return render( (4)
component,
props,
request,
{
id: react4xpId,
body: (5)
`<!DOCTYPE html><html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body class="xp-page">
<div id="${react4xpId}"></div>
</body>
</html>`,
ssr: true, (6)
hydrate: true, (7)
}
);
}
1 | Standard XP controller implemeting the HTTP get function. |
2 | ID for the react app (TODO) |
3 | React templates placed in src/resources/main/react4xp/entries may simply be referenced by their name, using a concept we call jsxPath). |
4 | The output of React4XP render() is a regular XP response object. |
5 | Bootstrap markup for the React application |
6 | ssr true means the component will be Server Side Rendered |
7 | hydrate true means the component will be activated on client-side |
Controller mapping
To trigger the controller, we’ll add a simple mapping to site.xml
- this will cause the controller to execute whenever someone previews a site where the application is deployed:
<?xml version="1.0" encoding="UTF-8"?>
<site>
<form/>
<mappings>
<mapping controller="/controllers/hello-react.js">
<match>type:'portal:site'</match>
</mapping>
<!-- Added during the guillotine tutorial -->
<mapping controller="/controllers/previewMovie.js" order="50">
<match>type:'com.enonic.app.samples_react4xp:movie'</match>
</mapping>
<!-- Also added during the guillotine tutorial -->
<mapping controller="/headless/guillotineApi.js" order="50">
<pattern>/api/headless</pattern>
</mapping>
</mappings>
</site>
SSR vs client side gotchas When using isomorphic JavaScript, some functionality may only be working on server-side, and some only on client side, like In the example above,
Of course, this also applies to imported packages / nested components and so on. Importing them is usually safe, but consider wrapping the areas where they’re called/used. |
Preview
-
Your changes should automatically be picked up by XP, to manually deploy use (
enonic project deploy
in a terminal from your project root) -
Visit
localhost:8080/admin
. Log in to XP and open Content Studio. -
Create a new content project, then create a new Site.
-
Add your React4xp app ("starter-react4xp"?) to the site
After saving, you should now see something like this:
In order to support a visual editing experience, components will not be hydrated.
-
Click Preview for a live view of the page.
Clicking anywhere on the text should trigger the
dropBottle
function from hello-react.tsx, modify the DOM and output a message in the browser console. Look at those bottles go!
You might get serverside runtime errors in the react components that you write. The React4xp rendering engine will dump an error message and some suspected code in the server log - but it’s not always easy to make sense of those. Switching over to clientside rendering (SSR false, Hydrate true) for the particular react component will often give you a better/sourcemapped error message in the browser console, making your debugging life easier. |
The output
Back to the actual rendering of HTML. Looking at the page source code, you should see the following:
INFO: The links have been shortened for better readability
<!DOCTYPE html><html lang="en">
<head>
<meta charset="UTF-8">
<script defer src=".../react4xp/globals.js"></script> (1)
<script defer src=".../react4xp/runtime.js"></script> (2)
<script defer src=".../react4xp/hello-react.js"></script> (3)
<script defer src=".../react4xp/client-QZN5J5VC.global.js"></script> (4)
<script data-react4xp-app-name="com.enonic.app.samples_react4xp" data-react4xp-ref="react4xpApp" type="application/json">{"command":"hydrate","devMode":false,"hasRegions":0,"isPage":1,"jsxPath":"site/pages/hello-react/hello-react","props":{}}</script> (5)
</head>
<body class="xp-page">
<div id="react4xpApp">
<div data-reactroot=""> (6)
<h1>Hello Sitename</h1>
<p><span id="counter">99</span> bottles of beer on the wall.</p>
</div>
</div>
<script defer src=".../react4xp/executor-BL4RRDZO.js"></script> (7)
</body>
</html>
We can see this whole output is actually the body
HTML string we passed into the render
call in the controller - but a lot has been inserted. Most importantly, three assets are loaded into the client.
1 | globals.<contenthash>.js is react and react-dom bundled together. For better security, they are served from XP instead of a random CDN. |
2 | runtime.js containst chunked boilerplate code to run entries. |
3 | hello-react is the compiled version of hello-react.tsx |
4 | client-QZN5J5VC.global.js is the client-wrapper. TODO: What does it do? |
5 | Properties and props needed for the client-side rendering to work properly (pun intended) |
6 | The <div id="react4xpApp"> target container, now filled with a server-side rendering of the react component |
7 | The script that actually runs hydrate with the props on the clientside. |
Most of these static assets are content-hashed, and optimized for client-side caching - thus running a CDN in front of XP will speed up the delivery of these. |
So, we are rolling, next up lets look into using pages and editorial components.