No description
Find a file
autocommit a867c8a57a
Some checks failed
Publish / publish (push) Failing after 0s
deps-upgrade(dependencies): ⬆️ Update dependencies to latest stable versions for security and bug fixes
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-10 06:55:32 -07:00
.forgejo/workflows chore: initial package split from monorepo 2026-04-20 01:10:37 -07:00
src chore: initial package split from monorepo 2026-04-20 01:10:37 -07:00
.gitignore chore: add .gitignore, remove node_modules/dist/.turbo from tracking 2026-04-20 01:12:37 -07:00
IDEAS.md chore: initial package split from monorepo 2026-04-20 01:10:37 -07:00
package.json deps-upgrade(dependencies): ⬆️ Update dependencies to latest stable versions for security and bug fixes 2026-06-10 06:55:32 -07:00
README.md chore: initial package split from monorepo 2026-04-20 01:10:37 -07:00
tsconfig.json chore: initial package split from monorepo 2026-04-20 01:10:37 -07:00

@lilith/ui-lazy

SOLID/DRY lazy loading utilities for React. Defer heavy dependencies and reduce initial bundle size for consumer-facing applications.

Installation

pnpm add @lilith/ui-lazy

Usage

import { createLazyComponent, createLazyModal } from '@lilith/ui-lazy'

// Lazy component with Suspense built-in
const HeavyChart = createLazyComponent(
  () => import('@/components/HeavyChart'),
  { fallback: <ChartSkeleton /> }
)

// Use like any component
<HeavyChart data={data} />

// Preload on hover
<button onMouseEnter={HeavyChart.preload}>View Chart</button>

Modal Pattern

import { createLazyModal } from '@lilith/ui-lazy'

// Optimized for modals - unmounts when closed, preloads on hover
const PaymentModal = createLazyModal(
  () => import('@lilith/payments/frontend'),
  {
    extract: (mod) => mod.GiftCardPurchaseModal,
    fallback: <ModalSpinner />,
  }
)

// Preload when user hovers buy button
<button
  onClick={() => setOpen(true)}
  onMouseEnter={PaymentModal.preload}
>
  Buy Now
</button>

<PaymentModal isOpen={open} onClose={() => setOpen(false)} />

Hook Pattern (Full Control)

import { useLazyComponent, useLazyOnInteraction, useLazyOnVisible } from '@lilith/ui-lazy'

// Basic hook
const { Component, isLoaded, isLoading, error, preload } = useLazyComponent(
  () => import('@/components/HeavyComponent')
)

// With interaction triggers (hover/focus/touch)
const { Component, triggerProps } = useLazyOnInteraction(
  () => import('@/components/Modal')
)
<button {...triggerProps}>Open</button>

// Visibility-based (IntersectionObserver)
const { ref, Component, isLoaded } = useLazyOnVisible(
  () => import('@/components/BelowFold'),
  { rootMargin: '200px' }
)
<div ref={ref}>{isLoaded && <Component />}</div>

Named Export Extraction

// When module has named exports (not default)
const GiftCardModal = createLazyModal(
  () => import('@lilith/payments/frontend'),
  { extract: (mod) => mod.GiftCardPurchaseModal }
)

Preload Utilities

import { preloadComponents, preloadComponent, preloadAfterDelay, preloadOnNetworkIdle } from '@lilith/ui-lazy'

// Preload multiple during idle time
preloadComponents([
  () => import('@/pages/Checkout'),
  () => import('@/pages/Profile'),
])

// Preload after delay
preloadAfterDelay(() => import('@/components/HeavyWidget'), 2000)

// Preload when network is idle
preloadOnNetworkIdle(() => import('@/components/Analytics'))

API Reference

Factories

Function Purpose
createLazyComponent Creates lazy component with built-in Suspense
createLazyModal Modal-optimized (unmounts when closed)

Hooks

Hook Purpose
useLazyComponent Core hook with full loading state control
useLazyOnInteraction Preload on hover/focus/touch
useLazyOnVisible Preload when element enters viewport

Components

Component Purpose
LazyBoundary Suspense wrapper with error boundary

Utilities

Function Purpose
preloadComponents Bulk preload during idle time
preloadComponent Immediate single preload
preloadAfterDelay Preload after specified delay
preloadOnNetworkIdle Preload when network is idle

When to Use

Good candidates for lazy loading:

  • Payment/checkout modals (only needed at purchase)
  • Heavy visualization libraries (charts, graphs, maps)
  • Below-the-fold content
  • Feature-gated components
  • Large form wizards

Not recommended:

  • Admin panels (internal, not SEO-sensitive)
  • Components needed immediately on page load
  • Small components (overhead > benefit)

Current Consumers

  • @lilith/landing - GiftCardPurchaseModal from @lilith/payments

Future Work

Testing

  • Unit tests for hooks (useLazyComponent, useLazyOnInteraction, useLazyOnVisible)
  • Unit tests for factories (createLazyComponent, createLazyModal)
  • Integration tests with actual lazy imports
  • Error boundary testing
  • SSR compatibility tests

Demo/Showcase App

  • Interactive demo showing lazy loading in action
  • Network tab visualization of chunk loading
  • Bundle size comparison (eager vs lazy)
  • Performance metrics dashboard
  • Code examples with live preview

Documentation

  • Storybook stories for visual documentation
  • Performance benchmarking guide
  • Migration guide from React.lazy
  • Best practices for chunk naming

License

MIT