Performance
This page explains how performance is handled in 3AM and what you should do before adding heavy UI.
The project already has strong lazy-loading infrastructure. Most performance regressions happen when contributors bypass it.
For API-level component usage, pair this page with Ready Components and Building Components.
Default strategy: lazy media components
For images and video, prefer project components first:
LazyImage for a single source image.
LazyPicture for responsive phone/tablet/desktop assets.
LazyVideo for deferred poster/source hydration.
These are connected to src/lib/lazyMedia.ts, which scans the page and hydrates deferred media with IntersectionObserver.
What lazy media controller looks for
The controller hydrates elements with attributes like:
data-lazy-src, data-lazy-srcset, data-lazy-sizes, data-lazy-poster, and data-lazy-bg-src.
Initial scan runs at app startup. Another scan runs after every route change. That is why route-level media can stay lazy even in SPA navigation.
plain image vs project lazy image
Problematic Example
render(): DocumentFragment {
return this.tpl`<img src="/assets/dusk/dusk_transparent.webp" alt="Dusk" />`;
}Recommended Example
render(): DocumentFragment {
return this.tpl`
${new LazyImage({
src: "/assets/dusk/dusk_transparent.webp",
alt: "Dusk",
className: "hero-image",
})}
`;
}responsive hero image
Problematic Example
<img src="/assets/hero/slide-1-1440.png" alt="Hero" />Recommended Example
${new LazyPicture({
src: {
phone: "/assets/hero/slide-1-520.png",
tablet: "/assets/hero/slide-1-1024.png",
pc: "/assets/hero/slide-1-1440.png",
},
alt: "Hero slide",
sizes: "100vw",
pictureClassName: "hero-slide-picture",
className: "hero-slide-image",
})}Boot loader and perceived performance
src/app/bootloader.ts keeps the loader visible until critical conditions are met:
- minimum visible time
- page load event
- critical image decode
- critical font readiness
It also respects reduced-motion and skips fade transition in that mode.
Bundle budget checks
When your change can affect payload size, run:
bun run build:bundle
bun run budgetBudget limits are defined in package.json (size-limit). Increase limits only with a clear reason and mention it in PR.
Common mistakes that hurt performance
Mounting heavy media without lazy strategy.
Adding expensive work inside frequently called paths.
Registering repeated listeners because setup is done in render() instead of lifecycle.
Adding rich motion without reduced-motion fallback.