A higher order component for loading components with promises
—
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.
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>
`;
}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');
}
});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'));
});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
});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;
}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 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 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);
});
}// 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 />;
}// 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'));
});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