В двух словах проблема в том, что я на стороне сервера отрисовываю HTML-документ, а затем приложение React увлажняет и повторно отображает то, что уже есть.После этого момента приложение работает на стороне клиента просто замечательно.
Я использую React, Apollo Client (Boost 0.3.1), Node, Express и сервер Graphql, который у нас есть.
Смотрите это в действии здесь: https://www.slowdownshow.org/
В основном я пробовал то, что предлагается в документах: https://www.apollographql.com/docs/react/features/server-side-rendering
Вот что не ясно.Должен ли я предполагать, что если я реализую регидратацию магазина, запрос Apollo Client xhr на получение данных не должен произойти?Если это так, то проблема в том, что я попробовал то, что документы предлагают для регидратации магазина, но документ немного двусмысленный
<script>
window.__APOLLO_STATE__ = JSON.stringify(client.extract());
</script>
Что такое клиент в этом случае?Я считаю, что это ApolloClient.Но это метод, а не объект, если я использую его здесь, я получаю сообщения об ошибках вроде
Warning: Failed context type: Invalid context
client of type
function supplied to
Component , expected
object .
Если метод Store Rehydration не является способом предотвращения ненужных повторных рендеров на стороне клиента - мне не ясно, что это такое.
Вот соответствующий код сервера:
import React from 'react';
import ReactDOM from 'react-dom/server';
import { ApolloProvider, renderToStringWithData } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import FragmentMatcher from '../shared/graphql/FragmentMatcher';
import { HelmetProvider } from 'react-helmet-async';
import { ServerLocation } from 'apm-titan';
import App from '../shared/App';
import fs from 'fs';
import os from 'os';
import {
globalHostFunc,
replaceTemplateStrings,
isFresh,
apm_etag,
siteConfigFunc
} from './utils';
export default function ReactAppSsr(app) {
app.use((req, res) => {
const helmetContext = {};
const filepath =
process.env.APP_PATH === 'relative' ? 'build' : 'current/build';
const forwarded = globalHostFunc(req).split(':')[0];
const siteConfig = siteConfigFunc(forwarded);
const hostname = os.hostname();
const context = {};
const cache = new InMemoryCache({ fragmentMatcher: FragmentMatcher });
let graphqlEnv = hostname.match(/dev/) ? '-dev' : '';
graphqlEnv = process.env.NODE_ENV === 'development' ? '-dev' : graphqlEnv;
const graphqlClient = (graphqlEnv) => {
return new ApolloClient({
ssrMode: false,
cache,
link: createHttpLink({
uri: `https://xxx${graphqlEnv}.xxx.org/api/v1/graphql`,
fetch: fetch
})
});
};
let template = fs.readFileSync(`${filepath}/index.html`).toString();
const component = (
<ApolloProvider client={graphqlClient}>
<HelmetProvider context={helmetContext}>
<ServerLocation url={req.url} context={context}>
<App forward={forwarded} />
</ServerLocation>
</HelmetProvider>
</ApolloProvider>
);
renderToStringWithData(component).then(() => {
const { helmet } = helmetContext;
let str = ReactDOM.renderToString(component);
const is404 = str.match(/Not Found\. 404/);
if (is404?.length > 0) {
str = 'Not Found 404.';
template = replaceTemplateStrings(template, '', '', '', '');
res.status(404);
res.send(template);
return;
}
template = replaceTemplateStrings(
template,
helmet.title.toString(),
helmet.meta.toString(),
helmet.link.toString(),
str
);
template = template.replace(/__GTMID__/g, `${siteConfig.gtm}`);
const apollo_state = ` <script>
window.__APOLLO_STATE__ = JSON.stringify(${graphqlClient.extract()});
</script>
</body>`;
template = template.replace(/<\/body>/, apollo_state);
res.set('Cache-Control', 'public, max-age=120');
res.set('ETag', apm_etag(str));
if (isFresh(req, res)) {
res.status(304);
res.send();
return;
}
res.send(template);
res.status(200);
});
});
}
на стороне клиента:
import App from '../shared/App';
import React from 'react';
import { hydrate } from 'react-dom';
import { ApolloProvider } from 'react-apollo';
import { HelmetProvider } from 'react-helmet-async';
import { client } from '../shared/graphql/graphqlClient';
import '@babel/polyfill';
const graphqlEnv = window.location.href.match(/local|dev/) ? '-dev' : '';
const graphqlClient = client(graphqlEnv);
const Wrapped = () => {
const helmetContext = {};
return (
<HelmetProvider context={helmetContext}>
<ApolloProvider client={graphqlClient}>
<App />
</ApolloProvider>
</HelmetProvider>
);
};
hydrate(<Wrapped />, document.getElementById('root'));
if (module.hot) {
module.hot.accept();
}
graphqlCLinet.js:
import fetch from 'cross-fetch';
import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import FragmentMatcher from './FragmentMatcher';
const cache = new InMemoryCache({ fragmentMatcher: FragmentMatcher });
export const client = (graphqlEnv) => {
return new ApolloClient({
ssrMode: true,
cache,
link: createHttpLink({
uri: `https://xxx${graphqlEnv}.xxx.org/api/v1/graphql`,
fetch: fetch
})
});
};
FragmentMatcher.js:
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
const FragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData: {
__schema: {
types: [
{
kind: 'INTERFACE',
name: 'resourceType',
possibleTypes: [
{ name: 'Episode' },
{ name: 'Link' },
{ name: 'Page' },
{ name: 'Profile' },
{ name: 'Story' }
]
}
]
}
}
});
export default FragmentMatcher;
См. Повторное рендеринг на стороне клиента в действии
https://www.slowdownshow.org/
В рабочей версии кода выше я пропускаю регидратацию состояния window.__APOLLO_STATE__ = JSON.stringify(${graphqlClient.extract()});
, так как я не делаюэто работает