ReactベースのSPA開発で開発者が気をつけたいSEOのお話

 ReactベースのSPA開発で開発者が気をつけたいSEOのお話

【シューマイ】Tech Lead Engineerから最新技術を学べ!React編
https://shuuu-mai.connpass.com/event/140002/

3ae2d821854f9f6749f5ab265559f8ef?s=128

Yoshinobu Wakamatsu

August 21, 2019
Tweet

Transcript

  1. ReactベースのSPA開発で 開発者が気をつけたいSEOのお話 株式会社クラウドポート IT・業務管理部⻑/エンジニア 若松 慶信

  2. Introduction 2019/8/21 2

  3. ⾃⼰紹介 株式会社クラウドポート IT・業務管理部⻑/エンジニア 若松 慶信 2019/8/21 3 • クラウドポートのFundsではフロントエンド含む開発・運⽤全般を担当 •

    SEOについては前職で経験
  4. 資産形成をしたい個⼈とお⾦を借りたい企業を結ぶ、 「貸付ファンド」のオンラインマーケット 第⼆種⾦融商品取引業者のクラウドポートが運営

  5. None
  6. 2019/8/21 6 今回のテーマについて

  7. 2019/8/21 7 Reactなどを使うモダンなフロントエンド開発での SEOに関する情報は少ない・・・

  8. FundsでのReactの使い⽅を SEOという視点からお話します 2019/8/21 8 ※SEOの話多めです

  9. SEOとフロントエンドエンジニアの役割 2019/8/21 9

  10. SEO(検索エンジン最適化)とは Webサイトが検索エンジンで⾒つかりやすくすること 2019/8/21 10

  11. SEOに取り組む上での基本的な考え⽅ • ベストエフォートで取り組み、結果が出るのを待つ • 銀の弾丸はない 2019/8/21 11

  12. 今回はフロントエンドエンジニアの役割にフォーカスした話をします 2019/8/21 12

  13. SEOにおけるフロントエンドエンジニアの役割 クローラーが正しくWebサイトを解釈できるようにし サイトが正当な評価を受けられるようにする 2019/8/21 13

  14. 「正当な評価を受けられる」とは︖ 2019/8/21 14

  15. 正当な評価を受けられないケースの例 • クローラーが正しくレンダリングできない • ページ間の関係性をリンクで伝えられていない • 複数のURLで同じコンテンツを表⽰できてしまう • 存在しないページなのに、200でコンテンツが表⽰できてしまう 2019/8/21

    15
  16. SEOにおけるフロントエンドエンジニアの役割 クローラーが正しくWebサイトを解釈できるようにし サイトが正当な評価を受けられるようにする 2019/8/21 16

  17. SEOにおけるフロントエンドエンジニアの役割 クローラーが正しくWebサイトを解釈できるようにし サイトが正当な評価を受けられるようにする 2019/8/21 17 エンジニアの考慮不⾜で機会損失を⽣まないことが重要

  18. SEOを考慮したレンダリング⼿法の選択 2019/8/21 18

  19. レンダリング (今回の⽂脈では) HTMLとJavaScriptからブラウザ上へ描画するための DOMツリーを構築すること 2019/8/21 19

  20. レンダリング 正しくレンダリングさせることは、正当な評価を受ける必要条件の1つ 2019/8/21 20

  21. Googlebotのレンダリング能⼒ • (〜2019/5)Chrome 41 程度のレンダリング能⼒ • ES6などのシンタックスは未サポートでpolyfillが必要 • (2019/5〜現在)最新のChromium エンジンを適⽤可能に

    • ES6などにも対応 • クローラー向けのpolyfillは不要になっている 2019/8/21 21
  22. 2019/8/21 22

  23. SPAの主なレンダリング⽅式 • CSR (Client Side Rendering) • ブラウザ上でJavaScriptによりレンダリングする⽅式 • SSG

    (Static Site Generator) • ビルド時に静的なHTMLを出⼒する⽅式 • Dynamic Rendering • ⼀般のユーザー向けにはCSRを適⽤する • クローラー向けの配信に限りレンダリング済みの結果を返す • SSR (Server Side Rendering) • コンテンツ配信時にサーバサイドでレンダリングする⽅式 • Rehydration により継続して CSR でレンダリングできるようにすることも多い • ⾯倒だが確実性はある 2019/8/21 23
  24. SSRは必要か︖ • レンダリングの問題に限ればSSRは必須ではない • SSRでなければ対応できない問題もある 2019/8/21 24

  25. SSRでなければできないこと • ステータスコードを適切に返す • ページ単位のURL正規化/ソフト404エラーの防⽌に必要 • メタ要素 (title, meta, link

    タグなど)を確実に認識させる • URL正規化で使⽤する rel=canonical はSSR以外では解釈されない • その他のメタ要素もサーバサイドでレンダリングして返すことが望ましい • 頻繁に更新されるコンテンツ • JavaScriptを使⽤するレンダリングは遅い(次に説明) • これはDynamic Renderingでも対応できる 2019/8/21 25
  26. 引⽤元: https://developers.google.com/search/docs/guides/javascript-seo-basics GoogleがJavaScriptをレンダリングするプロセス JavaScriptのレンダリングには このプロセスが追加で必要 レンダリングされるまで時間を要する 2019/8/21 26

  27. SEOを考慮したレンダリング⼿法の選択 ⾃然検索流⼊が重要な集客チャネルかどうか︖に注⽬する 2019/8/21 27

  28. SEOを考慮したレンダリング⼿法の選択 ⾃然検索流⼊が重要 ⾃然検索流⼊はあまり重要ではない サイトの例 • Webメディア • ECサイト/マーケットプレイス • ポータルサイト

    • コーポレートサイト • 会員登録が必須のサイト • 他の集客チャネルがメインのサイト 採⽤する アプローチ SSRを採⽤することが望ましい SSRを採⽤するかはUX次第 2019/8/21 28
  29. Fundsの集客チャネルと要件 ⾃然検索流⼊が重要 ⾃然検索流⼊はあまり重要ではない サイトの例 • Webメディア • ECサイト/マーケットプレイス • ポータルサイト

    • コーポレートサイト • 会員登録が必須のサイト • 他の集客チャネルがメインのサイト 採⽤する アプローチ SSRを採⽤することが望ましい SSRを採⽤するかはUX次第 2019/8/21 29 ⾃然検索流⼊は主要な集客チャネルではないが、機会損失がないようにしたい + UXも考慮してSSRを採⽤
  30. Fundsのフロントエンド概要 2019/8/21 30

  31. Fundsのフロントエンド概要 Service app (JavaScript on Browser) Service API (Scala) Service

    app (Node.js) on Server on Browser ClientとServerの双⽅で動作するIsomorphicなアプリケーション 2019/8/21 31
  32. Fundsのフロントエンド概要 • ES6 + Flow(TypeScript移⾏したい) • React + Redux •

    WebpackでPC⽤, SP⽤, SSR⽤に3種類のビルドを⽣成 • Node.js + Express での SSR(Next.js など使⽤せず) 2019/8/21 32
  33. 3種類のビルド⽣成 • 単⼀のコードベースからPC⽤とSP⽤のビルドを⽣成 • またNode.jsでの実⾏⽤にSSR⽤のビルドを別に⽣成 source code build (PC) build

    (SP) build (server) webpack 2019/8/21 33
  34. clientではPC/SPでentrypointを分離 (webpack.client.config.js) client/server⽤の設定 (webpack.config.js) 3種類のビルド⽣成: Webpackの設定(抜粋) 2019/8/21 34 const client

    = require('./webpack.client.config’); const server = require('./webpack.server.config’); module.exports = [client, server]; entry: { appPc: [ './src/funds_pc.jsx’, ], appSp: [ './src/funds_sp.jsx’, ], }, …
  35. Dynamic Serving(動的な配信) PC・SPで異なるコンテンツを表⽰する⽅式 Server Browser (PC) Browser (SP) HTML (for

    SP) request request HTML (for PC) 2019/8/21 35
  36. Dynamic Serving(動的な配信) UserAgent⽂字列を識別して、PC/SPのレンダリング内容を分離 // モバイル判定 const isMobile = (req) =>

    { const md = new MobileDetect(req.headers['user-agent']); return !!md.mobile() && !md.tablet(); }; // モバイル判定結果に応じてレンダリングするコンポーネントを変更 <StaticRouter location={location} context={context}> {isMobile(req) ? <AppSP /> : <AppPC />} </StaticRouter> 2019/8/21 36
  37. ReactでのSEOを加味したアプローチ例 2019/8/21 37

  38. エンジニアが注意すべきSEO上のポイントの⼀例 • レンダリング • 正しく有効なマークアップ • ステータスコードの制御 • URL正規化 •

    ページの表⽰速度 • モバイルフレンドリー化 • 構造化データ (ほかにもあるよ) 2019/8/21 38
  39. 今回お話するトピック • レンダリング • 正しく有効なマークアップ • ステータスコードの制御 • URL正規化 •

    ページの表⽰速度 • モバイルフレンドリー化 • 構造化データ 2019/8/21 39
  40. 今回お話するトピック • レンダリング • 正しく有効なマークアップ • ステータスコードの制御 • URL正規化 •

    ページの表⽰速度 • モバイルフレンドリー化 • 構造化データ 2019/8/21 40
  41. レンダリング • FundsではReactでSSR • SSR時のstateをクライアント側でRehydrationし CSRできるようにしている 2019/8/21 41

  42. SSR with Rehydration FundsではReactのSSRにRehydrationでCSRを組み合わせています Browser Server state state API ②更新されたredux

    stateで renderToString ①レンダリングに 必要な情報取得 ③クライアントサイドに stateを引き継ぎ ④ブラウザ上で差分更新 2019/8/21 42
  43. SSR with Rehydration // react-redux の Providerでreactとredux stateを結合 const provider

    = ( <Provider store={store}> <StaticRouter location={location} context={context}> ... </StaticRouter> </Provider> ); // サーバサイドでレンダリング const renderedContent = renderToString(provider); ... // レンダリングしたコンテンツを埋め込み <div id="app">${renderedContent}</div> ②更新されたredux stateを注⼊しrenderToString on Server 2019/8/21 43
  44. SSR with Rehydration // state はrenderToStringで使⽤したRedux State const state =

    store.getState(); const serializedState = JSON.stringify(state); // SSRでレンダリングしておきクライアントサイドで利⽤ <script type="text/javascript">var __CWP_INITIAL_STORE=${serializedState};</script> // クライアントサイドでは以下のようにロード const store = configureStore(window.__CWP_INITIAL_STORE, history); ③クライアントサイドにstateを引き継ぎ on Server on Browser 2019/8/21 44
  45. SSR with Rehydration FundsではReactのSSRにRehydrationでCSRを組み合わせています Browser Server state state API ②更新されたredux

    stateで renderToString ①レンダリングに 必要な情報取得 ③クライアントサイドに stateを引き継ぎ ④ブラウザ上で差分更新 2019/8/21 45
  46. 今回お話するトピック • レンダリング • 正しく有効なマークアップ • ステータスコードの制御 • URL正規化 •

    ページの表⽰速度 • モバイルフレンドリー化 • 構造化データ 2019/8/21 46
  47. ソフト404エラー • ステータスコード200で、エラー⾵のページを表⽰している状態 • クローラーは404ページであることを認識できない 404 HTTP上では・・・ HTTP/2 200 Content-Encoding:

    gzip Accept-Ranges: bytes Cache-Control: max-age=604800 Content-Type: text/html; charset=UTF-8 … 2019/8/21 47
  48. SPA固有の404ページに関する問題 SPAでは⼀度200で返したHTML・JavaScriptから CSRでレンダリングするため、ステータスコードを404に設定できない 404 CSR前のページを表⽰ この時点で200 ⾒た⽬が404⾵のページをレンダリング 実際はステータスコード200 2019/8/21 48

  49. Fundsの404ページ ステータスコード404で404ページを表⽰(あたりまえですが・・・) 2019/8/21 49

  50. Fundsの404ページ ステータスコードに対応するフィールドをredux state上に定義 SSR時にredux stateを参照しステータスコードを変更する ステータス変更⽤のredux action⽣成 ステータスコードに対応するフィールドを更新 SSR時にstateからステータスコードを変更 Fundsで404ページを表⽰する場合の流れ

    2019/8/21 50
  51. 今回お話するトピック • レンダリング • 正しく有効なマークアップ • ステータスコードの制御 • URL正規化 •

    ページの表⽰速度 • モバイルフレンドリー化 • 構造化データ 2019/8/21 51
  52. 重複コンテンツとURL制御 複数のURLで同じコンテンツにアクセス可能だと、 本来得られるべき適切な評価が得られない ページ ページ /cat /cat/hoge ページ /cat/fuga ミスなのか、本当に表⽰したいページが複数あるのかわからない

    2019/8/21 52
  53. 重複コンテンツとURL制御 まずは正しいURL以外でアクセスできないようにするのが原則 ページ ページ /cat /cat/hoge ページ /cat/fuga 2019/8/21 53

  54. 重複コンテンツとURL制御 • react-router でURLに対応するコンポーネントを選択 • できる限りexactを指定 import { Route, Switch

    } from 'react-router’; return ( <Switch> <Route exact // exact を指定することで /cat/hoge などはマッチしない path="/cat” component={CatPage} /> ... 2019/8/21 54
  55. URL正規化 クエリパラメータの有無など、複数のURLでアクセス可能になる場合もある ページ /cat/1 ページ /cat/1?foo=1 2019/8/21 55

  56. URL正規化 ステータスコード 301でリダイレクト ページ /cat/1 ページ /cat/1?foo=1 正しいURLの場所をHTTP Responseで明⽰ リソースが直接参照できるURLを1つに限定する(URL正規化)

    HTTP/1.1 301 Location: /cat/1 2019/8/21 56
  57. rel=canonical によるURL正規化 301リダイレクトの代わりに rel=canonical で正しいURLを指定することで評価の分散を防⽌ ページ /cat/1 ページ /cat/1?foo=1 クローラーは

    /cat/1 の⽅をインデックスすべきURLと解釈し /cat/1?foo=1 はインデックスしない <link rel="canonical” href="https://example.com/cats/1"> 2019/8/21 57
  58. react-helmetでのメタ情報設定 // Helmet (react-helmet) import Helmet from 'react-helmet’; ... return

    ( <Helmet title=“ファンドを探す” meta={[ { name: 'description’, content: 'Funds(ファンズ)のファンド⼀覧ページです。', }, react-helmet はメタ要素を挿⼊するためのライブラリ <Helmet>で定義すると ⾃動的に <head /> 内へ含めてくれる 2019/8/21 58
  59. rel=canonical によるURL正規化 rel=canonicalを指定する場合も react-helmet を使⽤ // Helmet (react-helmet) import Helmet

    from 'react-helmet’; ... return ( <Helmet link={[ { ref: ‘canonical’, href: ‘https://funds.jp/fund/list', }, 2019/8/21 59
  60. react-helmet で設定可能なメタ要素の例 • title • meta description • SERPs(検索結果のページ)に表⽰される内容には影響する •

    meta robots • OGP • og:image など。Facebookなどでのシェアの際に有効 • Twitter Cards • Twitterでのシェアの際の表⽰ • rel=canonical • URL正規化で使⽤ このあたりの指定は SSR or Dynamic Renderingが必要 2019/8/21 60
  61. まとめ 公開サイトを開発するフロントエンドエンジニアなら SEOについても知っておこう 2019/8/21 61