CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-loadable

A higher order component for loading components with promises

Pending
Overview
Eval results
Files

server-side-rendering.mddocs/

Server-Side Rendering

Comprehensive server-side rendering (SSR) support for React Loadable, enabling seamless hydration between server-rendered content and client-side lazy loading. This includes module tracking, preloading utilities, and bundle mapping.

Capabilities

Module Capture

Track which loadable components are rendered during server-side rendering to ensure the correct bundles are sent to the client.

/**
 * Component for capturing rendered modules during SSR
 */
class Capture extends React.Component {
  static propTypes: {
    /** Function called for each rendered module */
    report: (moduleName: string) => void;
    /** Child components to render */
    children: React.ReactNode;
  };
  
  static childContextTypes: {
    loadable: {
      report: (moduleName: string) => void;
    };
  };
}

Usage Examples:

import React from 'react';
import ReactDOMServer from 'react-dom/server';
import Loadable from 'react-loadable';
import App from './App';

// Server-side rendering with module capture
app.get('/', (req, res) => {
  const modules = [];
  
  const html = ReactDOMServer.renderToString(
    <Loadable.Capture report={moduleName => modules.push(moduleName)}>
      <App />
    </Loadable.Capture>
  );
  
  console.log('Rendered modules:', modules);
  
  // Use modules to determine which bundles to include
  const bundles = getBundles(stats, modules);
  
  res.send(createHTML(html, bundles));
});

function createHTML(content, bundles) {
  return `
    <!DOCTYPE html>
    <html>
      <head><title>My App</title></head>
      <body>
        <div id="app">${content}</div>
        ${bundles.map(bundle => 
          `<script src="${bundle.publicPath}"></script>`
        ).join('\n')}
        <script src="/main.js"></script>
      </body>
    </html>
  `;
}

Server Preloading

Preload all loadable components on the server before rendering to ensure all components are available during SSR.

/**
 * Preloads all registered loadable components
 * @returns Promise that resolves when all components are loaded
 */
function preloadAll(): Promise<void>;

Usage Examples:

import express from 'express';
import Loadable from 'react-loadable';
import { renderApp } from './render';

const app = express();

// Preload all components before starting the server
Loadable.preloadAll().then(() => {
  app.get('/', renderApp);
  
  app.listen(3000, () => {
    console.log('Server started on http://localhost:3000');
  });
}).catch(err => {
  console.error('Failed to preload components:', err);
  process.exit(1);
});

// Alternative: preload on each request (not recommended for production)
app.get('/slow', async (req, res) => {
  try {
    await Loadable.preloadAll();
    const html = renderToString(<App />);
    res.send(html);
  } catch (error) {
    res.status(500).send('Server error');
  }
});

Client Hydration

Preload components that were server-rendered before hydrating the client application.

/**
 * Preloads components that are ready (webpack modules already available)
 * Typically used on the client before hydration
 * @returns Promise that resolves when ready components are loaded
 */
function preloadReady(): Promise<void>;

Usage Examples:

// Client entry point
import React from 'react';
import ReactDOM from 'react-dom';
import Loadable from 'react-loadable';
import App from './App';

// Wait for loadable components before hydrating
Loadable.preloadReady().then(() => {
  ReactDOM.hydrate(<App />, document.getElementById('app'));
});

// Alternative with error handling
Loadable.preloadReady()
  .then(() => {
    ReactDOM.hydrate(<App />, document.getElementById('app'));
  })
  .catch(err => {
    console.error('Failed to preload components:', err);
    // Fallback to client-side rendering
    ReactDOM.render(<App />, document.getElementById('app'));
  });

Module and Bundle Mapping

Module Declaration

Loadable components need to declare which modules they load for SSR support. This is typically automated by the Babel plugin.

/**
 * Manual module declaration (usually automated by Babel plugin)
 */
interface LoadableSSROptions {
  /** Function returning webpack module IDs */
  webpack?: () => number[];
  /** Array of module paths */
  modules?: string[];
}

Usage Examples:

