サーバー側のレンダリングとは何ですか?必要ですか?

こんにちは、Habr!



新年には、サーバー側のレンダリングに関するシード記事から会話を始めましょう。興味があれば、Nuxt.jsに関するより最近の出版物と、この方向でのさらなる出版物が可能です。



主にインタラクティブなWebページと単一ページアプリケーションの作成を目的とした最新のJavaScriptフレームワークとライブラリの出現により、ユーザーにページを表示するプロセス全体が大きく変化しました。



完全にJSで生成されたアプリケーションがブラウザに登場する前は、HTTP呼び出しに応答してHTMLがクライアントに提供されていました。これは、コンテンツを含む静的HTMLファイルを返すか、サーバー側の言語(PHP、Python、またはJava)を使用して応答を処理することにより、より動的な方法で実行できます。



このソリューションを使用すると、「途中」のリクエストに費やされる時間がなくなるため、標準のリクエストレスポンスサイトよりもはるかに高速に実行されるレスポンシブサイトを作成できます。



Reactで記述されたサイトへの要求に対してサーバーから送信される一般的な応答は、次のようになります。



<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <link rel="shortcut icon" href="/favicon.ico">
    <title>React App</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="/app.js"></script>
  </body>
</html>
      
      





この応答を選択すると、ブラウザはapp.js



アプリケーションを含む「パッケージ」も選択し、 1〜2秒後にページ全体をレンダリングします。



この時点で、ブラウザの組み込みHTMLインスペクタを使用して、レンダリングされたすべてのHTMLを表示できます。ただし、ソースコードを見ると、上記のHTML以外は表示されません。



なぜこれが問題なのですか?



この動作は、ほとんどのユーザーにとって、またはアプリケーションの開発時に問題になることはありませんが、次の場合は望ましくない可能性があります。



  • -, ,
  • , ,


人口統計学的な観点から、ターゲットオーディエンスがこれらのグループのいずれかに属している場合、サイトでの作業は不便になります。特に、ユーザーは「読み込み中...」という記号(さらに悪いことに、空白の画面)を見つめながら長時間待つ必要があります。



「わかりましたが、人口統計学的に、私のターゲットオーディエンスは間違いなくこれらのグループの1つではないので、心配する必要がありますか?」



クライアントがレンダリングしたアプリケーションを使用する際に考慮すべき点は、 検索エンジンソーシャルメディアプレゼンスの2つです。



今日、すべての検索エンジンの中で、ページを表示する前にサイトを表示し、そのJSを考慮に入れる機能を備えているのはGoogleだけです。さらに、Googleはサイトのインデックスページを表示できますが、ルーターのあるサイトをナビゲートする際に問題が発生する可能性があることがわかっています。



これは、サイトがGoogle以外の検索エンジン結果のトップに上がるのが非常に難しいこと意味し ます。



同じ問題は、Facebookなどのソーシャルネットワークでも見られます。サイトへのリンクが共有されている場合、その名前もプレビュー画像も正しく表示されません。



この問題を解決する方法



それを解決する方法はいくつかあります。



A-サイトのすべての主要ページを静的に保つようにしてください



ユーザーが自分のユーザー名でログインする必要があるプラットフォームサイトが作成され、システムにログインしないと、コンテンツが訪問者に提供されないため、サイトの静的な(HTMLで記述された)公開ページ、特にインデックス、「会社概要」、「連絡先」を残してみることができ ます。 「そして、それらを表示するときにJSを使用しないでください



コンテンツはログイン要件によって制限されているため、検索エンジンによってインデックスが作成されたり、ソーシャルメディアで共有したりすることはできません。



B-ビルド中にアプリケーションの一部をHTMLページとして生成します



プロジェクトにreact-snapshotなどのライブラリを追加でき ます。これらは、アプリケーションのページのHTMLコピーを生成し、専用のディレクトリに保存するために使用されます。次に、このディレクトリはJSパッケージとともに展開されます。したがって、HTMLは応答とともにサーバーから提供され、サイトはJavaScriptを無効にしているユーザーや検索エンジンなどにも表示されます。



通常、react-snapshotの構成は簡単です。ライブラリをプロジェクトに追加し、ビルドスクリプトを次のように変更するだけです。



"build": "webpack && react-snapshot --build-dir static"





このソリューションの欠点は次のとおりです。生成するすべてのコンテンツはビルド時に利用可能である必要があります。APIにアクセスして取得することはできません。また、ユーザーから提供されたデータに依存するコンテンツを事前に生成することもできません。 (たとえば、URLから)。



C-サーバーレンダリングを使用するJSアプリケーションを作成します



今日の世代のJSアプリケーションの最大のセールスポイントの1つは、クライアント(ブラウザー)とサーバーの両方で実行できることです。これにより、ビルド時にコンテンツがまだわかっていない、より動的なページのHTMLを生成できます。これらのアプリケーションは、「同形」または「ユニバーサル」と呼ばれることがよくあります。



Reactで最も人気のある2つのサーバー側レンダリングソリューションは次のとおりです。





独自のSSR実装を作成する



重要:Reactアプリケーション用に独自のSSR実装を自分で作成しようとする場合は、サーバーにノードバックエンドを提供する必要があります。githubページの場合のように、このソリューションを静的ホストにデプロイすることはできません。



最初に行う必要があるのは、他のReactアプリケーションと同じようにアプリケーションを作成することです。



エントリポイントを作成しましょう:



