サーバーレンダリング
このガイドでは、Web向けReact NativeとReact Navigationを使用してReact Nativeアプリをサーバーレンダリングする方法について説明します。以下のケースについて説明します。
- リクエストURLに応じて正しいレイアウトをレンダリングする
- フォーカスされた画面に基づいて適切なページメタデータを設定する
前提条件
ガイドに従う前に、アプリがサーバーで正常にレンダリングされることを確認してください。そのためには、以下を確認する必要があります。
- 使用するすべての依存関係が、npmに公開する前にコンパイルされているため、Nodeで構文エラーが発生しない。
- Nodeが画像やフォントなどのアセットファイルを`require`できるように設定されている。そのためには、webpack-isomorphic-toolsを試すことができます。
- `react-native`が`react-native-web`にエイリアスされている。babel-plugin-module-resolverを使用してこれを行うことができます。
アプリのレンダリング
まず、React Navigationを使用せずにReact Native Webでサーバーレンダリングを行う方法の例を見てみましょう。
import { AppRegistry } from 'react-native-web';
import ReactDOMServer from 'react-dom/server';
import App from './src/App';
const { element, getStyleElement } = AppRegistry.getApplication('App');
const html = ReactDOMServer.renderToString(element);
const css = ReactDOMServer.renderToStaticMarkup(getStyleElement());
const document = `
<!DOCTYPE html>
<html style="height: 100%">
<meta charset="utf-8">
<meta httpEquiv="X-UA-Compatible" content="IE=edge">
<meta
name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover"
>
${css}
<body style="min-height: 100%">
<div id="root" style="display: flex; min-height: 100vh">
${html}
</div>
`;
ここで、`./src/App`は`AppRegistry.registerComponent('App', () => App)`があるファイルです。
アプリでReact Navigationを使用している場合、これはホームページによってレンダリングされた画面をレンダリングします。ただし、アプリでリンクを設定している場合は、クライアントでレンダリングされる内容と一致するように、サーバーでリクエストURLの正しい画面をレンダリングする必要があります。
`location` propにこの情報を渡すことで、`ServerContainer`を使用してこれを行うことができます。たとえば、Koaでは、コンテキスト引数から`path`プロパティと`search`プロパティを使用できます。
app.use(async (ctx) => {
const location = new URL(ctx.url, 'https://example.org/');
const { element, getStyleElement } = AppRegistry.getApplication('App');
const html = ReactDOMServer.renderToString(
<ServerContainer location={location}>{element}</ServerContainer>
);
const css = ReactDOMServer.renderToStaticMarkup(getStyleElement());
const document = `
<!DOCTYPE html>
<html style="height: 100%">
<meta charset="utf-8">
<meta httpEquiv="X-UA-Compatible" content="IE=edge">
<meta
name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover"
>
${css}
<body style="min-height: 100%">
<div id="root" style="display: flex; min-height: 100vh">
${html}
</div>
`;
ctx.body = document;
});
また、検索エンジン、オープングラフなどのために正しいドキュメントタイトルと説明を設定することもできます。そのためには、コンテナに`ref`を渡すことで、現在の画面のオプションを取得できます。
app.use(async (ctx) => {
const location = new URL(ctx.url, 'https://example.org/');
const { element, getStyleElement } = AppRegistry.getApplication('App');
const ref = React.createRef<ServerContainerRef>();
const html = ReactDOMServer.renderToString(
<ServerContainer
ref={ref}
location={location}
>
{element}
</ServerContainer>
);
const css = ReactDOMServer.renderToStaticMarkup(getStyleElement());
const options = ref.current?.getCurrentOptions();
const document = `
<!DOCTYPE html>
<html style="height: 100%">
<meta charset="utf-8">
<meta httpEquiv="X-UA-Compatible" content="IE=edge">
<meta
name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover"
>
${css}
<title>${options.title}</title>
<body style="min-height: 100%">
<div id="root" style="display: flex; min-height: 100vh">
${html}
</div>
`;
ctx.body = document;
});
画面に`title`オプションを指定していることを確認してください。
<Stack.Screen
name="Profile"
component={ProfileScreen}
options={{ title: 'My profile' }}
/>
404またはその他のステータスコードの処理
無効なURLの画面をレンダリングする場合は、サーバーから`404`ステータスコードも返す必要があります。
まず、ステータスコードを添付するコンテキストを作成する必要があります。これを行うには、サーバーとクライアントの両方でインポートする別のファイルに次のコードを配置します。
import * as React from 'react';
const StatusCodeContext = React.createContext();
export default StatusCodeContext;
次に、`NotFound`画面でコンテキストを使用する必要があります。ここでは、画面が見つからなかったことを示すために、値`404`を持つ`code`プロパティを追加します。
function NotFound() {
const status = React.useContext(StatusCodeContext);
if (status) {
staus.code = 404;
}
return (
<View>
<Text>Oops! This URL doesn't exist.</Text>
</View>
);
}
必要に応じて、このオブジェクトに追加情報を添付することもできます。
次に、サーバーのコンテキストに渡すステータスオブジェクトを作成する必要があります。デフォルトでは、`code`を`200`に設定します。次に、`ServerContainer`を持つ要素をラップする必要がある`StatusCodeContext.Provider`にオブジェクトを渡します。
// Create a status object
const status = { code: 200 };
const html = ReactDOMServer.renderToString(
// Pass the status object via context
<StatusCodeContext.Provider value={status}>
<ServerContainer ref={ref} location={location}>
{element}
</ServerContainer>
</StatusCodeContext.Provider>
);
// After rendering, get the status code and use it for server's response
ctx.status = status.code;
`ReactDOMServer.renderToString`でアプリをレンダリングした後、`NotFound`画面がレンダリングされた場合、`status`オブジェクトの`code`プロパティは`404`に更新されます。
たとえば、`401`を不正アクセスなどに使用する場合など、他のステータスコードにも同様のアプローチに従うことができます。
まとめ
- `ServerContainer`の`location` propを使用して、受信リクエストに基づいて正しい画面をレンダリングします。
- `ServerContainer`に`ref`を添付して、現在の画面のオプションを取得します。
- コンテキストを使用して、ステータスコードなどの詳細情報を添付します。