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

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

![May-04-2020 20-10-49.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131737/b95b5670-18e2-95e5-468c-c56f76bd52e4.gif)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131737/a84e1ca0-f3b7-83d1-2986-bc61d621a141.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131737/2d0e80b7-30ee-5002-ae90-b7c8bdf03f51.png)

# 実装
## 大まかな説明

- モーダルを作成
    - [material-uiのモーダル](https://material-ui.com/components/modal/)を使う
        - 注意1：`open`は常に`true`にしておく
        - 注意2：`onClose`ではリダイレクトの関数を実施する
    - モーダルの中に`react-share`の好きなコンポーネントを置く
- `react-helmet`でmetaタグを設定する
    - metaタグ設定のコードは、モーダルの横に設置する
- `react-router`でルーティングする(※URLとOGPは1:1対応なので、ルーティングは必須)
    - [ネストされたルーティング](https://reacttraining.com/react-router/web/guides/quick-start/2nd-example-nested-routing)を参考にする
        - 注意：`/`配下に直接`:hogehoge`を指定することはできない
- Netlifyでホスティングする(※自前でprerenderingすれば他のホスティングサービスでも可)
    - [Netlify](https://www.netlify.com/)の[pre-rendering](https://docs.netlify.com/site-deploys/post-processing/prerendering/#how-it-works)によりSPAでもreact-helmetが機能する
        - 注意1：`public`ディレクトリ配下に`_redirects`ファイルを設置しないとreact-routerがうまく動かず、URLが機能しない
        - 注意2：[pre-rendering](https://docs.netlify.com/site-deploys/post-processing/prerendering/#how-it-works)の設定をし忘れない
        - 注意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

## コード
```tsx: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();

```

```tsx: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>
    )
}
```

```tsx: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動作確認

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

- https://cards-dev.twitter.com/validator
- https://developers.facebook.com/tools/debug/?locale=ja_JP

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

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