Can I use Meteor SSR with React Loadable (dynamic import)?

Dang. I’m already doing that. I feel like I’m so close, but not able to see anything render other than the loading component.

oh you may need some additional waiting logic for the apollo data.

If you view source does it show the loading, or does it show content -> loading -> content?

Im not sure if you mean it show loading from the server side or client side

I’m thinking I need to wait, it’s showing Loadable’s loading, so not content -> loading -> content.

Hi! Are you were able to solve problem with react loadable ?

Yes, I had to fork react loadable.

You can see it working in my starter.

1 Like

I also came up with a solution and put it in a package. Maybe someone is interested in it: https://github.com/nemms/meteor-react-loadable

Feedback is appreciated.

2 Likes

Amazing! I really like this approach! I tried a similar technique and was able to hack something together that kind of sort of worked, but what you did here is much more flexible and elegant. Love how it sticks really close to the react-loadable API, too.

Gonna give this a whirl, because it is much nicer than what I came up with.

Do you have an example app of this working? I have had no success with it so far; the client tries preload the components before they have been registered. Not sure what I am doing wrong.

The server side rendering works perfectly and the __meteorReactLoadableModules__ are scripted into the page, it’s just that calling await MeteorLoadable.preloadComponents() causes the preload to be called before the components have had an opportunity to run preloadableComponents.registerComponent.

I don’t have a public example running, only my app. There it’s working fine. Where are you calling registerComponent? You must call it (and also define the MeteorLoadable) outside of any component. Then they should be initialized before onPageLoad.

You must call it (and also define the MeteorLoadable ) outside of any component.

Solved the problem. I was defining the loadable component inline inside of react-router’s Route component, like

<Route 
   path="/signin" 
   component={MeteorLoadable({
      loader: () => import('/imports/ui/pages/application/Login'),
      moduleId: '/imports/ui/pages/application/Login',
      loading: PageLoader,
   })} 
/>

For some reason, doing it that way wouldn’t have the component register before the preload. Simply changing it to something like:

const LoginPage = MeteorLoadable({
  loader: () => import('/imports/ui/pages/application/Login'),
  moduleId: '/imports/ui/pages/application/Login',
  loading: PageLoader,
});
...
<Route path="/signin" component={LoginPage} />

fixed the problem. Not sure why, but it works!

Gonna play around with this a bit more and see if there is a nice way to do something where we can wrap react-router’s <Link> to automatically preload for you. Might be an opportunity here to create something nice like a FlowRouter-lite that works with react-router and meteor-loadable.

The browser loads all of the javascript. That means everything you define like a constant in a module will be evaluated. At this time also the component will register.

Only after everything is loaded Meteor calls the onPageLoad where you start the react app.
So if you define your Loadable inside a React component this gets evaluated when your app gets rendered, so that is too late to register your component since all registered component were loaded already.

What exactly do you want to achieve with a Link wrapper component? Preload on link click? Or at which point do you want to preload?
Maybe it’s enough to use .preload() on the component when the parent component is loaded or so.

Ah, didn’t know that! Thank you!

With the Link component, it would just preload the page level component when it renders, so that by the time the user clicks on it, it will be ready to go. Similar to what Next.js does.

So… let’s say you had something like <Route path="/profile" component={ProfilePage} /> defined somewhere, where ProfilePage is a react-loadable component. Somewhere, you have <LoadableLink to="/profile">Profile</Link> and it preloads the ProfilePage for you when it renders.

The router might have to be a bit smarter, because it would have to know how to map the to prop to a certain component, which would be tricky if you have a route path like /profile/:id.

I wouldn’t go that far. Let’s say you have 4 links to different pages all those pages will be preloaded. I would rather suggest to think about the typical navigation path and only preload what’s most likely the next page. Let’s assume the visitor opens the home page. There you have a lot of links. What might be the next page the user visits? Maybe the login page? Is it important that the contact page is loaded immediately? Don’t preload everything. Just my two cents.

Oh absolutely. You could just use the regular <Route> component to “opt out” of the preload behaviour. Or maybe just have a prefetch option to opt in to the behaviour like Next.js does. Just think it would be nice to be able to do declaratively wherever you wanted the behaviour. Just throwing some ideas out there. The routing in Next.js is really nice and one of the only things I feel it does a better job of in comparison to Meteor. Your meteor-loadable utility really helped close that gap!

hey @captainn,

thank you very much for your starter!

I managed to get SSR work with FlowRouter, loadable, etc.

It works now very good on develop, but i noticed something odd on production (when deployed to a server):

There is this

  <Loadable.Capture
            report={moduleName => {
              modules.push(moduleName);
            }}
            reportResolved={resolvedModuleName => {
              modulesResolved.push(resolvedModuleName);
            }}
          >

this misses some modules on production, so __preloadables__ ends up with missing modules and so some parts are missing on rendering. Always the same modules are missing, but i don’t really see the pattern.

Any idea what’s going on? This only happens on the server (deployed with kubernetes), but not when i do meteor --production… so that’s very weird…

Are you using the babel plugin? I’m really not sure why that would be happening - but if there’s a weak point in the chain, it would be the way the babel plugin translates relative import paths. I sort of just poked around with stuff until it worked. I’d love to finally do a proper rewrite of loadable (as an atmosphere package for a couple of reasons) but I haven’t had the time yet.

If you run locally with the --production flag do you also get problems?

Hey captainN!

I found the problem, it was related to not having the same data on the server as on the client, so i ended up loading a different component!

Another problem is, however, if a lazy loaded component loads another one. Then the second component doesnt get tracked by the server.

Chained loading should actually work. I have a pattern where I lazy load a component (I hide the instantiation of the Loadable until after mount) intentionally to avoid rendering it on the server. I figure there’s no point in rendering authenticated routes on the server, so I do a couple of things avoid it.