React's lazy() API is typically associated with client-side code splitting. However, it can also be used in server environments to reduce the initial response time of the development server by deferring the work needed to compute your application.
When the development server receives a request, it needs to compute <App /> to generate the response. This computation requires loading all imported modules, even those for contents that are not needed for the current request. For example, consider a routing setup like this:
// All these imports are loaded immediately
import HomePage from "./pages/Home";
import AboutPage from "./pages/About";
import DocsPage from "./pages/Docs";
import SettingsPage from "./pages/Settings";
// ... more page imports
const routes = [
route({ path: "/", component: defer(<HomePage />) }),
route({ path: "/about", component: defer(<AboutPage />) }),
// ... more routes
];
export default function App() {
return <Router routes={routes} />;
}
In a large application with many routes, this upfront loading adds latency to the first request, even though only one route's component will actually render.
While use of defer() helps reducing initial load by deferring rendering of route components, the work to import those components still happens immediately.
lazy() defers the import of a module until the component is actually rendered. In a server environment, this means:
<App /> computation only loads the routing structureimport { lazy } from "react";
// These imports are deferred
const HomePage = lazy(() => import("./pages/Home"));
const AboutPage = lazy(() => import("./pages/About"));
const DocsPage = lazy(() => import("./pages/Docs"));
const SettingsPage = lazy(() => import("./pages/Settings"));
const routes = [
route({ path: "/", component: defer(<HomePage />) }),
route({ path: "/about", component: defer(<AboutPage />) }),
// ... more routes
];
When a user visits /about, only AboutPage is actually imported. The other page modules remain unloaded, reducing the work the server needs to do.
defer()To use lazy() effectively in server components, you should combine it with defer().
Without defer(), the contents of the lazy-loaded components would be part of the initial RSC payload. This means the server would still need to fully render them before sending the response, negating the benefits of lazy loading.
Here's a complete example of using lazy() with FUNSTACK Router as a router library:
import { lazy, Suspense } from "react";
import { Outlet } from "@funstack/router";
import { route } from "@funstack/router/server";
import { defer } from "@funstack/static/server";
import { Layout } from "./components/Layout";
// Lazy load page components
const HomePage = lazy(() => import("./pages/Home"));
const AboutPage = lazy(() => import("./pages/About"));
const DocsPage = lazy(() => import("./pages/Docs"));
const routes = [
route({
path: "/",
component: (
<Layout>
<Outlet />
</Layout>
),
children: [
route({
path: "/",
component: defer(<HomePage />),
}),
route({
path: "/about",
component: defer(<AboutPage />),
}),
route({
path: "/docs",
component: defer(<DocsPage />),
}),
],
}),
];
This optimization is most beneficial when:
For small applications with few routes, the overhead of lazy() may not be worth it. But as your application grows, lazy loading routes becomes increasingly valuable.
defer() to split RSC payloads