Lazy loading with SSR in React

Lazy loading with SSR in React

How would you implement a mechanism for dynamically loading components (lazy loading) in a React application considering server-side rendering (SSR)? And how would you handle situations where the component fails to load in time, for example, due to a slow connection or a server error?

To implement dynamic component loading in React with server-side rendering (SSR) in mind, you can use a combination of React.lazy() and Suspense, as well as tools like loadable-components to support SSR, since built-in support for SSR is missing in React.lazy().

Steps for Implementation:

1. Dynamic loading with React.lazy:

Use React.lazy() to load the components that we need.

const LazyComponent = React.lazy(() => import('./MyComponent'));
function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>
  );
}

However, React.lazy is not suitable for server-side rendering, as it is designed primarily for client-side rendering.

2. SSR with loadable-components:

For server-side rendering, we can use the @loadable/component library. It supports SSR and allows for dynamic loading.

Steps:

  • Install the @loadable/component library:
npm install @loadable/component
  • Use it for dynamic component loading:
import loadable from '@loadable/component';
const LoadableComponent = loadable(() => import('./MyComponent'), {
  fallback: <div>Loading...</div>,
});
function App() {
  return <LoadableComponent />;
}
  • For SSR, you need to collect all dynamically loaded components on the server side using @loadable/server:
npm install @loadable/server

On the server, use the ChunkExtractor to gather all required chunks:

import { ChunkExtractor } from '@loadable/server';
import path from 'path';
const statsFile = path.resolve('./dist/loadable-stats.json');
const extractor = new ChunkExtractor({ statsFile });
const jsx = extractor.collectChunks(<App />);
const html = renderToString(jsx);

This ensures that the dynamic components are included during server-side rendering and loaded efficiently on the client.

3. Handling errors and slow loading:

To improve the user experience in case of slow loading or errors, you can add error handling using an ErrorBoundary.

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children; 
  }
}
function App() {
  return (
    <ErrorBoundary>
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
    </ErrorBoundary>
  );
}

4. Preloading components:

In some cases, you may want to preload components in advance if you know they will be needed. loadable-components also supports this functionality.

LoadableComponent.preload();

Thus, for full SSR support and a more reliable dynamic loading mechanism, as well as for handling errors and slow connections, the combination of loadable-components, Suspense, and ErrorBoundary is the best approach.

Back To Top