score:2
I have never tried @loadable/components
, but I do similar stuff (SSR + code splitting + data pre-fetching) with a custom implementation of code splitting, and I believe you should change your data pre-fetching approach.
If I got you right, your problem is that you are trying to intervene into the normal React rendering process, deducing in advance what components will be used in your render, and thus which data should be pre-fetched. Such intervention / deduction is just not a part of React API, and although I saw different people use some undocumented internal React stuff to achieve it, it all fragile in long term run, and prone to issues like you have.
I believe, a much better bullet-proof approach is to perform SSR as a few normal rendering passes, collecting in each pass the list list of data to be pre-fetch, fetching them, and then repeating the render from the very beginning with updated state. I am struggling to come up with a clear explanation, but let me try with such example.
Say, a component <A>
somewhere in your app tree depends on async-fetched data, which are supposed to be stored at some.path
of your Redux store. Consider this:
- Say you start with empty Redux store, and you also have you SSR context (for that you may reuse StaticRouter's
context
, or create a separate one with React's Context API). - You do the very basic SSR of entire app with
ReactDOMServer.renderToString(..)
. - When the renderer arrives to render the component
<A>
somewhere in your app's tree, no mater whether it is code-splitted, or not, if everything is set up correctly, that component will have access both to Redux store, and to the SSR context. So, if<A>
sees the current rendering happens at the server, and there is no data pre-fetched tosome.path
of Redux store,<A>
will save into SSR context "a request to load those data", and renders some placeholder (or whatever makes sense to render without having those data pre-fetched). By the "request to load those data" I mean, the<A>
can actually fire an async function which will fetch the data, and push corresponding data promise to a dedicated array in context. - Once
ReactDOMServer.renderToString(..)
completes you'll have: a current version of rendered HTML markup, and an array of data fetching promises collected in SSR context object. Here you do one of the following:- If there was no promises collected into SSR context, then your rendered HTML markup is final, and you can send it to the client, along with the Redux store content;
- If there are pending promises, but SSR already takes too long (counting from (1)) you still can send the current HTML and current Redux store content, and just rely on the client side to fetch any missing data, and finish the render (thus compromising between server latency, and SSR completeness).
- If you can wait, you wait for all pending promises; add all fetched data to the correct locations of your Redux store; reset SSR context; and then go back to (2), repeating the render from the begining, but with updated Redux store content.
You should see, if implemented correctly, it will work great with any number of different components relying on async data, no matter whether they are nested, and how exactly you implemented code-splitting, routing, etc. There is some overhead of repeated render passes, but I believe it is acceptable.
A small code example, based on pieces of code I use:
SSR loop (original code):
const ssrContext = {
// That's the initial content of "Global State". I use a custom library
// to manage it with Context API; but similar stuff can be done with Redux.
state: {},
};
let markup;
const ssrStart = Date.now();
for (let round = 0; round < options.maxSsrRounds; ++round) {
// These resets are not in my original code, as they are done in my global
// state management library.
ssrContext.dirty = false;
ssrContext.pending = [];
markup = ReactDOM.renderToString((
// With Redux, you'll have Redux store provider here.
<GlobalStateProvider
initialState={ssrContext.state}
ssrContext={ssrContext}
>
<StaticRouter
context={ssrContext}
location={req.url}
>
<App />
</StaticRouter>
</GlobalStateProvider>
));
if (!ssrContext.dirty) break;
const timeout = options.ssrTimeout + ssrStart - Date.now();
const ok = timeout > 0 && await Promise.race([
Promise.allSettled(ssrContext.pending),
time.timer(timeout).then(() => false),
]);
if (!ok) break;
// Here you should take data resolved by "ssrContext.pending" promises,
// and place it into the correct paths of "ssrContext.state", before going
// to the next SSR iteration. In my case, my global state management library
// takes care of it, so I don't have to do it explicitly here.
}
// Here "ssrContext.state" should contain the Redux store content to send to
// the client side, and "markup" is the corresponding rendered HTML.
And the logic inside a component, which relies on async data, will be somewhat like this:
function Component() {
// Try to get necessary async from Redux store.
const data = useSelector(..);
// react-router does not provide a hook for accessing the context,
// and in my case I am getting it via my <GlobalStateProvider>, but
// one way or another it should not be a problem to get it.
const ssrContext = useSsrContext();
// No necessary data in Redux store.
if (!data) {
// We are at server.
if (ssrContext) {
ssrContext.dirty = true;
ssrContext.pending.push(
// A promise which resolves to the data we need here.
);
// We are at client-side.
} else {
// Dispatch an action to load data into Redux store,
// as appropriate for your setup.
}
}
return data ? (
// Return the complete component render, which requires "data"
// for rendering.
) : (
// Return an appropriate placeholder (e.g. a "loading" indicator).
);
}
Source: stackoverflow.com
Related Query
- React Router v5 accompanied with Code Splitting, and Data Prefetching with the use of Server Side Rendering
- the pros and const of react ssr with code splitting and now React.Lazy
- CSS Code Splitting with Webpack 2 and React Router
- How to fetch the new data in response to React Router change with Redux?
- React Router V4: How to render a modal in the same screen changing only the url and then with that url, be able to rebuild the whole screen
- set chunk path a with webpack and react lazy code splitting
- React router - pass api data to the linked component to open with a new page
- React code splitting and server side rendering with System.import or require.ensure
- How to retrieve the parameter after a hash (#) with React Router and useParams hook?
- React router v4 broswer history not working with code splitting
- what is the best way to use react router V6 navigation with redux and redux thunk actions?
- React useEffect(), fetch data with React Hooks and set the formik form
- React Router Link with params not reloading page with new data from componentDidMount and Redux axios data fetching
- React Router v6 : How to render multiple component inside and outside a div with the same path
- react scroll to a component without react router and with most basic code
- Trying to implement a React "Lazy" route for code splitting with React Route and Webpack
- How to fetch data and make a route with the same endpoint React
- React router - code splitting lazy imports. Switch with Suspense outside
- Iterate through a for loop with the help of react and read out the data record
- Im using React Router and I want one of my route links with path="/" to be the home page on reload.. How can you do that?
- How to pass data to another component not in the URL with React Router
- Where and How to request data asynchronously to be passed down as props with React Router (v 1)
- React js with Laravel (5.1): Removal of the URL hash causes laravel router to kick in and fail
- how to use react fetch() with useEffect hook and map the fetched data
- React Context API, Hook loading twice. Once without the data and once with the data
- How to combine the previous data with the new data in React and display it at the same time
- Upload file (image) and data at the same time with react and node
- Using React Router with Switch and can't get the page to render different components when changing routes
- Code splitting with React Loadable and routes with containers
- navigating with react router push instead of link causes the application to not update and throw errors because actions are missing
More Query from same tag
- Push to another view from main component React Router 4
- Using the map function in react and parsing data down to child components
- key value mapping with array object
- Send object in value attribute with react
- React js Calling Component inside component
- filtering array based on each letter from input
- use a component in react again
- Prettier / ESLint / React
- How to show the whole table content from backendless?
- How to select option with JavaScript?
- ReduxRouterEngine Error: TypeError: Cannot read property 'then' of undefined
- Why is this string comparison in Jest's expect not failing?
- react custom dropdown not updating state
- How do I get Promises to work when using Jest CLI?
- How to add a key conditionaly in JavaScript object in the given code
- How do i update Auth0 lock logo from my React App using hooks
- fetching Unique value in React from map?
- API Design: Caching “partial” nested objects
- How to paginate Firestore data in react?
- If we have two Counter components in React, do we have to duplicate the Counter code?
- `No routes matched location "/" ` warning shown on the console
- Number of console logs in component body not equal to number of rerenders?
- Redirect in React not wroking
- Why child Styled-components is not applying styles?
- Cannot retrieve images in my react component
- How to set absolute positionning in react component
- Loading initial values from state in redux form
- How to add/change progress value to the Progress Bar in Semantic UI (CSS) in ReactJS
- State Doesn't calculate the new value : (
- Babelify ParseError 'import' and 'export' may appear only with 'sourceType: module'