A look at Edutopia.org
Decoupled Drupal + React case study
About Me
Darren Petersen
• Senior Technical Project Manager
at Lullabot
• Doing Drupal since 2008
Whooo
are you?
Things we’re about to discuss…
• Reasons to take on a decoupled project – or not
• Structured data!
• How the technology works
• Editorial modes
• Performance
• Bonus content: the problem of preview
To decouple or not to decouple…
Why build one website,
when two can do the same job?
Wait no, reverse that…
• Distribution of data to other platforms becomes
possible
• Over time, lightweight design changes are possible
without a total rebuild
• Performance turned out to be the killer app for
Edutopia
Why decoupled?
Sally Young
aka
‘Justafish’
Front-end
Express
React
Redux
Back-end
Drupal
JSONAPI
Mateu Aguilo Bosch
aka ‘e0ipso’
Author of Drupal’s
JSONAPI implementation
JSONAPI 101: What’s a JSONAPI?
• Contributed module for Drupal
• Renders pretty much any entity as JSON data
• /jsonapi/node/article/{UUID}
• /jsonapi/{entity}/{bundle}/{UUID}
• Also allows:
• lists of content
• filters by field
• inclusion of other content via entity reference
• Respects permissions in Drupal
What makes a good API?
Your API needs
structured content
Unstructured content is bad.
Structured content is good.
Matt Robison
CMS + JSONAPI
John Hannah
React/Front-end
Receive API
request and
look up data
Normalize,
and render
JSONAPI response
Handle API
Response Save data to state
Render!
Express
Redux/React
Drupal
Receive request
Parse request and
dispatch API call
Routing URLs from React to Drupal
Page Request: https://www.edutopia.org/article/when-rubric-isnt-enough
URL slug (): article/when-rubric-isnt-enough
API request: /jsonapi/node/article?filter[field_url_slug][value]=article/when-rubric-
isnt-enough
Handle API
Response Save data to state
Render!
Drupal
Redux/React
Express
Receive API
request and
look up data
Normalize,
and render
JSONAPI response
Receive request
Parse request and
dispatch API call
Express
Drupal
Redux/React
Handle API
Response Save data to state
Render!
Receive API
request and
look up data
Normalize,
and render
JSONAPI response
Receive request
Parse request and
dispatch API call
Express
Drupal
Redux/React
Handle API
Response Save data to state
Render!
Receive API
request and
look up data
Normalize,
and render
JSONAPI response
Receive request
Parse request and
dispatch API call
Attack of the 50-foot
Legacy URLs
Redux/React
Express
Drupal
Receive request
Parse request and
dispatch API call
Stock JSONAPI
endpoint
Normalize,
and render
JSONAPI response
Handle API
Response Save data to state
Render!
Quit trying so
hard and just ask
Drupal what it is.
Custom
resolver
endpoint
Outcome of hacking JSONAPI and React:
• Performance was fine
• The system worked great
• We introduced finicky maintenance issues due to non-standard ways
of using React/Redux.
• Doing things in the wrong order has a cost – but the business need
was met.
Let’s talk about editorial UX…
Structured content isn’t always user friendly.
Structured layout solutions work great for:
pages with clear, rigid design constraints
built by expert users
Less structured layout solutions are required for:
pages with more fluid design constraints
built by occasional users.
AKA, Where’s my WYSIWYG
Video of embedding an entity
Unstructured content is bad.
CMS Implementation
• media module for images, video and documents
• entity embed + inline entity form
• hidden entity ref fields that stored the media entities
referenced in the body field
• alterations to media browser modals for field
overrides
• ckeditor customizations for specific cases
Practically speaking,
this is structured data…
but it’s buried in the body field.
Body field
data
Rich text
processor
Text
block
Text
block
Image
Video
Text
block
Text
block
Image
Video
Text
block
Text
block
Image
Video
Each of these has a
corresponding entity in
the API response,
And React can
render their components
Performance!
• Code splitting reduced JS bundle sizes by ~40%
• Lazy-load of images and related content in articles improved load
times by nearly a second.
• Preloading content allows for sub-second response times within the
site.
• Sparse fieldsets
More JSONAPI: Sparse fieldsets
• Normally, relationships and includes load the WHOLE
related entity
• You can specify the data payload only contain what
you want, by listing those fields in your request.
Example: fields[bundle--type]={comma separated list of
fields}
/jsonapi/node/landing_page?_format=api_json&filter[field_url_slug][valu
e]=videos&include=related_content&fields[node--article]=
uuid,id,nid,title,field_cover_media,field_cover_media_display,field_sum
mary,field_eyebrow,field_url_slug,field_video,field_video_still,field_autho
rs,field_intro
More JSONAPI: Sparse fieldsets
Sparse fieldsets reduced the size of
API payloads by 60% or more
- as much as 90% in some cases.
• Two websites is definitely twice the work
• You’ll have to reinvent the wheel sometimes
• Everything is harder
• But you gain the capacity for multichannel publishing
• And you might have the fastest site on the block
Takeaways…
Editorial Preview
Yet another thing you can’t do
easily on a decoupled site.
Preview: Content Moderation + JSONAPI
• These two very fine modules don’t talk to each other
• We made an endpoint that serves revisions through JSONAPI
• JSONAPI respects permissions, so we can secure this out of the box.
• Hacked revisions tab and node views to render links to the preview
front-end
Preview: front-end considerations
• Separate web host for previews
• Same codebase, different config
• Routing of preview URLs triggers API request to the Preview endpoint
• Authentication support between front-end and Drupal
• Preview mode on the front-end is configurable, so that previews can’t
be rendered on prod

