【React】モーダルごとに別々のOGPを設定する【SPA】


やりたいこと

  • webアプリで、画像をモーダルで表示したい!
  • そのモーダルにSNSシェアボタンを設置したい
  • そのモーダルからSNSにシェアすると、その画像がOGPで表示されるようにしたい!
  • ただし、アプリ連携はさせたくない

実装

大まかな説明

  • モーダルを作成
    • material-uiのモーダルを使う
      • 注意1:openは常にtrueにしておく
      • 注意2:onCloseではリダイレクトの関数を実施する
    • モーダルの中にreact-shareの好きなコンポーネントを置く
  • react-helmetでmetaタグを設定する
    • metaタグ設定のコードは、モーダルの横に設置する
  • react-routerでルーティングする(※URLとOGPは1:1対応なので、ルーティングは必須)
  • Netlifyでホスティングする(※自前でprerenderingすれば他のホスティングサービスでも可)
    • Netlifypre-renderingによりSPAでもreact-helmetが機能する
      • 注意1:publicディレクトリ配下に_redirectsファイルを設置しないとreact-routerがうまく動かず、URLが機能しない
      • 注意2:pre-renderingの設定をし忘れない
      • 注意3:ビルドの結果出力されるディレクトリの指定を間違えると、ビルドは成功しても何も表示されない

ライブラリなどのバージョン

バージョンが異なるとコードや動作が異なる場合があります。

  • node v13.11.0
  • react v16.13.1
  • @material-ui/core v4.9.12
  • react-router-dom v5.1.2
  • react-share v4.1.0
  • react-helmet v6.0.0

コード

src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import * as serviceWorker from './serviceWorker';
import {
  BrowserRouter as Router,
  Switch,
  Route,
} from "react-router-dom";
import Lives from './components/Lives';

ReactDOM.render(
  <React.StrictMode>
    <Router>
      <Switch>
        <Route exact path="/">
          <App />
        </Route>
        <Route path="/lives">
          <Lives />
        </Route>
      </Switch>
    </Router>
  </React.StrictMode>,
  document.getElementById('root')
);
serviceWorker.unregister();

src/components/Lives.tsx
import React from 'react';
import { useRouteMatch, Link, Switch, Route } from 'react-router-dom';
import { Button } from '@material-ui/core';
import YourModal from './YourModal';

export default function Lives() {
    let { path, url } = useRouteMatch()
    return (
        <div>
            <Switch>
                <Route path={`${path}/:liveId`}>
                    <YourModal />
                </Route>
            </Switch>
            <Link to={`${url}/xxxxxxxxxx`}>
                <Button variant="contained">Open Modal</Button>
            </Link>
        </div>
    )
}
src/components/YourModal.tsx
import React from 'react';
import { useLocation, useHistory } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import { Modal } from '@material-ui/core';
import { TwitterShareButton, TwitterIcon } from 'react-share';

export default function YourModal() {
    const baseUrl = "http://example.com"
    const currentUrl: string = baseUrl + useLocation().pathname
    const imgUrl = "https://source.unsplash.com/random"

    const history = useHistory()
    const handleClose = () => {
        history.push("/lives")
    }

    return (
        <div>
            <Helmet
                title={`Lives&Lives`}
                meta={[
                    { name: 'twitter:card', content: 'summary_large_image' },
                    { name: 'twitter:site', content: '@IkkoKojima' },
                    { name: 'twitter:creator', content: '@IkkoKojima' },
                    { property: 'og:title', content: 'Lives&Lives' },
                    { property: 'og:type', content: 'website' },
                    { property: 'og:url', content: currentUrl },
                    { property: 'og:image', content: imgUrl },
                    { property: 'og:description', content: '動物たちのイキイキとした姿をみんなで観察' },
                ]}
            />
            <Modal
                open={true}
                onClose={handleClose}
                style={{
                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'center',
                }}
            >
                <div style={{ background: 'white' }}>
                    <img src={imgUrl} alt="alt-text" style={{ width: "600px" }} />
                    <TwitterShareButton url={currentUrl} >
                        <TwitterIcon round />
                    </TwitterShareButton>
                </div>
            </Modal>
        </div>
    )
}
public/_redirects
/* /index.html 200

OGP動作確認

この辺が使える。デプロイした後しか動作しないので注意。

最後に

自分用の備忘録として作成したのですごく雑で申し訳ない。なにぶん、数時間ハマった後なので疲れているのです...

不明点や記述のミスなどありましたら、コメントくださいmm