Meteor 1.8 SSR Server Data Double Render [content flashes twice]

Meteor 1.8 SSR Server Data Double Render [content flashes twice]

I would love to see if anyone in the community has any experience with this problem! I can’t seem to trouble shoot and solve.

Problem:
When we need to pull data from the server for a specific public facing page, the content loads an initial time and then re-loads causing the user to see a “flash” of content. I’m assuming this has to due with the sync in the client hydrate but as this is my first Meteor SSR setup I’m a bit lost.

Here is an example live page: https://www.winkkitten.com/s/7QQcCHWgxWmewX4P3

React Component Code

componentWillMount () {
  if (Meteor.isServer) {
    var result = AtlasConnection.call('public.order.status', this.props.match.params.order)
    this.setState({ order: res })
  }
}

async componentDidMount () {
  await AtlasConnection.call('public.order.status', this.props.match.params.order, (err, res) => {
    if (!err) {
      this.setState({ order: res })
    }
  }
}

render () {
  if (!this.state.order) {
    return (
      <LoadingSpinnerHere />
    )
  }

  return (
    <ContentHere />
  )
}

Client.js

const App = () => (
  <Provider store={store}>
    <StripeProvider apiKey={Meteor.settings.public.stripe.publishableKey}>
      <Router history={history}>
        <Switch>
          <Routes />
        </Switch>
      </Router>
    </StripeProvider>
  </Provider>
)

onPageLoad(async sink => {
  ReactDOM.hydrate(
    <App />,
    document.getElementById('wk')
  )
})

Server.js

onPageLoad((sink) => {
  const context = {}
  const store = createStore(reducers, applyMiddleware(thunk))
  const App = props => (
    <Provider store={store}>
      <StaticRouter location={props.location} context={context}>
        <Routes />
      </StaticRouter>
    </Provider>
  )
  App.propTypes = {
    location: object.isRequired
  }

  sink.renderIntoElementById('wk', renderToString(<App location={sink.request.url} />))
  const helmet = Helmet.renderStatic()
  sink.appendToHead(helmet.meta.toString())
  sink.appendToHead(helmet.title.toString())

  if (context.url) {
    sink.redirect(context.url)
  }

  const preloadedState = store.getState()
  sink.appendToBody(`
    <script>
      window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/</g, '\\u003c')}
    </script>
  `)
})

This is actually a react issue. During hydration, React compares the hydrated data to the data existing in the DOM. If the data does not match, React will repaint the entire DOM. And it is very difficult to make them match (for a reason I cannot explain). A simple new line between components can cause the matching to fail and the repaint to happen.

In one of our projects, we created a mechanism wherein during loading, instead of rendering the expected content, the component actually get the existing DOM and render it (in this case, the DOM and the rendered content is exactly the same so React will not re-render).

Here is an example where it is implemented:

Same project, but not implemented (where you can see the flashes you are experiencing)

Thank you @rjdavid! Very helpful. I looked at both examples and this is exactly what we are facing.

Did you render the empty DOM and then populate the data inside to avoid the re-painting issue?

The SSR version has the actual content (the same content that will be rendered by the client). The first content you see with the loader “Please wait while the page loads” is the SSR version.

But in the client, instead of rendering “the normal content” from the database, the system gets the DOM copy from the SSR version and renders it. This will only happen during the first load. Succeeding loading will be the normal render with content coming from the database