Decoupling Edutopia.org

  • 1.
    A look atEdutopia.org Decoupled Drupal + React case study
  • 2.
    About Me Darren Petersen •Senior Technical Project Manager at Lullabot • Doing Drupal since 2008
  • 4.
  • 5.
    Things we’re aboutto discuss… • Reasons to take on a decoupled project – or not • Structured data! • How the technology works • Editorial modes • Performance • Bonus content: the problem of preview
  • 6.
    To decouple ornot to decouple…
  • 7.
    Why build onewebsite, when two can do the same job? Wait no, reverse that…
  • 8.
    • Distribution ofdata to other platforms becomes possible • Over time, lightweight design changes are possible without a total rebuild • Performance turned out to be the killer app for Edutopia Why decoupled?
  • 10.
  • 11.
  • 12.
    Mateu Aguilo Bosch aka‘e0ipso’ Author of Drupal’s JSONAPI implementation
  • 13.
    JSONAPI 101: What’sa JSONAPI? • Contributed module for Drupal • Renders pretty much any entity as JSON data • /jsonapi/node/article/{UUID} • /jsonapi/{entity}/{bundle}/{UUID} • Also allows: • lists of content • filters by field • inclusion of other content via entity reference • Respects permissions in Drupal
  • 14.
    What makes agood API? Your API needs structured content
  • 15.
  • 16.
  • 17.
    Matt Robison CMS +JSONAPI John Hannah React/Front-end
  • 18.
    Receive API request and lookup data Normalize, and render JSONAPI response Handle API Response Save data to state Render! Express Redux/React Drupal Receive request Parse request and dispatch API call
  • 19.
    Routing URLs fromReact to Drupal Page Request: https://www.edutopia.org/article/when-rubric-isnt-enough URL slug (): article/when-rubric-isnt-enough API request: /jsonapi/node/article?filter[field_url_slug][value]=article/when-rubric- isnt-enough
  • 20.
    Handle API Response Savedata to state Render! Drupal Redux/React Express Receive API request and look up data Normalize, and render JSONAPI response Receive request Parse request and dispatch API call
  • 22.
    Express Drupal Redux/React Handle API Response Savedata to state Render! Receive API request and look up data Normalize, and render JSONAPI response Receive request Parse request and dispatch API call
  • 25.
    Express Drupal Redux/React Handle API Response Savedata to state Render! Receive API request and look up data Normalize, and render JSONAPI response Receive request Parse request and dispatch API call
  • 26.
    Attack of the50-foot Legacy URLs
  • 27.
    Redux/React Express Drupal Receive request Parse requestand dispatch API call Stock JSONAPI endpoint Normalize, and render JSONAPI response Handle API Response Save data to state Render! Quit trying so hard and just ask Drupal what it is. Custom resolver endpoint
  • 28.
    Outcome of hackingJSONAPI and React: • Performance was fine • The system worked great • We introduced finicky maintenance issues due to non-standard ways of using React/Redux. • Doing things in the wrong order has a cost – but the business need was met.
  • 29.
    Let’s talk abouteditorial UX…
  • 32.
    Structured content isn’talways user friendly.
  • 33.
    Structured layout solutionswork great for: pages with clear, rigid design constraints built by expert users
  • 34.
    Less structured layoutsolutions are required for: pages with more fluid design constraints built by occasional users. AKA, Where’s my WYSIWYG
  • 35.
  • 36.
  • 37.
    CMS Implementation • mediamodule for images, video and documents • entity embed + inline entity form • hidden entity ref fields that stored the media entities referenced in the body field • alterations to media browser modals for field overrides • ckeditor customizations for specific cases
  • 39.
    Practically speaking, this isstructured data… but it’s buried in the body field.
  • 40.
  • 41.
    Text block Text block Image Video Text block Text block Image Video Each of thesehas a corresponding entity in the API response, And React can render their components
  • 44.
    Performance! • Code splittingreduced JS bundle sizes by ~40% • Lazy-load of images and related content in articles improved load times by nearly a second. • Preloading content allows for sub-second response times within the site. • Sparse fieldsets
  • 45.
    More JSONAPI: Sparsefieldsets • Normally, relationships and includes load the WHOLE related entity • You can specify the data payload only contain what you want, by listing those fields in your request.
  • 46.
    Example: fields[bundle--type]={comma separatedlist of fields} /jsonapi/node/landing_page?_format=api_json&filter[field_url_slug][valu e]=videos&include=related_content&fields[node--article]= uuid,id,nid,title,field_cover_media,field_cover_media_display,field_sum mary,field_eyebrow,field_url_slug,field_video,field_video_still,field_autho rs,field_intro More JSONAPI: Sparse fieldsets
  • 47.
    Sparse fieldsets reducedthe size of API payloads by 60% or more - as much as 90% in some cases.
  • 48.
    • Two websitesis definitely twice the work • You’ll have to reinvent the wheel sometimes • Everything is harder • But you gain the capacity for multichannel publishing • And you might have the fastest site on the block Takeaways…
  • 49.
    Editorial Preview Yet anotherthing you can’t do easily on a decoupled site.
  • 50.
    Preview: Content Moderation+ JSONAPI • These two very fine modules don’t talk to each other • We made an endpoint that serves revisions through JSONAPI • JSONAPI respects permissions, so we can secure this out of the box. • Hacked revisions tab and node views to render links to the preview front-end
  • 52.
    Preview: front-end considerations •Separate web host for previews • Same codebase, different config • Routing of preview URLs triggers API request to the Preview endpoint • Authentication support between front-end and Drupal • Preview mode on the front-end is configurable, so that previews can’t be rendered on prod

