この記事のコメントから、非常に多くの人々がCreate Reactアプリのエコシステム(別名React Scripts)に傾いていることが明らかになりました。 これはかなり合理的です これは最も一般的で使いやすい製品であり(Reactコミュニティの主要な人々からの構成とサポートが不足しているため)、さらに、アセンブリ、開発モード、テスト、カバレッジ統計など、必要なほぼすべてを備えています。 不足しているのは、サーバー側のレンダリングです。
公式ドキュメントの方法の1つとして、初期データをテンプレートに入れるか、 静的キャストを使用することが提案されています。 最初のアプローチでは、検索エンジンが静的HTMLに通常のインデックスを作成できません。2番目のアプローチでは、HTML以外の初期データの転送をサポートしていません( ドキュメントのフレーズ :これは、マークアップに含まれているもの以外の状態を渡しません)。 したがって、Reduxを使用する場合は、レンダリングに別のものを使用する必要があります。
Create React Appで使用するために記事の例を修正しました。現在はCreate React Serverと呼ばれ、次のコマンドでサーバーレンダリングを起動できます。
create-react-server --createRoutes src/routes.js --createStore src/store.js
この起動では、特別な構成は必要ありません。すべてはコマンドラインオプションで実行されます。 必要に応じて、独自のテンプレートとハンドラーを同じ方法でスリップできます。
少し叙情的な余談。 React Routerの作者が言うように 、彼らのサイトは問題なく、サーバーレンダリングなしでGoogleによってインデックスされています。 たぶんそう。 しかし、主な問題の1つはGoogleだけでなく、ユーザーへのコンテンツの高速配信であり、これはだまされる可能性のあるインデックス作成よりも重要です。
設置
まず、この例に必要なパッケージをインストールします。
npm install create-react-server --save-dev
.babelrc
ファイルまたはbabel
セクションをpackage.json
ファイルに追加します
{ "presets": [ "react-app" ] }
babel-preset-react-app
react-scripts
とともにbabel-preset-react-app
れますが、サーバーレンダリングのために明示的に参照する必要があります。
ページ(React Router Endpoint)
前と同じように、サーバーレンダリングの本質は非常に簡単です。サーバーでは、ルーターのルールに基づいて、ページに表示するコンポーネントを決定し、機能する必要があるデータを見つけ、このデータを要求し、HTMLをレンダリングし、このHTMLをデータと共に送信する必要があります顧客ごと。
サーバーは最終コンポーネントをgetInitialProps
し、そのコンポーネントでgetInitialProps
を呼び出します。このコンポーネント内で、Reduxアクションのディスパッチを行い、初期セットのprops
を返すことができます(Reduxが使用されない場合)。 このメソッドは、クライアントとサーバーの両方で呼び出されるため、データの初期ロードが大幅に簡素化されます。
// src/Page.js import React, {Component} from "react"; import {connect} from "react-redux"; import {withWrapper} from "create-react-server/wrapper"; import {withRouter} from "react-router"; export class App extends Component { static async getInitialProps({location, query, params, store}) { await store.dispatch(barAction()); return {custom: 'custom'}; // props }; render() { const {foo, bar, custom, initialError} = this.props; if (initialError) return (<pre> getInitialProps: {initialError.stack}</pre>); return ( <div>Foo {foo}, Bar {bar}, Custom {custom}</div> ); } } // Redux Provider App = connect(state => ({foo: state.foo, bar: state.bar})(App); // WrapperProvider, initialProps App = withWrapper(App); // React Router App = withRouter(App); export default App;
変数initialError
は、 getInitialProps
関数でエラーが発生した場合に重要になります。クライアントまたはサーバー上の動作は同じです。
404エラーのスタブとして使用されるページには、静的notFound
プロパティが必要です。
// src/NotFound.js import React, {Component} from "react"; import {withWrapper} from "create-react-server/wrapper"; class NotFound extends Component { static notFound = true; render() { return ( <div>404 Not Found</div> ); } } export default withWrapper(NotFound);
ルーター
createRoutes
関数はルーターのルールを返す必要があり、非同期ルートもサポートされますが、簡単にするために、ここではこれを省略します。
// src/routes.js import React from "react"; import {IndexRoute, Route} from "react-router"; import NotFound from './NotFound'; import App from './Page'; export default function(history) { return <Route path="/"> <IndexRoute component={App}/> <Route path='*' component={NotFound}/> </Router>; }
Redux
createStore
関数は、パラメーターとして初期状態を取り、新しいStore
を返す必要があります。
// src/store.js import {createStore} from "redux"; function reducer(state, action) { return state; } export default function (initialState, {req, res}) { if (req) initialState = {foo: req.url}; return createStore( reducer, initialState ); }
サーバーで関数が呼び出されると、2番目のパラメーターにはNodeJSからのRequestオブジェクトとResponseオブジェクトが含まれます。一部の情報を抽出して、初期状態にすることができます。
メインエントリポイント
すべてをまとめて、サーバーからinitialProps
を取得する特別なラッパーを追加しましょう。
// src/index.js import React from "react"; import {render} from "react-dom"; import {Provider} from "react-redux"; import {browserHistory, match, Router} from "react-router"; import {WrapperProvider} from "react-router-redux-middleware/wrapper"; import createRoutes from "./routes"; import createStore from "./store"; const Root = () => ( <Provider store={createStore(window.__INITIAL_STATE__)}> <WrapperProvider initialProps={window.__INITIAL__PROPS__}> <Router history={browserHistory}>{createRoutes()}</Router> </WrapperProvider> </Provider> ); render((<Root/>), document.getElementById('root'));
コンソールユーティリティを使用した単純なサーバーの起動
package.json
ファイルのscripts
セクションにスクリプトを追加します。
{ "build": "react-scripts build", "server": "create-react-server --createRoutes src/routes.js --createStore src/store.js }
そして走る
npm run build npm run server
ブラウザでhttp://localhost:3000
を開くと、サーバー上に準備されたページが表示されます。
このモードでは、サーバーアセンブリの結果はどこにも保存されず、毎回その場で計算されます。
APIを介してサーバーを起動し、アセンブリ結果を保存する
コマンドライン機能が不足している場合、またはサーバーアセンブリの結果を保存する場合は、CLIではなくAPIを使用していつでもサーバーを作成できます。
以前のbabel-cli
に加えて、それをbabel-cli
し、サーバーをビルドするために必要になります。
npm install babel-cli --save-dev
package.json
ファイルのscripts
セクションにスクリプトを追加します。
{ "build": "react-scripts build && npm run build-server", "build-server": "NODE_ENV=production babel --source-maps --out-dir build-lib src", "server": "node ./build-lib/server.js" }
したがって、クライアントパーツは引き続きReact Appの作成(React Scripts)によって収集され、サーバーパーツはBabelを使用して収集されます。Babelはすべてとsrc
を取得してbuild-lib
ます。
// src/server.js import path from "path"; import express from "express"; import {createExpressServer} from "create-react-server"; import createRoutes from "./createRoutes"; import createStore from "./createStore"; createExpressServer({ createRoutes: () => (createRoutes()), createStore: ({req, res}) => (createStore({})), outputPath: path.join(process.cwd(), 'build'), port: process.env.PORT || 3000 }));
実行:
npm run build npm run server
ここで、ブラウザーでhttp://localhost:3000
再度開くと、サーバー上に同じページが準備されていることがわかります。
完全なサンプルコードは、 https : //github.com/kirill-konshin/react-router-redux-middleware/tree/master/examples/create-react-appにあります 。