Upgrade to Pro — share decks privately, control downloads, hide ads and more …

WordPressで Headlessフロントエンド / kansaiwp-meetup-202011

WordPressで Headlessフロントエンド / kansaiwp-meetup-202011

Hidetaka Okamoto

November 22, 2020
Tweet

More Decks by Hidetaka Okamoto

Other Decks in Programming

Transcript

  1. TL;DR • webαΠτදࣔํ๏͸SSR / SPA / SSGͷ3ͭ • SEO /

    Մ༻ੑॏࢹͳΒ͹SSG • Paywall / ձһઐ༻ίϯςϯπͳͲ͸SPA • ύϑΥʔϚϯεͱߋ৽଎౓ͷఱṝ࣍ୈͰSSR #WPmeetupOsaka
  2. Agenda • WordPressΛHeadlessʹ࢖͏ͨΊʹඞཁͳ΋ͷ • SPAͰ࡞Δํ๏ • SSGͰ࡞Δํ๏ • SSRͰ࡞Δํ๏ •

    Next.js ͱ Gatsby • All In One Content Management System͔Βͷ୤٫ #WPmeetupOsaka
  3. Agenda • WordPressΛHeadlessʹ࢖͏ͨΊʹඞཁͳ΋ͷ • SPAͰ࡞Δํ๏ • SSGͰ࡞Δํ๏ • SSRͰ࡞Δํ๏ •

    Next.js ͱ Gatsby • All In One Content Management System͔Βͷ୤٫ #WPmeetupOsaka
  4. Headless = ֎͔ΒWPͷσʔλΛऔಘ͢Δ • HTTP(S) APIΛར༻ͯ͠ɺ֎෦͔ΒWPͷσʔλΛऔಘ • REST API /

    GraphQL API / XMLRPC APIͷ3ͭ • XMLRPC APIͷ͜ͱ͸๨Ε·͠ΐ͏ • REST API: ͍ΘΏΔWP API • GraphQL: WP GraphQL ϓϥάΠϯʢ࠷͍ۙͭʹv1ϦϦʔεʣ #WPmeetupOsaka
  5. WP API • WPίΞ͕ఏڙ͢ΔREST API • GET͸͍͍ͩͨೝূͳ͠ͰOK • HookͰΧελϚΠζՄೳ •

    ΧελϜϑΟʔϧυͳͲͷ֦ு͸ ϓϥάΠϯ͔HookͰରԠ https://developer.wordpress.org/rest-api/reference/ #WPmeetupOsaka
  6. REST API or GraphQL • ໔ڐऔಘલ͔ΒϨʔγϯάΧʔʹ৐Δඞཁ͸ͳ͍ • γϯϓϧͳBlog / websiteͳΒREST

    APIͰ͍͍ͩͨࣄ଍ΓΔ • ΧελϜ౤ߘ / ϑΟʔϧυͳͲ͕૿͑ͯ͘ΔͱɺGraphQL΋ݕ౼ର৅ • REST APIΛ3ͭ΋4ͭ΋ಉ࣌ʹୟ͔ͳ͍ͱ͍͚ͳ͍ or HookͰΧελϜ͠·͘Βͳ͍ͱ͍͚ͳ͍ͳΒGraphQLͷग़൪ • ࠷ऴతʹHTTPͰGET(REST)͔POST(GraphQL)͢ΔͷʹมΘΓͳ͠ʢWebSocketͷ͜ͱ͸͍ͬͨΜ๨ΕΖʣ #WPmeetupOsaka
  7. Agenda • WordPressΛHeadlessʹ࢖͏ͨΊʹඞཁͳ΋ͷ • SPAͰ࡞Δํ๏ • SSGͰ࡞Δํ๏ • SSRͰ࡞Δํ๏ •

    Next.js ͱ Gatsby • All In One Content Management System͔Βͷ୤٫ #WPmeetupOsaka
  8. S ingle P age A pplication • ࠷ۙͷWebΞϓϦʹଟ͍ • AjaxͰσʔλΛऔಘͯ͠දࣔ

    • νϡʔτϦΞϧ΍ೖ໳ॻ͸ ͍͍ͩͨ͜ΕΛ࡞Δ͜ͱ͕ଟ͍ https://ja.reactjs.org/ #WPmeetupOsaka
  9. ReactͰͷ REST API import React, {useEffect, useState } from 'react'

    import DOMPurify from 'dompurify'; export const Home:FC<{ posts: WPPost[] }> = ({posts: initialProps}) => { const [posts, setPosts] = useState([]) useEffect(() => { fetch('https://wp.example.com/wp-json/wp/v2/posts') .then(data => { return data.json() }) .then(data => { setPosts(data) }) }) return ( <div> {posts.map(post => ( <div key={post.ID}> <h1 dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(post.title.rendered) }}/> <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(post.excerpt.rendered) }}/> </div> ))} </div> ) • APIͳͲඇಉظॲཧ͸ Ұखؒඞཁ • useEffectͰAPIݺͼग़͠ • useStateͰϨεϙϯεอ࣋ • ͋ͱ͸React JSXͰHTMLඳը #WPmeetupOsaka
  10. ReactͰͷ REST API import React, {useEffect, useState } from 'react'

    import DOMPurify from 'dompurify'; export const Home:FC<{ posts: WPPost[] }> = ({posts: initialProps}) => { const [posts, setPosts] = useState([]) useEffect(() => { fetch('https://wp.example.com/wp-json/wp/v2/posts') .then(data => { return data.json() }) .then(data => { setPosts(data) }) }) return ( <div> {posts.map(post => ( <div key={post.ID}> <h1 dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(post.title.rendered) }}/> <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(post.excerpt.rendered) }}/> </div> ))} </div> ) • APIͳͲඇಉظॲཧ͸ Ұखؒඞཁ • useEffectͰAPIݺͼग़͠ • useStateͰϨεϙϯεอ࣋ • ͋ͱ͸React JSXͰHTMLඳը #WPmeetupOsaka
  11. ReactͰͷ REST API import React, {useEffect, useState } from 'react'

    import DOMPurify from 'dompurify'; export const Home:FC<{ posts: WPPost[] }> = ({posts: initialProps}) => { const [posts, setPosts] = useState([]) useEffect(() => { fetch('https://wp.example.com/wp-json/wp/v2/posts') .then(data => { return data.json() }) .then(data => { setPosts(data) }) }) return ( <div> {posts.map(post => ( <div key={post.ID}> <h1 dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(post.title.rendered) }}/> <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(post.excerpt.rendered) }}/> </div> ))} </div> ) • APIͳͲඇಉظॲཧ͸ Ұखؒඞཁ • useEffectͰAPIݺͼग़͠ • useStateͰϨεϙϯεอ࣋ • ͋ͱ͸React JSXͰHTMLඳը #WPmeetupOsaka
  12. ReactͰͷ REST API import React, {useEffect, useState } from 'react'

    import DOMPurify from 'dompurify'; export const Home:FC<{ posts: WPPost[] }> = ({posts: initialProps}) => { const [posts, setPosts] = useState([]) useEffect(() => { fetch('https://wp.example.com/wp-json/wp/v2/posts') .then(data => { return data.json() }) .then(data => { setPosts(data) }) }) return ( <div> {posts.map(post => ( <div key={post.ID}> <h1 dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(post.title.rendered) }}/> <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(post.excerpt.rendered) }}/> </div> ))} </div> ) • APIͳͲඇಉظॲཧ͸ Ұखؒඞཁ • useEffectͰAPIݺͼग़͠ • useStateͰϨεϙϯεอ࣋ • ͋ͱ͸React JSXͰHTMLඳը #WPmeetupOsaka
  13. SPAͰWebαΠτΛ࡞Δ • ೖ໳͠΍͍͢ • ॻ੶ͳͲͷϦιʔε΋ଟΊ • શ෦ϑϩϯτͰ࣮ߦͳͷͰ ൺֱతγϯϓϧʹ࡞ΕΔ • CapacitorͳͲͰiOS

    / AndroidରԠ ϝϦοτ • SEO / OGPͰҰखؒඞཁ • ϧʔςΟϯά΋͋Δఔ౓ࣗલ࣮૷ • First Meaningful Paint͕஗Ί • ίʔυ෼ׂ͠ͳ͍ͱ JSͷಡΈࠐΈαΠζ͕ංେ͕ͪ͠ σϝϦοτ #WPmeetupOsaka
  14. Agenda • WordPressΛHeadlessʹ࢖͏ͨΊʹඞཁͳ΋ͷ • SPAͰ࡞Δํ๏ • SSGͰ࡞Δํ๏ • SSRͰ࡞Δํ๏ •

    Next.js ͱ Gatsby • All In One Content Management System͔Βͷ୤٫ #WPmeetupOsaka
  15. S tatic S ite G enerator • Jekyll΍HugoͳͲ΋ • දࣔ͢ΔHTMLΛࣄલʹੜ੒͢Δ

    • MovableTypeͷGenerate΋͍ۙ • FW / Tool͕܈༤ׂڌঢ়ଶ Vue / React + Ruby / Go / etc… https://jamstack.org/generators/ #WPmeetupOsaka
  16. SSGͰWebαΠτΛ࡞Δ • ࣄલʹHTMLΛϏϧυ͢ΔͷͰɺ දࣔ଎౓͕଎͘ͳΓ΍͍͢ • WP / DBʹো֐͕ى͖ͯ΋ɺ webαΠτ͸μ΢ϯ͠ͳ͍ •

    αʔόʔ͕҆Ձ • FWͷػೳ / API͕๛෋ ϝϦοτ • Ϗϧυ͠ͳ͍ͱެ։Ͱ͖ͳ͍ • ϖʔδ਺ʹൺྫ͢ΔϏϧυ࣌ؒ • FW΁ͷґଘ͕ڧ͘ͳΓ͕ͪ • Ϗϧυͷσόοά͕গ͠େม σϝϦοτ #WPmeetupOsaka
  17. Agenda • WordPressΛHeadlessʹ࢖͏ͨΊʹඞཁͳ΋ͷ • SPAͰ࡞Δํ๏ • SSGͰ࡞Δํ๏ • SSRͰ࡞Δํ๏ •

    Next.js ͱ Gatsby • All In One Content Management System͔Βͷ୤٫ #WPmeetupOsaka
  18. S erver S ide R endering • WordPressςʔϚͱಉ͡ख๏ • ౎౓HTMLඳըͳͷͰɺ

    ϏϧυΛ଴ͭඞཁͳ͠ • αʔόʔଆඳըͳͷͰɺ SEO / OGPͷෆ҆΋ͳ͠ • WPಛԽFW “Frontity”ͷଘࡏ΋ https://frontity.org/ #WPmeetupOsaka
  19. FrontityͰ WebαΠτ # ΞϓϦͷηοτΞοϓ $ npx frontity create my-app $

    cd my-app # ϩʔΧϧͰͷςεταΠτ্ཱͪ͛ $ npx frontity dev -> http://localhost:3000 # σΟϨΫτϦߏ੒ $ tree my-app/ |__ node_modules/ |__ package.json |__ frontity.settings.js |__ favicon.ico |__ packages/ |__ mars-theme/ • جຊతͳૢ࡞͸CLI͚ͩ • —themeͰςʔϚ͕બ΂Δ • —typescript΋αϙʔτ #WPmeetupOsaka
  20. FrontityͰ WebαΠτ "name": "frontity-example", "state": { "frontity": { "url": "https://test.frontity.io",

    "title": "WordPress Theme Unittest", "description": "Frontity example site using WordPress Theme Unittest posts" } }, "packages": [ { "name": "@frontity/mars-theme", "state": { "theme": { "menu": [ [ “Home", “/“ ], [ "Edge Case Category”, “/category/edge-case-2/“ ], [ “About”, “/about/“ ] ], "featured": { "showOnList": false, "showOnPost": false } } } }, { "name": "@frontity/wp-source", "state": { "source": { "api": "https://test.frontity.io/wp-json" } } }, • frontity.config.jsͰઃఆ • WPςʔϚͷΑ͏ʹ ίʔυΛ৮Βͤͳ͍ ΧελϚΠζํ๏ • ςʔϚͷࣗ࡞ / ެ։΋Մೳ #WPmeetupOsaka
  21. S erver S ide R endering • WordPressςʔϚͱಉ͡ख๏ • ౎౓HTMLඳըͳͷͰɺ

    ϏϧυΛ଴ͭඞཁͳ͠ • αʔόʔଆඳըͳͷͰɺ SEO / OGPͷෆ҆΋ͳ͠ • WPಛԽFW “Frontity”ͷଘࡏ΋ https://frontity.org/ #WPmeetupOsaka
  22. SSRͰWebαΠτΛ࡞Δ • SSGͱSPAͷܽ఺Λิ͑Δ • SEO / OGP / HeaderͳͲͷग़ྗ •

    ϏϧυʹΑΔެ։·Ͱͷϥά • Ruby / Go / PythonͳͲͰ΋Մೳ ϝϦοτ • ʮWordPressςʔϚͰΑ͘Ͷʁʯ • WPಉ༷ࣄނΕ͹നը໘ͷHTTP500 • ϑϩϯτͷJS + αʔόʔͷJSͷ ೋॏ؅ཧ͕ൃੜ͠͏Δ σϝϦοτ #WPmeetupOsaka
  23. SPA / SSG / SSR͓͞Β͍ 41" 44( 443 )5.-ඳը ϒϥ΢β্

    Ϗϧυ࣌ ʢ$*ϩʔΧϧͳͲʣ αʔόʔ্ '8ͳͲ *POJD3FBDU $SFBUF3FBDU"QQ /FYUKT (BUTCZ 'SPOUJUZ XJUI81ͷ αΠτߏங ʮ΍ͬͯΈͨʯهࣄ ॻ੶ͳͲΛࢀߟʹ '8ͷνϡʔτϦΞϧ Λࢀߟʹ '8ͷνϡʔτϦΞϧ Λࢀߟʹ ޲͍͍ͯΔέʔε 8FCΞϓϦ ձһ޲͚αΠτ ϒϩάϙʔτϑΥϦΦ -1ΩϟϯϖʔϯαΠτ ͦΕҎ֎ #WPmeetupOsaka
  24. Agenda • WordPressΛHeadlessʹ࢖͏ͨΊʹඞཁͳ΋ͷ • SPAͰ࡞Δํ๏ • SSGͰ࡞Δํ๏ • SSRͰ࡞Δํ๏ •

    Next.js ͱ Gatsby.js • All In One Content Management System͔Βͷ୤٫ #WPmeetupOsaka
  25. ReactͰͷSSG 2TOP: Next.js / Gatsby /FYUKT (BUTCZ 3FOEFSJOH 44(443 44(

    3PVUJOH OFYUSPVUFS !SFBDISPVUFS 0⒏DJBM )PTUJOH 7FSDFM (BUTCZ$MPVE %BUB 3FRVFTU 3&45"1*(SBQI2- (SBQI2-
  26. Next.js / Gatsbyݸਓతൺֱ • σʔλͷऔಘ͕ࣗલ࣮૷ • SSRซ༻Ͱެ։·ͰͷϥάΛ୹ॖ • SSGߴ଎Խ͸3rd partyґଘ

    • SSR΋Մೳɻ෯޿͘࢖͑Δ Next.js • ϓϥάΠϯͰ؆୯σʔλऔಘ • ϏϧυඞਢͳͷͰެ։ʹϥά༗ • ࠩ෼ϏϧυʹΑΔϏϧυߴ଎Խ • SSGʹಛԽ Gatsby #WPmeetupOsaka
  27. Next.jsͱGatsbyͷσʔλऔಘ export const getStaticPaths = async () => { const

    posts = await fetcher(url) return { paths: posts.map(post => ({ params: { id: post.id, slug: decodeURI(post.slug) } })), fallback: false } } export const pageQuery = graphql` query SinglePost($slug: String) { wordpressPost(slug: { eq: $slug }) { id slug title content } } ` Next.js Gatsby #WPmeetupOsaka
  28. SSGͷϏϧυ஗Ԇ໰୊ • Gatsby͸ʮࠩ෼ͷΈϏϧυ͢Δํ๏ʯΛϕʔλϦϦʔε Incremental Builds: https://www.gatsbyjs.com/blog/2020-04-22-announcing-incremental-builds/ • Next.js͸ʮͳ͍ͳΒͦͷ৔ͰSSG / SSR͠Α͏ʯͱ͍͏ൃ૝

    Fallback mode: https://nextjs.org/docs/basic-features/data-fetching#fallback-true • Next.js͸͞ΒʹʮఆظతʹཪଆͰSSG͠ͳ͓ͦ͏ʯͱ΋ߟ͍͑ͯΔ Incremental Static Regeneration: https://nextjs.org/docs/basic-features/data-fetching#incremental-static-regeneration • Gatsby͸ϏϧυΛߴ଎ԽɺNext.js͸SSRతൃ૝Λซ༻ͯ͠ߴ଎Խ #WPmeetupOsaka
  29. Agenda • WordPressΛHeadlessʹ࢖͏ͨΊʹඞཁͳ΋ͷ • SPAͰ࡞Δํ๏ • SSGͰ࡞Δํ๏ • SSRͰ࡞Δํ๏ •

    Next.js ͱ Gatsby.js • All In One Content Management System͔Βͷ୤٫ #WPmeetupOsaka
  30. ಠࣗλάͷ࡞੒ / ར༻ͰϚʔΫΞοϓͷڞ௨Խ … <link href="css/bootstrap.min.css" rel="stylesheet"> </head> <body <?php

    body_class(); ?> <div class="jumbotron"> <h1>Hello, world!</h1> <p> <a class="btn btn-primary btn-lg” role="button">Learn more</a> </p> </div> <div class="row"> <div class="col-md-1">.col-md-1</div> <div class="col-md-1">.col-md-1</div> </div> import React from 'react'; import { Jumbotron, Button, Row, Col } from 'reactstrap'; export const App: FC = () => ( <> <Jumbotron> <h1>Hello, world!</h1> <p className=“lead”> <Button color="primary">Learn More</Button> </p> </Jumbotron> <Row> <Col md={1}>.col</Col> </Row> </> ) WordPress Theme React (Reactstrap) #WPmeetupOsaka
  31. ެ։͞Ε͍ͯΔUIϑϨʔϜϫʔΫͷಋೖ / ެ։ • Bootstrap / Material Design / Foundation

    / etc.. • ଟ͘ͷCSSϑϨʔϜϫʔΫ͸͍͍ͩͨReactnize͞Εͨ΋ͷ͕͋Δ • reactstrap / material-ui / react-foundation / etc.. • Ionic FrameworkͷΑ͏ʹReact / Angular / VueΛԣஅͰ͖Δ΋ͷ΋ • AWS NorthstarͷΑ͏ʹɺ σβΠϯΨΠυϥΠϯΛϥΠϒϥϦԽͯ͠γΣΞ͕Ͱ͖Δ #WPmeetupOsaka
  32. σʔλͱ ϑϩϯτͷ෼཭ • HeadlessԽ͢Δ͜ͱͰɺ CMSґଘগͷϑϩϯτ͕࡞ΕΔ • Gatsby͸Source PluginͰ ෳ਺data source͕ར༻Մೳ

    • dataʹґଘ͠ͳ͍ϑϩϯτٕज़ ͱͯ͠ͷJavaScript https://www.gatsbyjs.com/plugins?=source #WPmeetupOsaka
  33. TL;DR • Headlessʹ͢Δ͜ͱͰɺ ͞·͟·ͳํ๏ / ݴޠ / FWͰαΠτ͕࡞ΕΔ • ReactΛ࢖͏৔߹ɺNext.js

    / GatsbyͳͲΛ࢖͏ͷ͕ ͍·ͷͱ͜ΖఆੴԽͭͭ͋͠Δ • WordPress͚ͩͰαΠτΛ։ൃ / ӡ༻͍ͯ͠ͳ͍৔߹ʹɺ ؀ڥʹґଘ͠ͳ͍Headless͕׆͖΍͍͢ #WPmeetupOsaka