Vous êtes sur la page 1sur 40

React SSR

Dvelopper et dployer une


webapp isomorphique avec React
Dvelopper et
dployer une 1 Introduction
webapp
isomorphique avec
React 2 Comment cela fonctionne ?

3 Le routing

4 La configuration

5 La donne

6 Code splitting / Lazy loading

7 CI et CD
1 Introduction
Quelques dfinitions :

Vocabulaire
SSR (server side rendering) : Le markup HTML dune page
est retourn par le serveur (NodeJS, java, PHP, ).

CSR (client side rendering) : Le markup HTML dune page est


gnr par le javascript ct client (ex: SPA Angular, SPA
React, ).

Application isomorphique : partage le mme code ct client


(navigateur) et ct serveur.


Raliser une application isomorphique avec JavaScript ?

Introduction
Cest envisageable depuis que :

Ct serveur, NodeJS permet dxcuter du javascript


(depuis 2010),

Ct client, le javascript permet dxecuter plus doprations


et plus vite (moteur V8 de Google, ).

Le langage JavaScript est devenu plus riche (ES6, ES2016,


) et Babel permet den profiter ds maintenant.

Aussi, ces dernires annes, la logique applicative est


progressivement passe du serveur au client sur un grand
nombre dapplications web (SPA, ).
Grce une application JS isomorphique :

Quelques Pas de duplication de code entre serveur et client dans


avantages deux langages diffrents. Maintenance facilite.

Site utilisable partiellement si le Javascript est dsactiv,

Les moteurs de recherche voient le contenu.


Rfrencement et indexation possibles.

Meilleure impression de performance (la page apparat


dj remplie, pas besoin de loader, ).

On conserve aussi les avantages dune SPA : pas de


chargement complet de page pendant la navigation,

Les tests unitaires concernant les vues et la logique


mtier sont communs au front et au back (Jest, Enzyme, ).
Le coverage peut tre optimal.
Client side rendering (CSR)

source : https://aerotwist.com/blog/when-everything-is-important-nothing-is/
Server side rendering (SSR)

source : https://aerotwist.com/blog/when-everything-is-important-nothing-is/
Quand raliser une web
app isomorphique plutt
quune SPA (single page
app) ?
2 Comment cela fonctionne ?
React
Comment cela React nest pas un framework MVC !
fonctionne ?
Ce nest que la couche vue. On peut donc sen servir pour
gnrer des templates partir de composants partags entre
client et serveur.

La rconciliation client/serveur avec React

En appellant ReactDOM.render() sur un noeud contenant du


markup rendu ct serveur, React le prserve et ne fait
quattacher les event handlers.

<div id='app'></div>

ReactDOM.render(<App />,
document.querySelector('#app'));
Exemple minimaliste de structure
Comment cela
fonctionne ? src
shared (composants React partags)
| app.js
| app.spec.js
| menu.js
| content.js
| autres composants ReactJS
client
| index.js (point dentre client)
server
| index.js (point dentre serveur)
Composant React partag (shared/app.js)

Comment cela
fonctionne ? import React from 'react';
import Menu from 'shared/Menu';
import Content from 'shared/Content';
import Footer from 'shared/Footer';

const App = (
<div className="app">
<Menu />
<Content />
<Footer />
</div>
);

return default App;


Ct serveur (expressJS) (server/index.js)

Comment cela
fonctionne ? import { renderToString } from 'react-dom/server';
import App from '../shared/App';

const app = express();


app.get('/*', (req, res) => {
res.send(`
<!doctype html>
<html>
<head>
<title>My Universal App</title>
</head>
<body>
<div id=app'>
${renderToString(<App />)}
</div>
<script src='bundle.js'></script>
</body>
</html>
`);
});
Ct client (browser) (client/index.js)

Comment cela
fonctionne ? import ReactDOM from 'react-dom';
import React from 'react';
import App from '../shared/App';

