Skip to content

Deferred Loading ​

The defer() API lets you delay loading of heavy components or content until a trigger condition is met. This keeps your initial bundle small and loads code only when it is actually needed.

Overview ​

ts
import { defer } from '@akashjs/runtime';

const LazyChart = defer(
  () => import('./HeavyChart.akash'),
  { trigger: 'viewport' }
);

The first argument is a loader function that returns a dynamic import (or any promise). The second argument configures when and how the loading should happen.

Triggers ​

viewport ​

Load when the placeholder element enters the viewport. Uses IntersectionObserver under the hood.

ts
const LazySection = defer(
  () => import('./HeroSection.akash'),
  { trigger: 'viewport' }
);

You can customize the intersection threshold and root margin:

ts
const LazySection = defer(
  () => import('./HeroSection.akash'),
  {
    trigger: {
      type: 'viewport',
      rootMargin: '200px',  // start loading 200px before visible
      threshold: 0.1,
    },
  }
);

interaction ​

Load when the user interacts with the placeholder. Supports click, hover, and focus events.

ts
const Editor = defer(
  () => import('./RichTextEditor.akash'),
  {
    trigger: {
      type: 'interaction',
      events: ['click', 'focus'],  // default: ['click', 'hover', 'focus']
    },
  }
);

Shorthand for default events:

ts
const Editor = defer(
  () => import('./RichTextEditor.akash'),
  { trigger: 'interaction' }
);

idle ​

Load when the browser is idle, using requestIdleCallback. Falls back to setTimeout in browsers that do not support it.

ts
const Analytics = defer(
  () => import('./AnalyticsDashboard.akash'),
  { trigger: 'idle' }
);

timer ​

Load after a fixed delay.

ts
const Chatbot = defer(
  () => import('./ChatWidget.akash'),
  {
    trigger: { type: 'timer', delay: 3000 },  // load after 3 seconds
  }
);

hover ​

A convenience alias for interaction with only the hover event.

ts
const Tooltip = defer(
  () => import('./DetailedTooltip.akash'),
  { trigger: 'hover' }
);

Custom condition ​

Pass a reactive function that returns true when loading should begin.

ts
import { signal } from '@akashjs/runtime';

const isReady = signal(false);

const Module = defer(
  () => import('./Module.akash'),
  {
    trigger: { type: 'custom', when: () => isReady() },
  }
);

// Later, trigger the load:
isReady.set(true);

Loading and error placeholders ​

Show a loading indicator while the chunk is being fetched, and an error state if it fails.

ts
const Dashboard = defer(
  () => import('./Dashboard.akash'),
  {
    trigger: 'viewport',
    loading: () => <div class="skeleton">Loading dashboard...</div>,
    error: (err) => <div class="error">Failed to load: {err.message}</div>,
  }
);

Both loading and error accept render functions.

Prefetch ​

When prefetch is enabled, the chunk is fetched at low priority in the background but the component is not rendered until the trigger fires. This gives you instant swap-in when the user reaches the content.

ts
const HeavyWidget = defer(
  () => import('./HeavyWidget.akash'),
  {
    trigger: 'viewport',
    prefetch: true,
  }
);

Prefetch uses <link rel="prefetch"> when available, falling back to a low-priority fetch.

Minimum load time ​

To prevent a flash of loading state when the chunk loads almost instantly, set a minimumLoadTime. The loading placeholder will be shown for at least this many milliseconds.

ts
const Panel = defer(
  () => import('./Panel.akash'),
  {
    trigger: 'interaction',
    loading: () => <Spinner />,
    minimumLoadTime: 300,  // show spinner for at least 300ms
  }
);

This avoids the jarring experience of a spinner appearing for a single frame.

Full options reference ​

ts
interface DeferOptions {
  trigger: DeferTrigger;
  loading?: () => AkashNode;
  error?: (err: Error) => AkashNode;
  prefetch?: boolean;
  minimumLoadTime?: number;
}

type DeferTrigger =
  | 'viewport'
  | 'interaction'
  | 'idle'
  | 'hover'
  | { type: 'viewport'; rootMargin?: string; threshold?: number }
  | { type: 'interaction'; events?: string[] }
  | { type: 'timer'; delay: number }
  | { type: 'custom'; when: () => boolean };

Combining with Suspense ​

Deferred components integrate with <Suspense> boundaries. If a deferred component is inside a <Suspense>, the suspense fallback is shown until all deferred children have loaded:

html
<Suspense fallback={() => <PageSkeleton />}>
  <Header />
  {DeferredSidebar}
  {DeferredContent}
</Suspense>

Released under the MIT License.