Skip to main content

Lazy loading quickstart

  • How-to guide
  • 5-min read

Code splitting breaks an app into smaller chunks so only the necessary code is loaded initially, reducing the upfront bundle size. Lazy loading then delays loading those chunks until they're actually needed, delivering the real performance gains by fetching code on demand.

When a user opens your app, the browser first downloads and parses main.js, the primary JavaScript bundle, before anything appears on screen. In large apps, this bundle can grow to several megabytes, causing slow initial load times. Keeping main.js below 1 MB is a good target; the smaller it is, the faster your app starts.

Lazy loading concepts

Here are the essential steps to enable code splitting in your Dynatrace app. For a more thorough explanation, see Understand lazy loading.

Prerequisites

  • dt-app CLI version 1.9.0 or higher.

Steps

1. Convert static route imports to lazy-loaded routes

Route-based splitting is the most impactful technique for most apps. Each route loads only the code it needs, so users navigating to the home page never download the code for the data page, and vice versa.

Replace static import statements for your page components with React.lazy() and wrap your routes in a Suspense boundary:

ui/app/App.tsx
import { Page } from '@dynatrace/strato-components/layouts';
import React, { Suspense, lazy } from 'react';
import { Route, Routes } from 'react-router-dom';
import { Header } from './components/Header';
import { ProgressCircle } from '@dynatrace/strato-components/content';

const Home = lazy(() => import('./pages/Home'));
const Data = lazy(() => import('./pages/Data'));

export const App = () => {
return (
<Page>
<Page.Header>
<Header />
</Page.Header>
<Page.Main>
<Suspense fallback={<ProgressCircle />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/data" element={<Data />} />
</Routes>
</Suspense>
</Page.Main>
</Page>
);
};

Each page component must use a default export:

ui/app/pages/Home.tsx
const Home = () => {
return <div>Welcome To Your Dynatrace App</div>;
};

export default Home;
ui/app/pages/Data.tsx
const Data = () => {
return <div>Explore Data</div>;
};

export default Data;
Note

React.lazy() requires the dynamically imported module to have a default export containing a React component. If your pages use named exports, see the named exports section below.

Handle named exports

If your page components use named exports (for example, export const Home = () => { ... }), wrap the dynamic import to map the named export to a default export:

ui/app/App.tsx
const Home = lazy(() => import('./pages/Home').then((module) => ({ default: module.Home })));

2. Split heavy components

You can also split individual components that are heavy or not immediately visible. This is useful for components like charts, editors, or modals that add significant JavaScript weight but are only needed after user interaction.

For example, if your Data page includes a heavy chart library, lazy-load just the chart and add a button to trigger loading it:

ui/app/pages/Data.tsx
import React, { Suspense, lazy, useState } from 'react';
import { Button } from '@dynatrace/strato-components/buttons';
import { Flex } from '@dynatrace/strato-components/layouts';
import { Heading } from '@dynatrace/strato-components/typography';
import { ProgressCircle } from '@dynatrace/strato-components/content';

const DataChart = lazy(() => import('../components/DataChart'));

export const Data = () => {
const [isOpen, setIsOpen] = useState(false);

return (
<Flex flexDirection="column" padding={32}>
<Heading level={2}>Explore Data</Heading>
<Button onClick={() => setIsOpen(true)}>Explore Data</Button>
{isOpen && (
<Modal title="Explore Data" show onDismiss={() => setIsOpen(false)}>
<Suspense fallback={<ProgressCircle />}>
<DataChart onClose={() => setIsOpen(false)} />
</Suspense>
</Modal>
)}
</Flex>
);
};

3. Load non-component code on demand

For non-component code such as utility functions or data processing libraries, use dynamic import() directly. This gives you fine-grained control over when a module is loaded.

ui/app/pages/Data.tsx
import React, { useState } from 'react';

const Data = () => {
const [result, setResult] = useState(null);

const handleAnalyze = () => {
void import('../utils/analyze').then(({ analyzeData }) => {
setResult(analyzeData());
});
};

return (
<div>
<button onClick={handleAnalyze}>Analyze</button>
{result && <pre>{JSON.stringify(result, null, 2)}</pre>}
</div>
);
};

export default Data;

The ../utils/analyze module is only downloaded when the user clicks the button.

4. Add loading feedback to Suspense boundaries

Every Suspense boundary needs a fallback that shows while the chunk loads. Use Strato's ProgressCircle to give users visual feedback instead of a plain text placeholder. For example, the MapView component is large and benefits from a spinner while it loads:

ui/app/pages/Map.tsx
import React, { Suspense, lazy } from 'react';
import { Flex } from '@dynatrace/strato-components/layouts';
import { ProgressCircle } from '@dynatrace/strato-components/content';

const MapView = lazy(() => import('@dynatrace/strato-geo').then((mod) => ({ default: mod.MapView })));

const Map = () => {
return (
<Flex flexDirection="column" padding={32}>
<Suspense fallback={<ProgressCircle />}>
<MapView />
</Suspense>
</Flex>
);
};

export default Map;

5. Build and verify

Build and deploy the app to confirm code splitting works by enabling code splitting in your build scripts:

npx dt-app build --optimize --enable-code-splitting
npx dt-app deploy --optimize --enable-code-splitting

The build command creates a dist folder containing the compiled output. You should see multiple .js files instead of a single bundle, confirming that code splitting is active.

Summary

TechniqueBest forHow it works
Route-based splittingPages / viewsReact.lazy() + Suspense on route components
Component-based splittingHeavy componentsReact.lazy() + Suspense on individual components

Combine these techniques to progressively reduce your app's initial bundle size and improve load performance.

Still have questions?
Find answers in the Dynatrace Community