// index.js
import React from 'react';
import { render } from 'react-dom';
import App from './App.js';render(<App />, document.getElementById('root'));
      
      







そして、コンポーネントアプリケーション(アプリ):



// App.js
import React from 'react';const App = () => {
  return (
    <div>
      Welcome to SSR powered React application!
    </div>
  );
}
      
      





また、アプリケーションをロードするための「ラッパー」:



// index.html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <div id="root"></div>
    <script src="/bundle.js"></script>
  </body>
</html>
      
      





ご覧のとおり、アプリケーションは非常にシンプルです。この記事では、正しいWebpack + babelアセンブリを生成するために必要なすべての手順を実行するわけではありません。

アプリケーションを現在の状態で起動すると、ウェルカムメッセージが画面に表示されます。ソースコードを見ると、ファイルの内容が index.html



表示されますが、ウェルカムメッセージは表示されません。この問題を解決するために、サーバーレンダリングを追加しましょう。まず、3つのパッケージを追加しましょう。



yarn add express pug babel-node --save-dev
      
      





Expressはノード用の強力なWebサーバーであり、pugはexpressで使用できるテンプレートエンジンであり、babel-nodeはオンザフライの変換を提供するノードのラッパーです。



まず、ファイルindex.html



コピーして次のように保存し ましょう index.pug







// index.pug
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <div id="root">!{app}</div>
    <script src="bundle.js"></script>
  </body>
</html>
      
      





ご覧のとおり、HTMLに挿入されているものを除いて、ファイルはあまり変更されていません !{app}



これは、pug



後で実際のHTMLに置き換えられる変数 です。



サーバーを作成しましょう:



// server.jsimport React from 'react';
import { renderToString } from 'react-dom/server';
import express from 'express';
import path from 'path';import App from './src/App';const app = express();
app.set('view engine', 'pug');
app.use('/', express.static(path.join(__dirname, 'dist')));app.get('*', (req, res) => {
  const html = renderToString(
    <App />
  );  res.render(path.join(__dirname, 'src/index.pug'), {
    app: html
  });
});app.listen(3000, () => console.log('listening on port 3000'));
      
      





このファイルを順番に分析してみましょう。



import { renderToString } from 'react-dom/server';
      
      





react-domライブラリには、renderToString



私たちが知っているものと同じように機能する別の名前付きexport含まれ render



ていますが、DOMはレンダリングされず、HTMLが文字列としてレンダリングされます。



const app = express();
app.set('view engine', 'pug');
app.use('/', express.static(path.join(__dirname, 'dist')));
      
      







新しいエクスプレスサーバーインスタンスを作成し、テンプレートエンジンを使用することを伝えます pug



。この場合、通常のファイルの読み取りと「検索と置換」操作の実行で問題を解決できますが、このアプローチは実際には効果がなく、ファイルシステムへの複数のアクセスやキャッシュの問題が原因で問題が発生する可能性があります。



最後の行で、エクスプレスにディレクトリ内のファイルを検索するように指示し dist



、リクエスト(たとえば/bundle.js



)がこのディレクトリに存在するファイルと一致する場合は 、それを返します。



app.get('*', (req, res) => {
});
      
      







ここで、存在しないファイルを含め、一致しないすべてのURLにハンドラーを追加するようにexpressに指示します index.html



(覚えているようにindex.pug



名前をに変更し ましたが、ディレクトリにはありません dist



)。



const html = renderToString(
  <App />
);
      
      





助けrenderToString



借り て、アプリケーションを表示します。コードはエントリポイントとまったく同じように見えますが、そのような一致はオプションです。



res.render(path.join(__dirname, 'src/index.pug'), {
  app: html
});
      
      





HTMLをレンダリングしたので、expressに、応答としてファイルをレンダリングしindex.pug



、変数app



を受け取ったHTMLに置き換える ように指示します



app.listen(3000, () => console.log('listening on port 3000'));
      
      





最後に、サーバーが起動し、ポート3000でリッスンするように構成します

。次に、必要なスクリプトをpackage.json



次の場所に追加するだけです



"scripts": {
  "server": "babel-node server.js"
}
      
      





ここで、を呼び出す yarn run server



ことにより、サーバーが実際に実行されているという確認を受け取る必要があります。ブラウザlocalhost:3000に移動し ます。ここでも、アプリケーションが表示されます。この段階でソースコードを見ると、次のことがわかります。



<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <div id="root">div data-reactroot="">Welcome to SSR powered React application!</div></div>
    <script src="bundle.js"></script>
  </body>
</html>
      
      





すべてがこのように見える場合は、サーバーレンダリングが期待どおりに機能していることを意味し、アプリケーションの拡張を開始できます。



なぜまだbundle.jsが必要なのですか?



ここで検討するこのような非常に単純なアプリケーションの場合、bundle.jsを含める必要はありません。このファイルがなくても、アプリケーションは機能します。ただし、実際のアプリケーションの場合は、このファイルを含める必要があります。



これにより、JavaScriptを処理できるブラウザが作業を引き継ぎ、クライアント側ですでにページを操作できるようになります。JSの解析方法がわからないブラウザは、サーバーが返した目的のHTMLを使用してページに移動します。



覚えておくべきこと



サーバーのレンダリングは非常に単純に見えますが、アプリケーションを開発するときは、一見しただけでははっきりしないトピックに注意を払う必要があります。



  • , , . , , HTML, this.state



    ,
  • componentDidMount



    — , , . , , . , ( res.render



    ) , . -
  • react (. @reach/router react-router) , URL, . !



All Articles