Lazy Loading

As an application grows, downloading all route code on the initial load can increase startup time, including code for routes a user does not visit.

Lazy loading splits code into separate files and downloads a file when the user navigates to its route.

Basic Lazy Loading

Use lazy() to lazy-load a route component. Pass it a function that returns a dynamic import() for the component file.

import { defineRoutes, lazy } from 'retend/router';

// This component is included in the main initial download
const Home = () => <div>Home</div>;

// This component is separated into its own file and downloaded only when needed
const SettingsLazy = lazy(() => import('./views/Settings'));

const routes = defineRoutes([
  { path: '/', component: Home },
  { path: '/settings', component: SettingsLazy },
]);

Exporting your Component

The component passed to lazy() must be the file’s default export.

// views/Settings.tsx

// You can still use local helper components
function SettingsForm() {
  return <form>...</form>;
}

// But the component you want to lazy-load MUST be the `default` export
export default function SettingsView() {
  return (
    <div class="settings">
      <h1>Account Settings</h1>
      <SettingsForm />
    </div>
  );
}

Lazy Loading Nested Routes

Lazy loading also applies to nested routes. The parent layout, child pages, or both can be lazy-loaded.

import { defineRoutes, lazy, Outlet } from 'retend/router';

const DashboardLayout = () => (
  <div>
    <h1>Dashboard</h1>
    <Outlet />
  </div>
);

// The child page is lazy-loaded
const UserProfileLazy = lazy(() => import('./views/dashboard/UserProfile'));

const routes = defineRoutes([
  {
    path: '/dashboard',
    component: DashboardLayout,
    children: [{ path: 'profile', component: UserProfileLazy }],
  },
]);

Lazy Subtrees (Advanced)

For applications with many routes, the route configuration itself can become large.

Lazy subtrees split the routing configuration so a section’s routes are downloaded when the user enters that section.

Step 1: Create the Subtree File

Create a separate file that exports one route configuration as its default export:

// finance.routes.ts
import { defineRoute, lazy } from 'retend/router';
import FinanceDashboard from './views/FinanceDashboard';

// The path MUST match where it will be mounted in the parent router
export default defineRoute({
  path: '/finance/:accountId',
  component: FinanceDashboard,
  children: [
    {
      path: 'reports',
      component: lazy(() => import('./views/Reports')),
    },
    {
      path: 'invoices',
      component: lazy(() => import('./views/Invoices')),
    },
  ],
});

Step 2: Connect it to the Main Router

In the main route file, set subtree instead of component or children, and wrap the import in lazy():

// index.routes.ts
import { defineRoutes, lazy } from 'retend/router';

const routes = defineRoutes([
  { path: '/', component: Home },
  {
    path: '/finance/:accountId',
    // The entire finance section (routes and components) loads on demand
    subtree: lazy(() => import('./finance.routes.ts')),
  },
]);

Lazy loading can reduce the initial JavaScript required as the number of routes grows.