個人的にはServer side renderingはアーキテクチャ的に好きじゃないけどOpen Graph Protocolとかツイッターカードとかには対応したいなってことで、試みその1です。
追記
- 2017/6/3 - Netlify編を書きました。
- 2017/6/9 - CSRのもう1つの問題である初期ロードが遅い問題に対応するためにCode-Splittingを導入する記事を書きました。
- 2017/7/23 - Lambda@Edge編を書きました。
Prerenderとは
Prerenderはその名の通り事前にレンダリングして静的ファイルにすることです。静的ファイルになればWebサーバとかS3とかに置いて配信できるのでCDNの恩恵を受けたりOGPの対応ができたりします。PrerenderはPrerender.ioみたいなサービスを使うのがベターな気がしますが、今回はとりあえず自前でやってみます。
ちなみに今回のソースはここにあります。
手順
大体の流れはcreate-react-appのREADMEに書かれています。
Reactアプリを作る
ということでデモにはcreate-react-appで十分だと思うのでこれを使っていきます。
yarn create react-app prerender-demo
上記は初回実行時の場合はsudo付けないとエラーになります。
必要なパッケージを追加
とりあえずルーティング用にreact-routerを入れます。ルートごとのヘッダ部の書き換えにはreact-helmetを使います。helmet使っても実行中のユーザのヘッダを書き換えるだけで、実際にそのパスをシェアしても適切なリンクにはなりません。これを解決するためにreact-snapshotを使います。また、react-helmetの最新バージョンはreact-snapshotと一緒に使えないので4系を使います。
cd prerender-demo
yarn add react-router-dom react-helmet@4
yarn add -D react-snapshot
書き換え
react-routerとreact-helmet
今回はテスト用に2つのルートだけ作ります。まずはApp.js
を書き換えます。
import React, { Component } from 'react';
import Helmet from 'react-helmet';
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom'
import logo from './logo.svg';
import './App.css';
import {
Home,
About
} from './containers';
class App extends Component {
render() {
return (
<div className="App">
<Helmet title="Default title" />
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
<p className="App-intro">
<Router>
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
</ul>
<hr/>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
</div>
</Router>
</p>
</div>
);
}
}
export default App;
次にそれぞれのルート用のコンテナを作りてきとーにヘッダ情報を書いていきます。
import React from 'react';
import Helmet from 'react-helmet';
const Home = () => (
<div>
<Helmet
title="Home"
meta={[
{ name: 'twitter:card', content: 'summary' },
{ name: 'twitter:title', content: 'Home' },
{ name: 'twitter:description', content: 'description of Home' },
{ name: 'twitter:image', content: 'http://path/to/image' },
{ property: 'og:title', content: 'Home' },
{ property: 'og:type', content: 'website' },
{ property: 'og:url', content: 'http://path/to/this/url' },
{ property: 'og:image', content: 'http://path/to/image' },
{ property: 'og:description', content: 'description of Home' },
]}
/>
<h2>Home</h2>
</div>
)
export default Home;
About.jsはHomeをAboutに置換して画像を変えただけなので省略します。
react-snapshot
次にreact-snapshot向けの変更として、package.json
のbuild
セクションを以下のように変更します。
"build": "react-scripts build && react-snapshot"
そしてindex.jsで使うrenderをreact-snapshotのrenderメソッドに変更します。
import { render } from 'react-snapshot';
(略)
render(<App />, document.getElementById('root'));
これでyarn build
するとbuild
ディレクトリ以下に静的ファイルが出来上がります。
ただし、about.htmlができてしまうので、ルートを/aboutから/about.htmlに変更するかabout.htmlをaboutにリネームしてください。
配置して確認
あとはこれを配置して確認します。今回はテスト用に確保しておいたS3に置いてCloudFront経由で配信しました。
表示
以下のように問題なくブラウザから確認できました。
ちなみに諸事情によりドメインは伏せています。
Slackで確認
SlackはoEmbedやOGPやツイッターカードの情報を見てくれるので、試しにSlackに貼ってみたところ、ルートごとにきちんとOGPの情報が取得できることも確認できました。/
では自分のプロフィール画像、/about
ではQiitaのロゴを設定しましたが、設定通りに表示されておりdescriptionも反映されています。
最後に
という感じで結構簡単にOGP対応ができました。でもreact-snapshotのページにはexperimentalって書いてあるので、もう少しまともなアプリがちゃんと動くかも今度確認したいと思います。というかプロダクションとかでちゃんと使うなら最初に述べたPrerender.ioみたいなサービスを使った方が良い気もします。この辺は試したら追記したいと思います。
ただ恐らくprerender以外の方法でやるつもりでいるので、次回はまた別の方法を紹介したいと思います。