Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Server side rendering with React and Symfony

792 views

Published on

Theory companion for a workshop about SSR of React with PHP (Symfony). Web Summer Camp 2018.

Published in: Software
  • Be the first to comment

  • Be the first to like this

Server side rendering with React and Symfony

  1. 1. SSR of React with Symfony Workshop Bits of theory Nacho Martin
  2. 2. Nacho Martín I write code at Limenius. We build tailor-made projects, and provide consultancy and formation. We are very happy with React, and have been dealing with how to integrate with PHP for some time now & publishing libraries about it.
  3. 3. What is the problem that Server Side Rendering addresses?
  4. 4. A long time ago in a galaxy far, far away Server
  5. 5. A long time ago in a galaxy far, far away Server HTML </> HTML </> Client
  6. 6. Adding dynamic elements HTML </> Client HTML </> Server
  7. 7. Adding dynamic elements HTML </> Client HTML </> JS JS Server
  8. 8. Step 1: Client uses JS to modify the DOM Client HTML </> JS $( "p" ).addClass( “myClass" );
  9. 9. With DOM modification We can now modify the document reacting to user interaction. What about loading new content based on user interaction?
  10. 10. Example 1 2 3 4 5
  11. 11. Adding dynamic content HTML </> Client HTML </> JS JS Server
  12. 12. Adding dynamic content HTML </> Client HTML </> JS JS Server API
  13. 13. DOM Manipulation This happens in the Browser Element <body> Element <div id=“grid”> Element <h1> Text “Hi there” API $.get( “api/page2.json“, function(data) { $(“#grid”).html(renderPage(data)); } );
  14. 14. DOM Manipulation This happens in the Browser Element <body> Element <div id=“grid”> Element <h1> Text “Hi there” … Element <div> Element <div> API $.get( “api/page2.json“, function(data) { $(“#grid”).html(renderPage(data)); } );
  15. 15. This means that the first thing the user sees is this …and also crawlers :(
  16. 16. Slow page loads in mobile users https://www.doubleclickbygoogle.com/articles/mobile-speed-matters/ • Average load time over 3G: 19 seconds. • 53% of sites that take longer than 3s are abandoned. • Going from 19s to 5s means: • 25% more impressions of ads. • 70% longer sessions. • 35% lower bounce race. • 2x ad revenue.
  17. 17. When are these problems worse Apps. Bearable. Content pages. Probably unbearable.
  18. 18. We want HTML </> Initial state in the HTML code we provide to the client
  19. 19. And dynamically The client takes control over the element API $(“#grid”).html(renderPage(data));
  20. 20. So we need a way to run our client side JS app from our server Let’s run some JS from PHP
  21. 21. Webpack
  22. 22. We need
  23. 23. WebpackAssets JS JS TS SASS PNG JPEG JS Client App
  24. 24. WebpackAssets JS JS TS SASS PNG JPEG JS Client App JS Server side App
  25. 25. Configuration module.exports = { entry: output: }; Entry points of our code Files to be generated
  26. 26. Configuration module.exports = { entry: output: module: { rules: [ ] }, }; Entry points of our code Files to be generated How to deal with different types of modules (js, png, scss…)
  27. 27. Intro to React
  28. 28. We want to build a TODO list Pour eggs in the pan How to cook an omelette Buy eggs Break eggs
  29. 29. We want to build a TODO list Pour eggs in the pan Beat eggs How to cook an omelette Buy eggs Break eggs
  30. 30. Options
  31. 31. Options 1: Re-render everything.
  32. 32. Options 1: Re-render everything. Simple
  33. 33. Options 1: Re-render everything. Simple Not efficient
  34. 34. Options 2: Find in the DOM where to insert elements, what to move, what to remove… 1: Re-render everything. Simple Not efficient
  35. 35. Options 2: Find in the DOM where to insert elements, what to move, what to remove… 1: Re-render everything. Simple Complex Not efficient
  36. 36. Options 2: Find in the DOM where to insert elements, what to move, what to remove… 1: Re-render everything. Simple EfficientComplex Not efficient
  37. 37. Options 2: Find in the DOM where to insert elements, what to move, what to remove… 1: Re-render everything. Simple EfficientComplex Not efficient React allows us to do 1, although it does 2 behind the scenes
  38. 38. Fundamental premise Give me a state and a render() method that depends on it and forget about how and when to render.
  39. 39. Let’s write a React component Click me! Clicks: 0
  40. 40. Let’s write a React component Click me! Clicks: 1Click me!
  41. 41. Let’s write a React component import React, { Component } from 'react'; class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; } tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Click me!</button> <span>Clicks: {this.state.count}</span> </div> ); } } export default Counter;
  42. 42. Let’s write a React component import React, { Component } from 'react'; class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; } tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Click me!</button> <span>Clicks: {this.state.count}</span> </div> ); } } export default Counter; Initial state
  43. 43. Let’s write a React component import React, { Component } from 'react'; class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; } tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Click me!</button> <span>Clicks: {this.state.count}</span> </div> ); } } export default Counter; Set new state Initial state
  44. 44. Let’s write a React component import React, { Component } from 'react'; class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; } tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Click me!</button> <span>Clicks: {this.state.count}</span> </div> ); } } export default Counter; Set new state render(), called by React Initial state
  45. 45. Working with state constructor(props) { super(props); this.state = {count: 1}; } Initial state
  46. 46. Working with state constructor(props) { super(props); this.state = {count: 1}; } Initial state this.setState({count: this.state.count + 1}); Assign state
  47. 47. render() and JSX It is not HTML, it is JSX. React transforms it internally to HTML elements. Good practice: make render() as clean as possible, only a return. render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); }
  48. 48. Components hierarchy
  49. 49. Components hierarchy
  50. 50. Components hierarchy: props class CounterGroup extends Component { render() { return ( <div> <Counter name=“dude"/> <Counter name=“sir”/> </div> ); } }
  51. 51. Components hierarchy: props render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}> Click me! {this.props.name} </button> <span>Clicks: {this.state.count}</span> </div> ); } and in Counter… class CounterGroup extends Component { render() { return ( <div> <Counter name=“dude"/> <Counter name=“sir”/> </div> ); } }
  52. 52. Components hierarchy: props render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}> Click me! {this.props.name} </button> <span>Clicks: {this.state.count}</span> </div> ); } and in Counter… class CounterGroup extends Component { render() { return ( <div> <Counter name=“dude"/> <Counter name=“sir”/> </div> ); } }
  53. 53. Fundamental premise Everything depends on the state, therefore we can
  54. 54. Fundamental premise Everything depends on the state, therefore we can render the initial state to a HTML string
  55. 55. ReactDOMServer.renderToString(element) ReactDOM.hydrate(element, container[, callback]) SSR in React
  56. 56. ReactDOMServer.renderToString(<MyApp/>) SSR in React. 1) In the server: <div data-reactroot=""> This is some <span>server-generated</span> <span>HTML.</span> </div>
  57. 57. SSR in React. 2) insert in our template <html> … <body> <div id=“root”> <div data-reactroot=""> This is some <span>server-generated</span> <span>HTML.</span> </div> </div> …
  58. 58. ReactDOM.hydrate( <MyApp/>, document.getElementById('root') ) SSR in React. 3) In the client: <div id=“root”> <div data-reactroot=""> This is some <span>server-generated</span> <span>HTML.</span> </div> </div> … The client takes control over it
  59. 59. React Bundle
  60. 60. ReactRenderer ecosystem phpexecjsReactRendererReactBundle node.js v8js … Twig extension External renderer Selects JS runner Runs it Uses snapshots if available Integration with Symfony React on Rails integration
  61. 61. JS side part: React on Rails https://github.com/shakacode/react_on_rails Used among others by
  62. 62. JS side part: React on Rails import ReactOnRails from 'react-on-rails'; import RecipesApp from './RecipesAppServer'; ReactOnRails.register({ RecipesApp }); JavaScript:
  63. 63. Server part Twig: SSR: rendered HTML Client side: a <script/> tag with the info to render the component
  64. 64. Routing
  65. 65. React Router <Router> <Route path={"/movie/:slug"} render={props => <Movie {…props} />} /> <Route path={"/movies"} render={props => <MovieList {…props} />} /> </Router>;
  66. 66. React Router <Router> <Route path={"/movie/:slug"} render={props => <Movie {…props} />} /> <Route path={"/movies"} render={props => <MovieList {…props} />} /> </Router>; Different for SSR & Browser
  67. 67. React Router <Router> <Route path={"/movie/:slug"} render={props => <Movie {…props} />} /> <Route path={"/movies"} render={props => <MovieList {…props} />} /> </Router>; props.match.params.slug Different for SSR & Browser
  68. 68. React Router import { BrowserRouter, StaticRouter, Route } from "react-router-dom"; export default (initialProps, context) => { if (context.serverSide) { return ( <StaticRouter basename={context.base} location={context.location} context={{}} > <MainApp { ...initialProps} /> </StaticRouter> ); } else { return ( <BrowserRouter basename={context.base}> <MainApp { ...initialProps} /> </BrowserRouter> ); } };
  69. 69. Meta tags
  70. 70. Extracting headers react-helmet (vue-helmet) import { Helmet } from "react-helmet"; class Application extends React.Component { render() { return ( <div className="application"> <Helmet> <meta charSet="utf-8" /> <title>My Title </title> <link rel="canonical" href="http: //mysite.com/example" /> </Helmet> ... </div> ); } }
  71. 71. Extracting headers Return array instead of component export default (initialProps, context) => { if (context.serverSide) { return { renderedHtml: { componentHtml: renderToString( <StaticRouter basename={context.base} location={context.location} context={{}} > <MainApp { ...initialProps} /> </StaticRouter> ), title: Helmet.renderStatic().title.toString() } }; } else { return ( <BrowserRouter basename={context.base}> <MainApp { ...initialProps} /> </BrowserRouter> ); } };
  72. 72. Then in Twig {% set movie = react_component_array('MoviesApp', {'props': props}) %} {% block title %} {{ movie.title is defined ? movie.title | raw : '' }} {% endblock title %} {% block body %} {{ movie.componentHtml | raw }} {% endblock %} Use members of the array
  73. 73. External renderer
  74. 74. External JS server Client PHP App JS renderer JS Client side App Server side JS App Component and state
  75. 75. External JS server Client PHP App JS renderer JS Client side App Server side JS App Component and state
  76. 76. ReactRenderer ecosystem phpexecjsReactRendererReactBundle node.js v8js … External renderer

×