-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
158 lines (144 loc) · 5.35 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/* @flow */
// import SSRCaching from 'electrode-react-ssr-caching';
import type { $Request, $Response, Middleware } from 'express';
import React from 'react';
import { CodeSplitProvider, createRenderContext } from 'code-split-component';
import Helmet from 'react-helmet';
import { ServerRouter, createServerRenderContext } from 'react-router-v4-decode-uri';
import { ApolloProvider } from 'react-apollo';
import { renderToStringWithData } from 'react-apollo/server';
import { ApolloClient, createNetworkInterface } from 'apollo-client';
import render from './render';
import fetch from 'node-fetch';
import { parse, stringify } from 'olymp/query-string';
global.fetch = fetch;
/*if (process.env.NODE_ENV === 'production') {
SSRCaching.enableCaching();
SSRCaching.setCachingConfig({
components: {
ServerRouter: {
strategy: 'simple',
enable: true,
},
CodeSplitProvider: {
strategy: 'simple',
enable: true,
},
ApolloProvider: {
strategy: 'template',
enable: true,
},
GraphQL: {
strategy: 'template',
enable: true,
},
},
});
}*/
/**
* An express middleware that is capabable of doing React server side rendering.
*/
function universalReactAppMiddleware(request: $Request, response: $Response) {
/*if (process.env.NODE_ENV === 'production') {
SSRCaching.clearProfileData();
SSRCaching.enableProfiling();
}*/
// We should have had a nonce provided to us. See the server/index.js for
// more information on what this is.
if (typeof response.locals.nonce !== 'string') {
throw new Error('A "nonce" value has not been attached to the response');
}
const nonce = response.locals.nonce;
// It's possible to disable SSR, which can be useful in development mode.
// In this case traditional client side only rendering will occur.
if (process.env.DISABLE_SSR === 'true' || request.query['disable-ssr']) {
if (process.env.NODE_ENV === 'development') {
console.log('==> Handling react route without SSR'); // eslint-disable-line no-console
}
// SSR is disabled so we will just return an empty html page and will
// rely on the client to initialize and render the react application.
const html = render({
// Nonce which allows us to safely declare inline scripts.
nonce,
});
response.status(200).send(html);
return;
}
const uri = process.env.GRAPHQL_URL || `${process.env.URL || process.env.SERVER_URL}/graphql`;
const networkInterface = createNetworkInterface({
uri,
opts: {
credentials: 'same-origin',
headers: request.headers,
},
});
const client = new ApolloClient({
networkInterface,
dataIdFromObject: o => o.id,
ssrMode: true,
});
const App = require('app_alias').default;
// First create a context for <ServerRouter>, which will allow us to
// query for the results of the render.
const reactRouterContext = createServerRenderContext();
// We also create a context for our <CodeSplitProvider> which will allow us
// to query which chunks/modules were used during the render process.
const codeSplitContext = createRenderContext();
// Create the application react element.
const app = (
<CodeSplitProvider context={codeSplitContext}>
<ServerRouter stringifyQuery={stringify} parseQueryString={parse} location={decodeURI(request.url)} context={reactRouterContext}>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</ServerRouter>
</CodeSplitProvider>
);
renderToStringWithData(app).then((app) => {
// Generate the html response.
const html = render({
// Provide the full app react element.
app,
initialState: client.store.getState().apollo.data,
// Nonce which allows us to safely declare inline scripts.
nonce,
// Running this gets all the helmet properties (e.g. headers/scripts/title etc)
// that need to be included within our html. It's based on the rendered app.
// @see https://github.com/nfl/react-helmet
helmet: Helmet.rewind(),
// We provide our code split state so that it can be included within the
// html, and then the client bundle can use this data to know which chunks/
// modules need to be rehydrated prior to the application being rendered.
codeSplitState: codeSplitContext.getState(),
});
// Get the render result from the server render context.
const renderResult = reactRouterContext.getResult();
// Check if the render result contains a redirect, if so we need to set
// the specific status and redirect header and end the response.
if (renderResult.redirect) {
response.status(301).setHeader('Location', renderResult.redirect.pathname);
response.end();
return;
}
/*if (process.env.NODE_ENV === 'production') {
SSRCaching.enableProfiling(false);
console.log(request.url, JSON.stringify(SSRCaching.profileData, null, 2));
}*/
response
.status(
renderResult.missed
// If the renderResult contains a "missed" match then we set a 404 code.
// Our App component will handle the rendering of an Error404 view.
? 404
// Otherwise everything is all good and we send a 200 OK status.
: 200
)
.send(html);
}).catch((err) => {
console.log(err, uri);
response
.status(500)
.send(err);
});
}
export default (universalReactAppMiddleware : Middleware);