Comments (14)
This is my SSR middleware:
import path from 'path'
import React from 'react'
import { ServerStyleSheet } from 'styled-components'
import { renderToString, renderToNodeStream } from 'react-dom/server'
import { StaticRouter } from 'react-router'
import { HelmetProvider } from 'react-helmet-async'
import { ApolloProvider, getDataFromTree } from 'react-apollo'
import { ChunkExtractor } from '@loadable/server'
import config, { getClientConfig } from 'server/config'
import Head from 'server/components/Head'
import Body from 'server/components/Body'
import { asyncMiddleware } from 'server/utils/express'
import { createApolloClient } from 'server/graphql/apolloClient'
const nodeStats = path.resolve(
config.get('server.publicPath'),
'dist/node/loadable-stats.json',
)
const webStats = path.resolve(
config.get('server.publicPath'),
'dist/web/loadable-stats.json',
)
const ssr = asyncMiddleware(async (req, res) => {
const nodeExtractor = new ChunkExtractor({
statsFile: nodeStats,
outputPath: path.join(config.get('server.publicPath'), 'dist/node'),
})
const { default: App } = nodeExtractor.requireEntrypoint()
const webExtractor = new ChunkExtractor({ statsFile: webStats })
const apolloClient = createApolloClient()
const routerContext = {}
const helmetContext = {}
const app = (
<ApolloProvider client={apolloClient}>
<HelmetProvider context={helmetContext}>
<StaticRouter location={req.url} context={routerContext}>
<App />
</StaticRouter>
</HelmetProvider>
</ApolloProvider>
)
// Styled components
const sheet = new ServerStyleSheet()
let jsx = sheet.collectStyles(app)
jsx = webExtractor.collectChunks(app)
// Apollo
await getDataFromTree(jsx)
const apolloState = apolloClient.extract()
// Handle React router status
if (routerContext.status) {
res.status(routerContext.status)
}
// Handle React Router redirection
if (routerContext.url) {
const status = routerContext.status === 301 ? 301 : 302
res.redirect(status, routerContext.url)
return
}
const { helmet } = helmetContext
const stream = sheet.interleaveWithNodeStream(renderToNodeStream(jsx))
const head = renderToString(<Head helmet={helmet} extractor={webExtractor} />)
res.set('content-type', 'text/html')
res.write(
`<!DOCTYPE html><html ${helmet.htmlAttributes}><head>${head}</head><body ${
helmet.bodyAttributes
}><div id="main">`,
)
stream.pipe(
res,
{ end: false },
)
stream.on('end', () => {
const body = renderToString(
<Body
config={getClientConfig()}
helmet={helmet}
extractor={webExtractor}
apolloState={apolloState}
/>,
)
res.end(`</div>${body}</body></html>`)
})
})
export default ssr
from loadable-components.
@adardesign this is not the responsibility of loadable-components, you should try to use Apollo for GraphQL or another alternative for REST API.
from loadable-components.
I think I'm looking for the same thing as the OP
If I use loadable to load a component which loads data from an API (in this example using apollo client) then I end up with the Loading...
string rendered in the markup when doing SSR. If I load the component normally I get the results of the query rendered in the markup (using the standard method for apollo ssr as per here)
Is there a way to tell loadable to load fully in this situation? (I had figured this would be what extractor.collectChunks
would do but I must be mistaken)
I'm set up like this
Home content loaded with loadable
const Home = loadable(() => import('./atomic/templates/home/Home'))
Home component returns:
<Query
query={query}
>
{({ loading, error, data }) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
const items = data.listContent.map((item, index) =>
<li key={index}>
<Card
title={item.title}
path={item.path}
image={item.image}
/>
</li>
);
return (
<ul>
{items}
</ul>
)
}}
</Query>
On the server side:
const App = extractor.collectChunks(
<ApolloProvider client={client}>
<StaticRouter context={context} location={req.url}>
<Layout />
</StaticRouter>
</ApolloProvider>
);
renderToStringWithData(App)
.then((content) => {
const initialState = client.extract();
const helmetData = Helmet.renderStatic();
const html = <Html
content={content}
state={initialState}
helmetData={helmetData}
linkTags={extractor.getLinkElements()}
scripts={extractor.getScriptElements()}
/>;
res.status(200);
res.send(`<!doctype html>\n${ReactDOM.renderToStaticMarkup(html)}`);
res.end();
})
.catch(e => {
console.error('RENDERING ERROR:', e); // eslint-disable-line no-console
res.status(500);
res.end(
`An error occurred:\n\n${
e.stack
}`
);
});
});
from loadable-components.
Hello @bigwillch, I use Apollo too, you are lucky!
I figured out that react-apollo v2.2.4 si not compatible with "forwardRef" (used in "@loadable/component"). You should try to use react-apollo v2.3.0, it should solve your problem.
So use react-apollo@next
or [email protected]
.
Please tell me if it solves your problem.
from loadable-components.
Fantastic! That's got it working. I certainly am lucky!! :)
Thanks so much - and thanks for sharing the middleware. I'll definitely be cribbing from it.
Really amazing work on the module and documentation, thanks again
from loadable-components.
@neoziro await getDataFromTree(app)
maybe jsx
instead app
in your example
from loadable-components.
@xFloooo you are right, I edited it! Thanks!
from loadable-components.
@neoziro also in this example there is a memory leak
solution
do not run on every request
const nodeExtractor = new ChunkExtractor({
statsFile: nodeStats,
outputPath: path.join(config.get('server.publicPath'), 'dist/node'),
})
const { default: App } = nodeExtractor.requireEntrypoint()
const webExtractor = new ChunkExtractor({ statsFile: webStats })
const ssr = asyncMiddleware(async (req, res) => {
...
})
from loadable-components.
Hello @xFloooo, if there is a memory leak it is a problem. Where do you see a memory leak?
from loadable-components.
@neoziro, I'm not completely sure what it is @loadable/component
, but after removing the code from renderMiddleware
memory continues to flow, but in a much smaller amount.
I launched my application in debug mode
first snapshot - after 1 requests
second snapshot - after 15 requests
// package.json
"debug": "cross-env NODE_ENV=development npm run set-locale && node --harmony --inspect lib/server/index.js"
// rederMiddleware.js
import path from "path";
import { ChunkExtractor } from "@loadable/server";
import { jss, JssProvider, SheetsRegistry, ThemeProvider } from "react-jss";
import { ApolloProvider, getDataFromTree } from "react-apollo";
import { StaticRouter } from "react-router-dom";
import { Provider } from "mobx-react";
import theme from "../../../application/styles/themes/base";
import React from "react";
import { renderToNodeStream } from "react-dom/server";
import normalize from "normalize-jss";
import { Helmet } from "react-helmet";
let configs = require("../configs/index");
// let configRoutes = require("../../../configs/routes");
const ZipkinJavascriptOpentracing = require("zipkin-javascript-opentracing");
const tracer = require("../../../tracer/tracer").tracer;
const texts = require("../../../configs/text.config.json");
const Cookies = require("cookies");
const nodeStats = path.resolve(
__dirname,
"../../../public/dist/node/loadable-stats.json"
);
const webStats = path.resolve(
__dirname,
"../../../public/dist/web/loadable-stats.json"
);
const render = async function(req, res, next) {
// start render tracer
const child = tracer.startSpan("react render middleware", {
childOf: req.span
});
tracer.inject(
child,
ZipkinJavascriptOpentracing.FORMAT_HTTP_HEADERS,
req.traceHeaders
);
const apolloClient = req.apolloClient;
/*****************************/
try {
if (req.is404) {
throw new Error("Error application");
}
const nodeExtractor = new ChunkExtractor({
statsFile: nodeStats,
entrypoints: "app"
});
const {
default: App,
ApplicationModel
} = nodeExtractor.requireEntrypoint();
const webExtractor = new ChunkExtractor({
statsFile: webStats,
entrypoints: "app"
});
const cookies = new Cookies(req, res);
const preloadStateMobx = preloadStateData(req);
let applicationModel = new ApplicationModel(null, {});
applicationModel.preloadData(preloadStateMobx);
applicationModel.preloadTexts(texts);
const sheets = new SheetsRegistry();
const createGenerateClassName = () => {
let counter = 0;
return (rule, sheet) => `app${counter++}`;
};
const stores = {
routing: {},
ApplicationModel: applicationModel
};
const app = (
<ApolloProvider client={apolloClient}>
<StaticRouter location={req.url} context={{}}>
<Provider {...stores}>
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
</Provider>
</StaticRouter>
</ApolloProvider>
);
const jsx = webExtractor.collectChunks(app);
// Apollo
await getDataFromTree(jsx);
const apolloState = apolloClient.extract();
const helmet = Helmet.renderStatic();
const stream = renderToNodeStream(
<JssProvider
jss={jss}
registry={sheets}
generateClassName={createGenerateClassName()}
>
{jsx}
</JssProvider>
);
res.set("content-type", "text/html");
res.write(`<!DOCTYPE html>
<html>
<head>
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
${helmet.title.toString()}
${helmet.meta.toString()}
${helmet.link.toString()}
<meta charSet="utf-8" />
<meta
name="google-site-verification"
content="6BurPKJZGSClBM5QuL_myv0xHsorOa44i6RMpZsgPVc"
/>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<meta name="theme-color" content="#5812fe" />
${webExtractor.getLinkTags()}
${webExtractor.getStyleTags()}
</head>
<body>
<div id="root">`);
stream.pipe(
res,
{ end: false }
);
stream.on("end", async () => {
const cleanSheets_jss_app = sheets
.toString()
.replace(/\s{2,}|\r+|\n+/gm, "");
const cleanSheets_jss_normalize = jss
.createStyleSheet(normalize)
.toString()
.replace(/\s{2,}|\r+|\n+/gm, "");
res.end(`</div>
<style type="text/css" id="server-side-styles">
${cleanSheets_jss_normalize}
${cleanSheets_jss_app}
</style>
<script>window.texts=${JSON.stringify(texts)};</script>
<script>window.__MOBX_STATE__=${JSON.stringify(
preloadStateMobx
)};</script>
<script>window.__APOLLO_STATE__=${JSON.stringify(
apolloState
).replace(/</g, "\\\u003c")};</script>
${webExtractor.getScriptTags()}
</body>
</html>`);
});
} catch (e) {
console.log(e);
res.status(404);
res.send("error");
} finally {
/* finish render tracer **/
child.finish();
}
};
/**
* @param {RequestExstend} req
* @param res
* @param next
*/
function preloadStateData(req) {
let MetrikaId = configs.get("YANDEX_METRICA_ID");
let googleMetrikaId = configs.get("GOOGLE_METRIKA_ID");
let slackChannelId = configs.get("SLACK_CHANNEL_ID");
let slackChannelErrorsId = "CCABR59B7";
return {
location: req.url,
context: {},
data: {
currentRegion: req.site.currentRegion
? req.site.currentRegion
: null,
defaultRegion: req.site.regions.default
? req.site.regions.default
: null
},
MetrikaId: MetrikaId,
googleMetrikaId: googleMetrikaId,
slackChannelId: slackChannelId,
slackChannelErrorsId: slackChannelErrorsId,
siteCode: req.siteCode
};
}
module.exports = render;
after fix
first snapshot - after 1 requests
second snapshot - after 15 requests
// renderMiddleware
const nodeExtractor = new ChunkExtractor({
statsFile: nodeStats,
entrypoints: "app"
});
const { default: App, ApplicationModel } = nodeExtractor.requireEntrypoint();
const webExtractor = new ChunkExtractor({
statsFile: webStats,
entrypoints: "app"
});
const render = async function(req, res, next) {
...
})
from loadable-components.
@xFloooo Node has a special way to manage memory, garbage collector runs only when needed. I think there is no memory leak, I run it in production and my memory is stable.
from loadable-components.
Hello @neoziro! I use Apollo too. Got it working with your example of middleware! But could you explain a bit how exactly it works? )) I can't figure out what this part do:
const nodeExtractor = new ChunkExtractor({
statsFile: nodeStats,
outputPath: path.join(config.get('server.publicPath'), 'dist/node'),
})
const { default: App } = nodeExtractor.requireEntrypoint()
const webExtractor = new ChunkExtractor({ statsFile: webStats })
Why do we need both nodeExtractor and webExtractor? And what nodeExtractor actually do? Thank you!
from loadable-components.
Hello @evgeniysolodkov, I use nodeExtractor
to require the endpoint. It automatically handles cache in development and in production it will require the entrypoint even with the hash. You can do it yourself but it is just a helper to simplify things.
from loadable-components.
@adardesign this is not the responsibility of loadable-components, you should try to use Apollo for GraphQL or another alternative for REST API.
I'm also confused about this.It would be great if there were examples to tell me what to do.
from loadable-components.
Related Issues (20)
- problem with redial HOT 2
- How to change referencing external styles to preload mode
- Fallback running even when component has previously loaded HOT 1
- Webpack doesn't like @loadable/server 5.16.4 HOT 5
- Server is caching old request split chunks, and these are not being re-requested when a new request is made to the server.
- SassError: Undefined variable HOT 1
- Inline Chunk injection HOT 2
- Not working with latest @loadable/server & @loadable/babel-plugin `v5.16.0` version HOT 4
- How to lazyload a hook? HOT 3
- Fallback behaviour HOT 1
- Main.js is rendered on every page even if I splitted each page into chunks with @loadable/component
- error in ./node_modules/@loadable/component/dist/loadable.esm.mjs HOT 9
- Why ChunkExtractor is required? HOT 3
- Is website sync with repo?
- If I am using react18, do I still need loadable-components HOT 6
- Add esbuild support HOT 3
- react-native-web SSR support HOT 1
- Loading state is always true on first render HOT 3
- Add query parameter support HOT 3
- SSR is broken for component v5.16.2+ HOT 6
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from loadable-components.