// Manual configuration (not recommended - use Babel plugin instead)
const LoadableComponent = Loadable({
  loader: () => import('./MyComponent'),
  loading: Loading,
  webpack: () => [require.resolveWeak('./MyComponent')],
  modules: ['./MyComponent'],
});

// With Babel plugin, this is automatically added:
const LoadableComponent = Loadable({
  loader: () => import('./MyComponent'),
  loading: Loading,
  // webpack and modules options added automatically
});

Bundle Information

Structure of bundle objects returned by getBundles function.

/**
 * Bundle information for including scripts in HTML
 */
interface Bundle {
  /** Module ID or name */
  id: number | string;
  /** Module name from webpack */
  name: string;
  /** Filename of the bundle */
  file: string;
  /** Full public path to the bundle */
  publicPath: string;
}

Complete SSR Setup

Server Setup

import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import Loadable from 'react-loadable';
import { getBundles } from 'react-loadable/webpack';
import stats from './dist/react-loadable.json';
import App from './App';

const server = express();

// Serve static files
server.use('/dist', express.static('./dist'));

// SSR route
server.get('*', (req, res) => {
  const modules = [];
  
  const html = ReactDOMServer.renderToString(
    <Loadable.Capture report={moduleName => modules.push(moduleName)}>
      <App url={req.url} />
    </Loadable.Capture>
  );
  
  const bundles = getBundles(stats, modules);
  
  res.send(`
    <!DOCTYPE html>
    <html>
      <head>
        <title>My App</title>
        <meta charset="utf-8">
      </head>
      <body>
        <div id="app">${html}</div>
        <script src="/dist/manifest.js"></script>
        ${bundles.map(bundle => 
          `<script src="/dist/${bundle.file}"></script>`
        ).join('\n')}
        <script src="/dist/main.js"></script>
      </body>
    </html>
  `);
});

// Start server after preloading
Loadable.preloadAll().then(() => {
  server.listen(3000, () => {
    console.log('Running on http://localhost:3000/');
  });
});

Client Setup

// Client entry point (src/index.js)
import React from 'react';
import ReactDOM from 'react-dom';
import Loadable from 'react-loadable';
import App from './App';

// Preload components that were server-rendered
Loadable.preloadReady().then(() => {
  ReactDOM.hydrate(<App />, document.getElementById('app'));
});

Development vs Production

// Development setup
if (process.env.NODE_ENV === 'development') {
  // Skip preloading in development for faster startup
  const app = express();
  app.get('*', renderRoute);
  app.listen(3000);
} else {
  // Production setup with preloading
  Loadable.preloadAll().then(() => {
    const app = express();
    app.get('*', renderRoute);
    app.listen(3000);
  });
}

SSR Best Practices

Component Definition

// Good: Define loadable components at module level
const LoadableComponent = Loadable({
  loader: () => import('./Component'),
  loading: Loading,
});

export default function MyPage() {
  return <LoadableComponent />;
}

// Bad: Define loadable components inside render methods
export default function MyPage() {
  const LoadableComponent = Loadable({ // This won't work with preloadAll
    loader: () => import('./Component'),
    loading: Loading,
  });
  
  return <LoadableComponent />;
}

Error Handling

// Server error handling
Loadable.preloadAll()
  .then(() => {
    startServer();
  })
  .catch(err => {
    console.error('Failed to preload components:', err);
    process.exit(1);
  });

// Client error handling  
Loadable.preloadReady()
  .then(() => {
    ReactDOM.hydrate(<App />, document.getElementById('app'));
  })
  .catch(err => {
    console.warn('Preload failed, falling back to render:', err);
    ReactDOM.render(<App />, document.getElementById('app'));
  });

Multiple React Loadable Instances

Ensure only one instance of react-loadable is used in your application to avoid issues with preloadAll().

// package.json - ensure single version
{
  "dependencies": {
    "react-loadable": "5.5.0"
  },
  "resolutions": {
    "react-loadable": "5.5.0"
  }
}

Install with Tessl CLI

npx tessl i tessl/npm-react-loadable

docs

build-integration.md

dynamic-loading.md

index.md

multi-resource-loading.md

server-side-rendering.md

tile.json