Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Progressive Hydration #react_fukuoka
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Hiroyuki ANAI
June 05, 2019
Programming
1.9k
6
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Progressive Hydration #react_fukuoka
Hiroyuki ANAI
June 05, 2019
More Decks by Hiroyuki ANAI
See All by Hiroyuki ANAI
書き換えて学ぶTemporal #fukts
pirosikick
2
420
fukuoka.ts #3 社内でESLintの共通設定を配りたい2025年春版
pirosikick
3
480
compilerOptions、全部読んだ
pirosikick
1
300
Step Functionsの設計時に知っておいたほうがいいかもしれないこと
pirosikick
0
530
Go言語による並行処理「4.4 orチャネル」の図
pirosikick
0
480
サイボウズWebフロントエンド脱レガシーの今までとこれから
pirosikick
6
17k
@cybozu/eslint-configから学ぶ、全社共通ESLint configの運用
pirosikick
4
1.9k
Web Share Target API #w3fukuoka
pirosikick
0
740
Google I/O '19のWebをまとめる会
pirosikick
2
890
Other Decks in Programming
See All in Programming
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
340
PostgreSQL 18で考えるUUID主キー
kazuhiro1982
0
440
PHPだって関数型したい 〜できること、できないこと〜 / fp-in-php
jsoizo
1
260
言語を使う側から、作る側へ。 自作 Lisp で得た新たな気づき。
andpad
0
140
JAWS-UG横浜 #102 AWSサ終供養LT会 成仏できない AWS サービスたち 〜本日、三体供養します〜
maroon1st
0
280
わからない話を追いかけたら、プログラミング言語を作る側にいた
ydah
2
110
コーディングルールの鮮度を保ちたい for SRE NEXT 2026 / keep-fresh-go-internal-conventions-sre-next-2026
handlename
0
160
為什麼你並不需要ViewModel / No, you don't need a ViewModel
lovee
1
450
Laravel Boostに学ぶ、AIにPHPを書かせる技術 〜OSSの実装から蒸留するエージェント制御の王道〜
kentaroutakeda
3
590
PHP に部分適用が来るぞ!……ところで何それ?おいしいの? #phpcon / phpcon-2026
shogogg
0
440
ドリフトを絶対に許さない(?)CDK運用 / CDK Ops with Zero Tolerance for Drifts (?)
akihisaikeda
1
120
継続モナドとリアクティブプログラミング
yukikurage
3
670
Featured
See All Featured
The B2B funnel & how to create a winning content strategy
katarinadahlin
PRO
1
450
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
200
Tell your own story through comics
letsgokoyo
1
1k
Docker and Python
trallard
47
4k
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
1.1k
SEO Brein meetup: CTRL+C is not how to scale international SEO
lindahogenes
1
2.8k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
31
3.3k
Building the Perfect Custom Keyboard
takai
2
820
Documentation Writing (for coders)
carmenintech
77
5.4k
Large-scale JavaScript Application Architecture
addyosmani
515
110k
The World Runs on Bad Software
bkeepers
PRO
72
12k
svc-hook: hooking system calls on ARM64 by binary rewriting
retrage
2
410
Transcript
1SPHSFTTJWF )ZESBUJPO !QJSPTJLJDL ʢ8FEʣ 3FBDUษڧձ!ԬWPM
w !QJSPTJLJDL w αΠϘζגࣜձࣾ ϑϩϯτΤϯυΤΩεύʔτνʔϜ w స৬͠·ͨ͠! ԬͷΤϯδχΞ·ͩҰਓͰ͕͢ ͨͷ͍͠৬Ͱ͢ w
ԬͰ࠾༻ͯ͠·͢ ࣗݾհ
͢͜ͱ w )ZESBUJPOͷ͓͞Β͍ w 1SPHSFTTJWF)ZESBUJPOʹ͍ͭͯ
)ZESBUJPOͷ͓͞Β͍
)ZESBUJPOͱʁ w 4FSWFS4JEF3FOEFSJOHʢҎԼɺ443ʣʹΑͬͯ ඳը͞ΕͨίϯςϯπΛɺ w $MJFOU4JEF3FOEFSJOHʢҎԼɺ$43ʣͰ͍ճ͢͜ͱ
$43POMZ w ίϯϙʔωϯτͷπϦʔ͔Β%0.Λੜɺඳը w Πϕϯτϋϯυϥʔͷઃఆ // client.js import React from
'react'; import ReactDOM from 'react-dom'; import App from './App'; ReactDOM.render( <App />, document.getElementById('root') );
443 )ZESBUJPO w αʔόʔαΠυ w ίϯϙʔωϯτπϦʔΛ)5.-ͷจࣈྻʹม͠ɺϨεϙϯε͢Δ • ReactDOMServer.{renderToString|renderToNodeStream} // server.js
const ReactDOMServer = require('react-dom/server'); …省略 app.get('/', (req, res) => { const html = ReactDOMServer.renderToNodeStream( <Html><App /></Html> ); html.pipe(res); });
443 )ZESBUJPO w ΫϥΠΞϯταΠυ w 443͕ฦ͢)5.-Ͱ%0.͕ߏஙɾඳըࡁΈ w ΠϕϯτϋϯυϥʔͷઃఆͷΈΛߦ͏ w 3FBDU%0.SFOEFSͰͳ͘ɺ3FBDU%0.IZESBUFΛ͏
// client.js import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; ReactDOM.hydrate( <App />, document.getElementById('root') );
$41POMZ )551(&5 +4ͷμϯϩʔυ +4ͷ࣮ߦ '$1 +4͕࣮ߦ͞ΕΔ·Ͱը໘͕ਅͬന '$1'JSTU$POUFOUGVM1BJOUɺ ϖʔδભҠ͔Β࠷ॳͷίϯςϯπඳը·Ͱͷ࣌ؒ
443 )ZESBUJPO )551(&5 +4ͷμϯϩʔυ +4ͷ࣮ߦ '$1 '$1 '$1͕͘ͳΔ 443Ͱ ඳը͞ΕΔ
͜͜·Ͱͷ·ͱΊ w )ZESBUJPO443ͷ݁ՌΛ$43Ͱ͍ճ͢͜ͱΛݴ͏ w 443ʴ)ZESBUJPOʹΑͬͯ'$1͕͘ͳΔ w αʔόʔαΠυͰ 3FBDU%0.4FSWFSSFOEFS5P4USJOH͔ 3FBDU%0.4FSWFSSFOEFS5P/PEF4USFBNΛ͏ w
ΫϥΠΞϯταΠυͰ 3FBDU%0.SFOEFSͰͳ͘3FBDU%0.IZESBUFΛ͏
1SPHSFTTJWF )ZESBUJPO ʹ͍ͭͯ
)ZESBUJPOͷ w '$1͘ͳΔ͕55*͘ͳΒͳ͍ w 55*5JNF5P*OUFSBDUJWF w ϖʔδ͕ΠϯλϥΫςΟϒʢૢ࡞Մೳʣʹ ͳΔ·Ͱͷ࣌ؒ w ඳը͞Εͯૢ࡞ՄೳʹͳΔͷɺ+4͕࣮ߦ͞Εͨޙ
$41POMZ )551(&5 +4ͷμϯϩʔυ +4ͷ࣮ߦ '$1 55*
443 )ZESBUJPO )551(&5 +4ͷμϯϩʔυ +4ͷ࣮ߦ '$1 55*
1SPHSFTTJWF)ZESBUJPO w )ZESBUJPOΛஈ֊తʹߦ͏͜ͱͰ55*Λॖ͢Δ w (PPHMF*0Ͱհ͞ΕͨςΫχοΫ w 3FOEFSJOHPOUIF8FC 1FSGPSNBODF*NQMJDBUJPOTPG"QQMJDBUJPO"SDIJUFDUVSF IUUQTZPVUVCFL"7GV630H
443 )ZESBUJPO )551(&5 +4ͷ%- +4ͷ࣮ߦ '$1 55* Ұؾʹ)ZESBUJPO
1SPHSFTTJWF)ZESBUJPO +4ͷ࣮ߦ 55* ඞཁͳ͚ͩ)ZESBUJPO )551(&5 +4ͷ%- '$1 +4ͷ࣮ߦ Γͷ)ZESBUJPO ҰؾʹΔΑΓ
͘ͳΔ ඞཁʹͳͬͨΒ )ZESBUJPO͢Δ ɾɾɾ
σϞ w (PPHMF͕σϞΛެ։͍ͯ͠Δ w IUUQTHJUIVCDPN(PPHMF$ISPNF-BCTQSPHSFTTJWF SFOEFSJOHGSBNFXPSLTTBNQMFT w ϑΝʔετϏϡʔͷޙʹ݅ͷϦετ͕͋Δϖʔδ w Ϧετ෦࠷ॳͷ)ZESBUJPOͷର֎
w Ϧετ෦ը໘ʹೖͬͨλΠϛϯάͰ )ZESBUJPO͞ΕΔ
ϑΝʔετϏϡʔ ࠷ॳʹ)ZESBUJPO͞ΕΔ Ϧετ෦ ը໘ʹೖͬͨΒ)ZESBUJPO͞ΕΔ
"VEJUTͰൺֱ 1)ͳ͠ 1)͋Γ 55*͕ඵߴʹ
αϯϓϧͷιʔείʔυΛ ಡΉ w BQQKT w ϧʔτίϯϙʔωϯτ͕ఆٛ͞Ε͍ͯΔ w TUSFBNKTʢ˞ࠓճಡ·ͳ͍ʣ w Ϧετ෦ͷ4USFBNίϯϙʔωϯτ͕ఆٛ͞Ε͍ͯΔ
w IZESBUPSKT w 1SPHSFTTJWF)ZESBUJPOΛ࣮ߦ͢Δίϯϙʔωϯτ
// app.js … let load = () => import('./stream'); let
Hydrator = ClientHydrator; if (typeof window === 'undefined') { Hydrator = ServerHydrator; load = () => require('./stream'); } export default function App() { return ( <div id="app"> <Header /> <Intro /> <Hydrator load={load} /> </div> ); } BQQKT ϧʔτίϯϙʔωϯτʢ"QQʣ
// app.js … let load = () => import('./stream'); let
Hydrator = ClientHydrator; if (typeof window === 'undefined') { Hydrator = ServerHydrator; load = () => require('./stream'); } export default function App() { return ( <div id="app"> <Header /> <Intro /> <Hydrator load={load} /> </div> ); } $43Ͱ%ZOBNJD*NQPSUͰ 4USFBNίϯϙʔωϯτΛಡΈࠐΉ 4USFBNίϯϙʔωϯτ #VOEMF͕͔Εɺ ಈతʹϒϥβʹϩʔυ͞ΕΔ
// app.js … let load = () => import('./stream'); let
Hydrator = ClientHydrator; if (typeof window === 'undefined') { Hydrator = ServerHydrator; load = () => require('./stream'); } export default function App() { return ( <div id="app"> <Header /> <Intro /> <Hydrator load={load} /> </div> ); } 443࣌੩తʹ 4USFBNίϯϙʔωϯτΛಡΈࠐΉ
// app.js … let load = () => import('./stream'); let
Hydrator = ClientHydrator; if (typeof window === 'undefined') { Hydrator = ServerHydrator; load = () => require('./stream'); } export default function App() { return ( <div id="app"> <Header /> <Intro /> <Hydrator load={load} /> </div> ); } )ZESBUPSίϯϙʔωϯτ͕ 1SPHSFTTJWF)ZESBUJPOΛ࣮ߦ͢Δ QSPQTMPBE͕ฦ͢ίϯϙʔωϯτ Ԇͯ͠)ZESBUJPO͞ΕΔ
// hydrator.js … export class Hydrator extends React.Component { shouldComponentUpdate()
{ return false; } componentDidMount() { new IntersectionObserver(async ([entry], obs) => { if (!entry.isIntersecting) return; obs.unobserve(this.root); const { load, ...props } = this.props; const Child = interopDefault(await load()); ReactDOM.hydrate(<Child {...props} />, this.root); }).observe(this.root); } render() { return ( <section ref={c => this.root = c} dangerouslySetInnerHTML={{ __html: '' }} suppressHydrationWarning /> ); } } IZESBUPSKT )ZESBUPS$MJFOU)ZESBUPS
// hydrator.js … export class Hydrator extends React.Component { shouldComponentUpdate()
{ return false; } componentDidMount() { new IntersectionObserver(async ([entry], obs) => { if (!entry.isIntersecting) return; obs.unobserve(this.root); const { load, ...props } = this.props; const Child = interopDefault(await load()); ReactDOM.hydrate(<Child {...props} />, this.root); }).observe(this.root); } render() { return ( <section ref={c => this.root = c} dangerouslySetInnerHTML={{ __html: '' }} suppressHydrationWarning /> ); } } w TIPVME$PNQPOFOU6QEBUFͰ ৗʹGBMTFΛฦ͍ͯ͠ΔͷͰ Ξοϓσʔτ͞Εͳ͍
// hydrator.js … export class Hydrator extends React.Component { shouldComponentUpdate()
{ return false; } componentDidMount() { new IntersectionObserver(async ([entry], obs) => { if (!entry.isIntersecting) return; obs.unobserve(this.root); const { load, ...props } = this.props; const Child = interopDefault(await load()); ReactDOM.hydrate(<Child {...props} />, this.root); }).observe(this.root); } render() { return ( <section ref={c => this.root = c} dangerouslySetInnerHTML={{ __html: '' }} suppressHydrationWarning /> ); } } w ۭͷཁૉΛग़ྗɺ ཁૉͷSFGΛऔಘ w EBOHFSPVTMZ4FU*OOFS)5.-Ͱ ۭจࣈΛઃఆ͢Δͱɺ )ZESBUJPOΛᷖճ͢Δ
// hydrator.js … export class Hydrator extends React.Component { shouldComponentUpdate()
{ return false; } componentDidMount() { new IntersectionObserver(async ([entry], obs) => { if (!entry.isIntersecting) return; obs.unobserve(this.root); const { load, ...props } = this.props; const Child = interopDefault(await load()); ReactDOM.hydrate(<Child {...props} />, this.root); }).observe(this.root); } render() { return ( <section ref={c => this.root = c} dangerouslySetInnerHTML={{ __html: '' }} suppressHydrationWarning /> ); } } w *OUFSTFDUJPO0CTFSWFSͰ ཁૉͷεΫϩʔϧҐஔΛࢹ w ཁૉ͕ը໘ʹೖͬͨΒɺ )ZESBUJPOΛ࣮ߦ
w αʔόʔαΠυී௨ʹ443ΛΔ w )ZESBUPSίϯϙʔωϯτͷԼ ࠷ॳͷ)ZESBUJPOΛᷖճ͢Δ w EBOHFSPVTMZ4FU*OOFS)5.-ʹۭจࣈΛ͢ w )ZESBUPSίϯϙʔωϯτͷԼ͕ ը໘ʹೖͬͨΒͦͷ෦ͷ)ZESBUJPOΛ࣮ߦ
w *OUFSTFDUJPO0CTFSWFSͰࢹ͠ɺ 3FBDU%0.IZESBUFΛ࣮ߦ
·ͱΊ
w 1SPHSFTTJWF)ZESBUJPO ஈ֊తʹ)ZESBUJPO͢Δ͜ͱͰ55*Λॖ͢Δ w ݱঢ়)BDLZͳ࣮͕ͩɺެࣜʹରԠ༧ఆ͕͋Δ w "OHVMBSɺ7VFͰͰ͖ΔʢΒ͍͠ʣ
1SPHSFTTJWF)ZESBUJPO ಋೖ͖͔͢ʁ w ಋೖʹϦεΫ͕΄΅ͳ͍ w )ZESBUPSίϯϙʔωϯτΛऔΓআ͚ͩ͘ͰݩʹͤΔ w ݱঢ়ϋοΫͬΆ͍࣮ɻόʔδϣϯΞοϓ࣌ʹҙ͕ඞཁ͔ w 55*͕ಛผ͍ॴͰಋೖ͢Δͷ͕Αͦ͞͏
w 55*͕͍ॴͰޮՌ͕ബ͍ͷͰɺ શϖʔδͰಋೖ͖͔͢ඍົ w "VEJUTͰܭଌ͠·͠ΐ͏
w (PPHMF*0ͷ8FCΛ·ͱΊΔձ IUUQTNFOUBJDPKTDPOOQBTTDPNFWFOU w ʢਫʣ w ·ۭ͍ͩͯ·͢ͷͰɺͥͻʙ
͋Γ͕ͱ͏ ͍͟͝·ͨ͠