ReactDOM.render(<App />,
document.querySelector(#app'));
Quelques points dattention

Comment cela La mthode renderToString est bloquante (synchrone) et


fonctionne ? NodeJS monothread, il faut rester vigilant sur ses temps de
rponse (monitoring, caching, ).

Attention aux unhandled exceptions qui peuvent se


produire dans les composants React. Il faut entourer cette
mme mthode renderToString dans un try/catch.

Attention, le bundle client (bundle.js) peut savrer lourd au


final, on peut profiter de webpack pour faire du code
splitting.

Ne pas utiliser React.createClass() pour dclarer les


composants. Les classes ES6 ou composants stateless
sont deux trois fois plus rapides selon React University.
Comment viter de saturer la renderToString ?

Comment cela
On peut utiliser un CDN ou un cache HTTP (Akamai,
fonctionne ? CloudFront, Varnish, ) pour mettre en cache la page HTML
rendue par le serveur.


Le NodeJS est alors plus rarement sollicit,
On peut dfinir un TTL assez bref pour grer linvalidation,
On choisit de grer les donnes variables (informations
utilisateur, ) en CSR et de laisser des placeholders ct
serveur pour ces blocs.

Aussi, on peut procder des optimisations du code source et


faire des tests de performance du renderToString (Gatlin, ).
3 Le routing
Avec React Router V4

Le routing
React Router met notre disposition BrowserRouter (pour le
client) et StaticRouter (pour le serveur).

Ct client (utilise window.location)

ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);

client/index.js
Ct serveur (utilise request.url dexpress)

Le routing const context = {};


const markup = renderToString(
<StaticRouter
location={req.url}
context={context}
>
<App/>
</StaticRouter>
);

if (context.url) {
// Somewhere a `<Redirect>` was rendered
redirect(301, context.url)
} else {
// app rendering ...
}

server/index.js
4 La configuration
La configuration est issue de node-config

La configuration
$ yarn add config (ou npm install config)
$ mkdir config
$ vi config/default.json

Extrait de fichier de configuration (config/default.json)

{
Config prive non envoye ct client
"settings": {
"port": "1337",
"publicFolder": "./public"
},
"public": {
"api": {
"login": "https://foo"
}
}
Config publique envoye ct client
}
node-config

La configuration
On a la possibilit de dfinir des
surcharges de configuration
pour les diffrents environnements
utiliss.


urls de webservices,
port sur lequel lancer le serveur,
API keys,

Ct serveur (on peut accder toute la config simplement)

La configuration import config from 'config';


[]
config.get('settings.port')

Ct client (on accede la config publique uniquement)

Pour viter davoir faire une nouvelle requte ajax pour


rcuprer la config publique ct client, on linjecte directement
dans le bas du document pendant le render server.

<script type="text/javascript">
window.APP_CONFIG={"api":{"login":"https:\u002F\u002Ffoo"}};
</script>
Config publique injecte uniquement
On peut alors faire un utilitaire universel
Utilisable dans les composants shared (SSR ou CSR).
La configuration
5 La donne
Quelques solutions pour rcuprer la donne :

La donne Chaque composant liste lui mme les appels de webservices


ncessaires son fonctionnement.

OU
Chaque page effectue toujours une seule et unique requte
en fonction de son URL courante vers une API
dagrgation. Exemples :

naviguer sur /home fait appel /home.json,


naviguer sur /list?p=3 fait appel /list.json?p=3,

Ces fichiers JSON peuvent alors contenir :


Les donnes issues des webservices,
Les traductions utiles pour la page courante,
Les composants afficher et la structure de la page,
Architecture simplifie

Server
(NodeJS)
CMS

+
HTTP HTTP API dagrgation
API 1
(GraphQL, custom, )
HTML JSON

API 2

Client

GET /home GET /home.json


Data fetching et redux

La donne Ct serveur, il faut retourner la page aprs avoir requt la donne


ncessaire laffichage (un ou plusieurs services).

Ct client, un changement de page (soft routing) est le rsultat dune


action utilisateur entrainant lappel dun ou plusieurs webservices en ajax
puis le render de la nouvelle page en JS.

Dans les 2 cas, la donne rcupre est stocke dans le state


tree de Redux.

source : https://medium.com/@herablog/ameblo-2016-with-react-redux-2d496b868f15
Initialiser Redux (server side)

La donne Ct serveur, la page retourne rsulte dun store rempli par une action.

import { Provider } from 'react-redux';

// Create Redux store and plug middlewares


const store = configureStore();
const { getState, dispatch } = store;

// Trigger a redux action, will fill the store


dispatch(fetchPageData()).then(() => {
// Get the markup filled w/ data from redux store
const markup = renderToString(
<StaticRouter location={req.url} context={context}>
<Provider store={store}>
<App/>
</Provider>
</StaticRouter>
);
// [...] returns the server page
});

server/index.js
Initialiser Redux (server side)

La donne Ct serveur, on met le store disposition sur window pour le


rcuprer ct client.

import serialize from 'serialize-javascript';

// After Redux state init


const initialState = store.getState();

// Server side expressJS


res.send(`
<html>
<head>[...]</head>
<body>
[...]
<script type="text/javascript" charset="utf-8">
window.APP_STATE = ${serialize(initialState);}
</script>
</body>
</html>
`);

<script type="text/javascript">
window.APP_STATE={"page":{"data":"..."}};
</script> server/index.js
Initialiser Redux (client side)

La donne Ct client, on initialise le store partir de la donne issue du


serveur.

import { Provider } from 'react-redux';

// Create our Redux store.


const store = configureStore(window.APP_STATE);

ReactDOM.render(
<BrowserRouter>
<Provider store={store}>
<App />
</Provider>
</BrowserRouter>,
document.getElementById('root')
);

client/index.js
6 Code splitting / lazy loading
Lazy loading avec Webpack 2

Code splitting
Ct client, on voudrait viter davoir tout le code source dans
un seul et unique fichier javascript qui pourrait devenir massif.

Webpack permet simplement de charger des composants en


asynchrone ( la demande) :
shared/xxx.js
import('./component').then(Component => /* ... */);

Cela permet de gnrer des chunks chargs la demande.


Avec Webpack 2

Code splitting
Ct serveur, si lon veut que les composants issus des chunks
soient dans le render initial. On peut par exemple utiliser un
plugin babel.

babel-plugin-system-import-transformer

Dans un contexte NodeJS serveur,


shared/xxx.js
import('./component').then(Component => /* ... */);

est remplac par Babel par :

require(./component');

pour le bundle serveur.


7 CI et CD
Conclusion
Merci.

Vous aimerez peut-être aussi