Editor's Notes

  • #3 Tech vs. Business stakeholders? Interested newcomers? Explored React, Angular or similar JS frameworks? How many of you have built decoupled projects?
  • #4 50 or so ‘bots across N. America and Europe Fully distributed Work for government, finance, and media companies doing large-scale digital publishing Syfy.com, NYU School of medicine.
  • #5 Tech vs. Business stakeholders? Interested newcomers? Explored React, Angular or similar JS frameworks? How many of you have built decoupled projects? Thanks for that background – So what are we going to do today?
  • #8 Our discovery process @lullabot always asks this… because decoupled isn’t for everyone. reinventing the wheel … error handling, logging, authentication, preview retool your development team… At least twice the effort guaranteed So what’s the upside?
  • #9 If you’re not looking to do multichannel publishing, don’t have extreme performance needs Or a wish to burn lots of money on bleeding edge tech Then you don’t need a decoupled site. Yet. One of these days, ‘fast’ will be normal.
  • #10 publishes practical content for teachers, improve quality of education D7 site – 10-15 years of content Originally D6, migrated to D7, headed for D8 Lullabot engaged to do a discovery, come up with a future plan for Edutopia.
  • #11 Sally is one of our lullabot API/JS experts, and was on the discovery team. JavaScript Modernization Initiative… Multiple article content types, Design system and social…
  • #12 After all the discussion, to build a new Edutopia.org using a decoupled D8/React architecture Talk front-end, then back-end, Now, a word about JSONAPI…
  • #14 Run the list… This makes an API work out of the box… But you have to be serving up high-quality data for any of this to matter…
  • #15 If you can’t break your data down into discrete bits, you’re not going to have a successful decoupled experience. Content modeling is super important, all the way down to what you allow in the body field.
  • #16 HTML lacks the necessary structure for use in a decoupled project. It’s essentially one long string of text, which you have to render to the screen all at once. In this example, we don’t have much freedom to alter or remix the data, cause it’s all been rendered to markup already.
  • #17 Content modeling and structuring your data is essential. But… We can take each bit of data here and mix them up anyway we want in our React components. JSONAPI handles this for us in Drupal – except when we might bury important data inside other fields
  • #19 Talk through request lifecycle… Among the many problems you have to solve, there has to be some planning about the URLs your front-end will handle. This is commonly called routing. When you receive a request, how do you know what data to call from the API?
  • #20 The URL fragment shown above (/article/* ) is passed through. The Front-end has to inspect and figure out what api call to make. This is called routing. JSONAPI module provides entity/bundle endpoints for pretty much anything in the Drupal CMS – nodes, terms, users, custom entity types.
  • #21 Talk through request lifecycle… Among the many problems you have to solve, there has to be some planning about the URLs your front-end will handle. This is commonly called routing. When you receive a request, how do you know what data to call from the API?
  • #22 Two of the pieces that sit behind a page on Edutopia.org are the API data we called from Drupal… And the component (template) in react that we shove that data into… You’ll note that all our data is sitting under that attributes key, in name-value pairs…
  • #23 Talk through request lifecycle… Among the many problems you have to solve, there has to be some planning about the URLs your front-end will handle. This is commonly called routing. When you receive a request, how do you know what data to call from the API?
  • #25 The URL fragment shown above (/article/* ) is passed through. The Front-end has to inspect and figure out what api call to make. This is called routing. JSONAPI module provides entity/bundle endpoints for pretty much anything in the Drupal CMS – nodes, terms, users, custom entity types. That URL slug gave us enough of a clue that we could call the data and it would render, like this: So what’s behind this rendered page?
  • #26 Talk through request lifecycle… Among the many problems you have to solve, there has to be some planning about the URLs your front-end will handle. This is commonly called routing. When you receive a request, how do you know what data to call from the API?
  • #27 Our routing example from earlier was straightforward, but it’s not the whole story. Real websites have years of urls to handle – they change format over time, have to be kept up for SEO or social media reasons… Edutopia wanted to keep that haystack of URLs. This meant an url slugs like article/* might not really point to articles, and we can’t establish clear rules. Solution: add api endpoint that takes a url, looks it up, and then pipes it through the JSONAPI stack to render whatever JSON response is needed. Front-end then has to have logic to parse the type of response and hand off to the right rendering components. (get better words here)
  • #28 So we couldn’t expect the front-end to know anything certain about the url slugs that were being requested. So eventually we gave up and asked Drupal Instead of the stock jsonapi endpoint, we wrote a ‘resolver’ that looked up content by url alias Then we returned that data using the JSONAPI layer, and react had to make sense of it and do the right thing to render it. This added complexity, and we can’t recommend doing this… but it was performant, and allowed us to satisfy the requirement.
  • #31 Tour of the home page
  • #32 Node form for landing page –
  • #33 Editors don’t always like this structured data stuff. Too many fields, not enough visual feedback Abstraction is great for software, but hard for humans.
  • #34 For trained users who know what to expect… this works great.
  • #35 For users who just want to get the work done… Edutoipa needed more freedom to embed things into the body.
  • #36 Video: editing process But you said the body field was bad!
  • #37 Remember this slide? Yeah – you can’t make good choices on the front end if you’re already rendering markup from Drupal.
  • #38 Solution in Drupal: Media entities + entity embed + customizations to entity browser Hidden entity-ref field was important – cause it won’t show in the API unless it’s in a field. Here’s what it looked like:
  • #39 Yikes. This is that drupal entity, embedded in the body of the article. Data attributes keep things structured… But you have to extract that out of the body the hard way.
  • #40 Yikes. This is that drupal entity, embedded in the body of the article. Data attributes keep things structured… But you have to extract that out of the body the hard way.
  • #41 Our RichText component in React has to scan the body and break it into chunks (PITA, fragile) Chunks get rendered through React components as needed.
  • #42 And the editors loved it.
  • #43  Images render markup through react components, based on API data. The Editors can do their thing, and embed media into their articles, and everyone is happy…
  • #44 Except for the front-enders. They cry. It’s fincky regular expressions for days. But when they’re done, it’s great. Let’s switch gears and talk about performance…
  • #48 It’s troublesome to juggle these early in the dev process, when your content model is still in flux. But when it’s stable, and before you’re really in the front-end dev game, start using them. Also useful to clearly document page components and their related fields, so you can easily refer back and keep things clean.
  • #50 Problem: Authoring content in Drupal doesn't leave you with a sense of what the content will really look like when rendered. Back-end solution: Custom endpoint taking revision IDs, look up revision, render through JSONAPI stack, Front-end solution: set up additional host using end to serve revisions, with config to enable previews, basic auth token being passed, auth to preview system.
  • #55 To take it back to our example for the Edutopia homepage, here’s what the related content would look like:
  • #56 The actual JSONAPI response is a little messy – the component entity consists of some metadata and then a list of UUIDs, which are references to data objects elsewhere in the data. Our code works through all that and stores it in Redux, which triggers the render of the corresponding React component.
  • #57 The actual JSONAPI response is messy – the component entity consists of some metadata and then a list of UUIDs, which are references to data objects elsewhere in the data. Our code has to work through all that and store it in Redux, which triggers the render of the corresponding React component.
  • #58 EOL implementations - 3+ article-style content types - New design system engagement and SEO increasingly affected by performance Decoupled or not?