React, SSR & Data without Redux

What is the recommended approach to handle data loading while using React and SSR without using Redux?

1 Like

It’s pretty straightforward with react-router. You use meteor/server-render's onPageLoad hook server side to match the route. Load data for matched components. Set a window variable (window.__INITIAL_STATE__) and then read that variable client-side and use it when you hydrate.

1 Like

Do you have an example repos that show this in action. Would love to check out something more fleshed out.

Redux makes this much easier and you should consider it, but here’s roughly how I would do it without redux.

// SERVER
import { Promise } from 'meteor/promise';
import { onPageLoad } from 'meteor/server-render';

import React from 'react';
import { Helmet } from 'react-helmet';
import { renderToString } from 'react-dom/server';

import StaticRouter from 'react-router-dom/StaticRouter';
import { matchRoutes, renderRoutes } from 'react-router-config';

import routes from './imports/ui/routes';

onPageLoad(async sink => {
  const { request: { url, browser }, arch } = sink;
  if (arch === 'web.browser') {
    const branch = matchRoutes(routes, url.pathname);
    const promises = branch.map(({ route, match }) => {
      const { fetchData } = route.component;
      return fetchData instanceof Function ? fetchData(match) : Promise.resolve(null);
    });
    const [fetchedData] = await Promise.all(promises);
    const context = {};
    const content = renderToString(
        <StaticRouter location={url} context={context}>
          {renderRoutes(routes, fetchedData)}
        </StaticRouter>
    );
    sink.renderIntoElementById('react-root', content);
    const helmet = Helmet.renderStatic();
    sink.appendToHead(`
      ${helmet.title.toString()}
      ${helmet.meta.toString()}
      ${helmet.link.toString()}
      <script>
        window.__INITIAL_STATE__ = ${JSON.stringify(store.getState())};
      </script>
    `);
  }
});
// CLIENT
import React from 'react';
import { hydrate } from 'react-dom';

import BrowserRouter from 'react-router-dom/BrowserRouter';
import { renderRoutes } from 'react-router-config';

import routes from './imports/ui/routes';

const fetchedData = window.__INITIAL_STORE__;

delete window.__INITIAL_STORE__;

hydrate(
    <BrowserRouter>
      {renderRoutes(routes, fetchedData)}
    </BrowserRouter>,
  document.getElementById('react-root'),
);

A component might look like this:

class ExamplePage extends Component {
  static async fetchData({ params: { urlParam } }) {
    return fetchTheThingUsingAParam(urlParam);
  }
  ...
4 Likes

I’ve asked this question before and got no response so i came up with my own, slightly different solution which I explained here: React SSR data hydration help.

My solution is completely self-contained within any component which uses it, and the hydrated data is silently replaced with the live subscription once the client is loaded.

I created an example repo here: https://github.com/wildhart/viewmodel-react-starter-meteor-ssr

1 Like

mobix
more chars and more

Appreciate it. I have added Redux and have SSR working now. Thanks!

1 Like

I was trying your solution which works great up until the point using react-router-config on the server. I get errors for matchRoutes and renderRoutes, can I see how you have your routes configured or do you have a repo for example?

Here is a repo of what I have got so far https://github.com/NicholasEli/Meteor-SSR

export default [{
  component: AppLayout,
  routes: [{
    path: '/',
    exact: true,
    component: Home,
  }, {
    path: '/s/:scoreId',
    component: Score,
  }, {
    path: '/admin',
    component: AsyncAdmin,
  }, {
    component: SimpleCenterLogoLayout,
    routes: [{
      path: '/forgot-password',
      component: ForgotPassword,
    }, {
      path: '/change-password/:token',
      component: ChangePassword,
    }, {
      path: '/privacy-policy',
      component: PrivacyPolicy,
    }, {
      path: '/patreon-link',
      component: PatreonLink,
    }, {
      path: '/patreon-confirm',
      component: PatreonConfirm,
    }, {
      path: '404',
      component: NotFound,
    }, {
      path: '*',
      component: NotFound,
    }],
  }],
}];
1 Like

Thanks I will organize my routes file to mimic that and see what I come up with. Much appreciated.