WP-API + React with server rendering + =
Developer and teacher Co-founder of DecodeMTL Where to find me; github.com/ziad-saab @ZiadDotHTML decodemtl.com Hello! I am Ziad Saab
WARNING This presentation is full of cats and code. #WordCatMTL
lides and source code Slides French: ziad.cc/wcmtl-2016-fr.html English: ziad.cc/wcmtl-2016-en.html Source c ode github.com/ziad-saab/decodemtl-wp-frontend github.com/nyanofthemoon/decodemtl-wp-theme github.com/nyanofthemoon/recursive-acf-to-wp-rest-api github.com/nyanofthemoon/decodemtl-api
eam Paule Lepage Backend + Proxy API Lucas Lemonnier Design + SASS Ziad Saab Frontend
tructure of the talk 1.WordPress and the REST API 2.Overview of WP-API + React stack 3.Lessons learned: a.Metadata and technical SEO b.Async data loading c.How to manage i18n? 4.Conclusions
iny timeline ▷ Founded DecodeMTL in 2014 ▷ Static website — HTML, CSS, JS ▷ Jekyll generates the site’s pages ▷ Yay! No backend! ▷ Mid-2015: need new site ▷ More dynamic and content-based ▷ Content ⇒ CMS ⇒ WordPress ▷ Dynamic ⇒ AJAX/SPA ⇒ React
Inconvenients ▷ The theme is attached to backend ▷ Monolithic structure ▷ “The Loop” ▷ Have to use PHP for frontend ordPress... Advantages ▷ Free and open-source CMS ▷ Community and plugins ▷ Familiar/popular admin tool ▷ Custom post types ▷ Custom fields with ACF ▷ Languages management with Polylang or WPML
ow to keep only the advantages? { "author": 1, "comment_status": "open", "content": { "rendered": "<p>Hello World!</p>n" }, "date": "2016-06-29T13:53:49", "date_gmt": "2016-06-29T13:53:49", "excerpt": { "rendered": "<p>Hello World!</p>n" }, "featured_media": 0, "format": "standard", "id": 29, "link": "http://decodemtl.com/...", "modified": "2016-06-29T13:53:49", "ping_status": "open", "slug": "hello-world", "slug_en": "hello-world", "slug_fr": "bonjour-monde", "tags": [], "title": { "rendered": "First Post" }, "type": "post" }
{ "author": 1, "comment_status": "open", "content": { "rendered": "<p>Hello World!</p>n" }, "date": "2016-06-29T13:53:49", "date_gmt": "2016-06-29T13:53:49", "excerpt": { "rendered": "<p>Hello World!</p>n" }, "featured_media": 0, "format": "standard", "id": 29, "link": "http://decodemtl.com/...", "modified": "2016-06-29T13:53:49", "ping_status": "open", "slug": "hello-world", "slug_en": "hello-world", "slug_fr": "bonjour-monde", "tags": [], "title": { "rendered": "First Post" }, "type": "post" } I SHOULD BUY A WP-API... ow to keep only the advantages?
rchitecture WP-API + React Backend/ Data Template API Proxy
<!doctype html> <html> <head> <meta charset="utf-8"> <title>SUPERBlog!</title> </head> <body> <div id="app"> <!-- i can haz content? --> </div> <script src="app.js"></script> </body> </html> erver rendering: sans
erver rendering 101 GET /fr/cours/html-css HTTP/1.1 User-Agent: firefox Accept-Language: fr, en;q=0.7 ... ExpressJS server with /* route URL passed to react-router which returns tree of components Tree of components passed to redux-connect which uses Promise to wait for all data to be loaded Page data is loaded to a global store with the help of redux. react-dom/server renders the tree of components and react- redux injects store data as props HTTP/1.1 200 OK Date: Wed, 20 Jan 2038 16:19:42 GMT Content-Type: text/html Content-Length: … <!doctype html> <html> <head> <title><!--react-helmet title--></title> <!--meta tags I CAN HAZ SEOS??--> </head> <body> <div id=”app”><!-- react-dom --></div> <script src=”app.js”></script> <script><!--window.REDUX_STATE--></script> </body> </html>
Partagé 850+ Server 100 Client 50 Ines of code I LIKE MATH... IT MAKES PEOPLE CRY.
etadata and technical SEO react-helmet manages the <head>
<Helmet defaultTitle="DecodeMTL" titleTemplate="%s | DecodeMTL" meta={[ {charset: 'utf-8'}, {property: 'og:site_name', content: '...'}, ... ]} /> <Helmet title={this.props.post.title} /> etadata and technical SEO - front
const head = Helmet.rewind(); const title = head.title.toString(); // <title>Web Dev | DecodeMTL</title> const meta = head.meta.toString(); // <meta charset="utf-8"><meta ...> ... etadata and technical SEO - back
polylang manages posts and CPT translation in WordPress react-intl manages string translation in the templates nternationalization
nternationalization - WordPress function decodemtl_polylang_slugs($post) { $language = $polylang->model->get_post_language($post->ID); $current = $language->slug; $currentSlug = $post->post_name; $oppositeSlug = ''; $opposite = ('en' === $current) ? 'fr' : 'en'; $oppositePostId = pll_get_post($post->ID, $opposite); if ($oppositePostId) { $oppositePost = get_post($oppositePostId); $oppositeSlug = $oppositePost->post_name; } return array( 'slug_' . $current => $currentSlug, 'slug_' . $opposite => $oppositeSlug ); }
nternationalization - <head> <link rel="alternate" hreflang="fr" href="https://..."> PARLEZ-VOUS FRANÇAIS?
nternationalization - routes React <Route path="/" component={App}> <Route path="fr" onEnter={() => dispatch(switchLang('fr'))}> <IndexRoute component={Home}/> <Route path="blogue" component={Blog} /> <Route path="blogue/:slug" component={BlogPost} /> <Route path="cours" component={Courses} /> <Route path="cours/:slug" component={CoursePage} /> <Route path=":slug" component={SinglePage} /> </Route> <Route onEnter={() =>dispatch(switchLang('en'))}> <IndexRoute component={Home}/> <Route path="blog" component={Blog} /> <Route path="blog/:slug" component={BlogPost} /> <Route path="courses" component={Courses} /> <Route path="courses/:slug" component={CoursePage} /> <Route path=":slug" component={SinglePage} /> </Route> </Route>
nternationalization - components <FormattedMessage id="loadingBox.text" defaultMessage="Loading..." /> <FormattedDate value={this.props.post.createdAt} year="numeric" month="long" />
nternationalisation - translation module.exports = { "homePage.title": "Bienvenue à DecodeMTL", "homePage.courses": "Nos cours", "coursePage.instructor": "Votre instructeur", "coursePage.schedule": "Horaire du cours", "coursePage.sessions": "Prochaines sessions", "loadingBox.text": "Chargement...", "notFoundPage.title": "404 Non Trouvé!", ... };
TTP and AJAX isomorphic-fetch manages AJAX in front and HTTP requests in back
TTP and AJAX fetch('/wp-json/wp/v2/posts') .then( function(res) { return res.json(); } ) .then(...)
TTP and AJAX redux-connect manages the data fetching synchronization
TTP and AJAX - front @asyncConnect([ { deferred: true, promise: function(opt) { var slug = opt.params.slug; return opt.store.dispatch(loadCourse(slug)); } } ]) class BlogPost extends React.Component { render() {...} }
TTP and AJAX - back app.get('/*', (req, res) => { const memHistory = createHistory(req.url); const store = makeStore(memHistory); const routes = require('./routes'); const options = {history: memHistory, routes: routes, location: req.url}; match(options, function(err, redirectLocation, renderProps) { if (redirectLocation) { res.redirect(redirectLocation.pathname + redirectLocation.search); } else if (renderProps) { loadOnServer({...renderProps, store}) .then(function() { const html = ReactDOM.renderToString( <Provider store={store} key="provider"> <ReduxAsyncConnect {...renderProps} /> </Provider> ); const head = Helmet.rewind(); const title = head.title.toString(); const meta = head.meta.toString(); const link = head.link.toString(); const finalState = store.getState(); res.render('index', {html, title, meta, link, finalState}); }); } }); });
emo?
onclusions ▷ Using React for a WordPress theme gives you the advantages of SPA but also its inconvenients ▷ Server rendering negates some inconvenients like the lack of content and slow loading times ▷ Server rendering doesn’t require more work if we add more pages/features — scalable ▷ This method is a bit overkill for a simple blog. A small ExpressJS app would suffice if you want to stay in JS.
#wordCatMTL @ZiadDotHTML @DecodeMTL hank you

WordCamp Montreal 2016 WP-API + React with server rendering

  • 1.
    WP-API + Reactwith server rendering + =
  • 2.
    Developer and teacher Co-founderof DecodeMTL Where to find me; github.com/ziad-saab @ZiadDotHTML decodemtl.com Hello! I am Ziad Saab
  • 3.
    WARNING This presentation is fullof cats and code. #WordCatMTL
  • 4.
    lides and sourcecode Slides French: ziad.cc/wcmtl-2016-fr.html English: ziad.cc/wcmtl-2016-en.html Source c ode github.com/ziad-saab/decodemtl-wp-frontend github.com/nyanofthemoon/decodemtl-wp-theme github.com/nyanofthemoon/recursive-acf-to-wp-rest-api github.com/nyanofthemoon/decodemtl-api
  • 5.
    eam Paule Lepage Backend +Proxy API Lucas Lemonnier Design + SASS Ziad Saab Frontend
  • 6.
    tructure of thetalk 1.WordPress and the REST API 2.Overview of WP-API + React stack 3.Lessons learned: a.Metadata and technical SEO b.Async data loading c.How to manage i18n? 4.Conclusions
  • 7.
    iny timeline ▷ FoundedDecodeMTL in 2014 ▷ Static website — HTML, CSS, JS ▷ Jekyll generates the site’s pages ▷ Yay! No backend! ▷ Mid-2015: need new site ▷ More dynamic and content-based ▷ Content ⇒ CMS ⇒ WordPress ▷ Dynamic ⇒ AJAX/SPA ⇒ React
  • 8.
    Inconvenients ▷ The themeis attached to backend ▷ Monolithic structure ▷ “The Loop” ▷ Have to use PHP for frontend ordPress... Advantages ▷ Free and open-source CMS ▷ Community and plugins ▷ Familiar/popular admin tool ▷ Custom post types ▷ Custom fields with ACF ▷ Languages management with Polylang or WPML
  • 9.
    ow to keeponly the advantages? { "author": 1, "comment_status": "open", "content": { "rendered": "<p>Hello World!</p>n" }, "date": "2016-06-29T13:53:49", "date_gmt": "2016-06-29T13:53:49", "excerpt": { "rendered": "<p>Hello World!</p>n" }, "featured_media": 0, "format": "standard", "id": 29, "link": "http://decodemtl.com/...", "modified": "2016-06-29T13:53:49", "ping_status": "open", "slug": "hello-world", "slug_en": "hello-world", "slug_fr": "bonjour-monde", "tags": [], "title": { "rendered": "First Post" }, "type": "post" }
  • 10.
    { "author": 1, "comment_status": "open", "content":{ "rendered": "<p>Hello World!</p>n" }, "date": "2016-06-29T13:53:49", "date_gmt": "2016-06-29T13:53:49", "excerpt": { "rendered": "<p>Hello World!</p>n" }, "featured_media": 0, "format": "standard", "id": 29, "link": "http://decodemtl.com/...", "modified": "2016-06-29T13:53:49", "ping_status": "open", "slug": "hello-world", "slug_en": "hello-world", "slug_fr": "bonjour-monde", "tags": [], "title": { "rendered": "First Post" }, "type": "post" } I SHOULD BUY A WP-API... ow to keep only the advantages?
  • 11.
    rchitecture WP-API +React Backend/ Data Template API Proxy
  • 12.
    <!doctype html> <html> <head> <meta charset="utf-8"> <title>SUPERBlog!</title> </head> <body> <divid="app"> <!-- i can haz content? --> </div> <script src="app.js"></script> </body> </html> erver rendering: sans
  • 13.
    erver rendering 101 GET/fr/cours/html-css HTTP/1.1 User-Agent: firefox Accept-Language: fr, en;q=0.7 ... ExpressJS server with /* route URL passed to react-router which returns tree of components Tree of components passed to redux-connect which uses Promise to wait for all data to be loaded Page data is loaded to a global store with the help of redux. react-dom/server renders the tree of components and react- redux injects store data as props HTTP/1.1 200 OK Date: Wed, 20 Jan 2038 16:19:42 GMT Content-Type: text/html Content-Length: … <!doctype html> <html> <head> <title><!--react-helmet title--></title> <!--meta tags I CAN HAZ SEOS??--> </head> <body> <div id=”app”><!-- react-dom --></div> <script src=”app.js”></script> <script><!--window.REDUX_STATE--></script> </body> </html>
  • 14.
    Partagé 850+ Server 100 Client 50 Ines of code ILIKE MATH... IT MAKES PEOPLE CRY.
  • 15.
    etadata and technicalSEO react-helmet manages the <head>
  • 16.
    <Helmet defaultTitle="DecodeMTL" titleTemplate="%s | DecodeMTL" meta={[ {charset:'utf-8'}, {property: 'og:site_name', content: '...'}, ... ]} /> <Helmet title={this.props.post.title} /> etadata and technical SEO - front
  • 17.
    const head =Helmet.rewind(); const title = head.title.toString(); // <title>Web Dev | DecodeMTL</title> const meta = head.meta.toString(); // <meta charset="utf-8"><meta ...> ... etadata and technical SEO - back
  • 18.
    polylang manages posts andCPT translation in WordPress react-intl manages string translation in the templates nternationalization
  • 19.
    nternationalization - WordPress functiondecodemtl_polylang_slugs($post) { $language = $polylang->model->get_post_language($post->ID); $current = $language->slug; $currentSlug = $post->post_name; $oppositeSlug = ''; $opposite = ('en' === $current) ? 'fr' : 'en'; $oppositePostId = pll_get_post($post->ID, $opposite); if ($oppositePostId) { $oppositePost = get_post($oppositePostId); $oppositeSlug = $oppositePost->post_name; } return array( 'slug_' . $current => $currentSlug, 'slug_' . $opposite => $oppositeSlug ); }
  • 20.
    nternationalization - <head> <linkrel="alternate" hreflang="fr" href="https://..."> PARLEZ-VOUS FRANÇAIS?
  • 21.
    nternationalization - routesReact <Route path="/" component={App}> <Route path="fr" onEnter={() => dispatch(switchLang('fr'))}> <IndexRoute component={Home}/> <Route path="blogue" component={Blog} /> <Route path="blogue/:slug" component={BlogPost} /> <Route path="cours" component={Courses} /> <Route path="cours/:slug" component={CoursePage} /> <Route path=":slug" component={SinglePage} /> </Route> <Route onEnter={() =>dispatch(switchLang('en'))}> <IndexRoute component={Home}/> <Route path="blog" component={Blog} /> <Route path="blog/:slug" component={BlogPost} /> <Route path="courses" component={Courses} /> <Route path="courses/:slug" component={CoursePage} /> <Route path=":slug" component={SinglePage} /> </Route> </Route>
  • 22.
  • 23.
    nternationalisation - translation module.exports= { "homePage.title": "Bienvenue à DecodeMTL", "homePage.courses": "Nos cours", "coursePage.instructor": "Votre instructeur", "coursePage.schedule": "Horaire du cours", "coursePage.sessions": "Prochaines sessions", "loadingBox.text": "Chargement...", "notFoundPage.title": "404 Non Trouvé!", ... };
  • 24.
    TTP and AJAX isomorphic-fetch managesAJAX in front and HTTP requests in back
  • 25.
  • 26.
    TTP and AJAX redux-connect managesthe data fetching synchronization
  • 27.
    TTP and AJAX- front @asyncConnect([ { deferred: true, promise: function(opt) { var slug = opt.params.slug; return opt.store.dispatch(loadCourse(slug)); } } ]) class BlogPost extends React.Component { render() {...} }
  • 28.
    TTP and AJAX- back app.get('/*', (req, res) => { const memHistory = createHistory(req.url); const store = makeStore(memHistory); const routes = require('./routes'); const options = {history: memHistory, routes: routes, location: req.url}; match(options, function(err, redirectLocation, renderProps) { if (redirectLocation) { res.redirect(redirectLocation.pathname + redirectLocation.search); } else if (renderProps) { loadOnServer({...renderProps, store}) .then(function() { const html = ReactDOM.renderToString( <Provider store={store} key="provider"> <ReduxAsyncConnect {...renderProps} /> </Provider> ); const head = Helmet.rewind(); const title = head.title.toString(); const meta = head.meta.toString(); const link = head.link.toString(); const finalState = store.getState(); res.render('index', {html, title, meta, link, finalState}); }); } }); });
  • 29.
  • 30.
    onclusions ▷ Using Reactfor a WordPress theme gives you the advantages of SPA but also its inconvenients ▷ Server rendering negates some inconvenients like the lack of content and slow loading times ▷ Server rendering doesn’t require more work if we add more pages/features — scalable ▷ This method is a bit overkill for a simple blog. A small ExpressJS app would suffice if you want to stay in JS.
  • 31.