Capability-first progressive enhancement.
Ship a baseline-first page, then selectively upgrade visuals and interactivity when the device (and user preferences) say it is safe.
1) Render by tier with CakeLayer
Each layer has a built-in baseline fallback. Try overriding tiers via the BCL DevTools (top-right) to see the swaps.
Base (always)
Readable layout, crisp typography, no assumptions.
Lite layer
Now we can safely opt into richer imagery and minor UX polish.
Rich layer
Eligible for client-side upgrades and premium interactions.
Ultra layer
Locked until the tier reaches ultra.
import { CakeLayer } from "@shiftbloom-studio/birthday-cake-loading";
<CakeLayer minTier="rich" fallback={<HeroStatic />}>
<HeroAnimated />
</CakeLayer>2) Lazy-load enhancements with CakeUpgrade
Use strategies like idle, visible, and interaction so upgrades load only when needed.
Waits for idle time before loading. Great for non-critical polish.
Loads only once the card scrolls near the viewport.
Hover, focus, or click to trigger the import.
A small delight reserved for the highest tier.
import { CakeUpgrade } from "@shiftbloom-studio/birthday-cake-loading/upgrade";
<CakeUpgrade
strategy={{ type: "visible", rootMargin: "300px" }}
minTier="rich"
loader={() => import("./RichGallery")}
fallback={<GalleryStatic />}
/>3) Signals and feature gates
BCL derives features from capability + preferences. For example: motion is off when reduced-motion is on.
| Signal | Value |
|---|---|
| saveData | — |
| prefersReducedData | — |
| prefersReducedMotion | — |
| effectiveType | — |
| downlinkMbps | — |
| rttMs | — |
| deviceMemoryGB | — |
| hardwareConcurrency | — |
| devicePixelRatio | — |
| screenWidth | — |
| screenHeight | — |
| userAgentMobile | — |
4) Feature-first gates
Sometimes you want to gate on a concrete feature (like richImages or privacyBanner) instead of thinking in tiers. BCL lets you do that.
Rich imagery is allowed. This is where you can increase image quality, enable richer art-direction, or swap to heavier components.
Render a richer consent UI only when BCL says it is safe (rich+ and not Save-Data).
5) Server bootstrap (SSR)
On the server, BCL can precompute signals and a tier from request headers (Client Hints). This keeps SSR and hydration consistent and avoids guessing.
Computed on the server
Tier: rich • Features: motion=on • smoothScroll=on • audio=on • privacyBanner=on • richImages=on
View request headers used
Save-Data: —
ECT: —
Downlink: —
RTT: —
Device-Memory: —
DPR: —
Viewport-Width: —
Viewport-Height: —
Sec-CH-UA-Mobile: —
Sec-CH-Prefers-Reduced-Motion: —
Sec-CH-Prefers-Reduced-Data: —Bootstrap JSON
The exact object you can pass to CakeProvider as bootstrap.
{
"signals": {},
"tier": "rich"
}// app/layout.tsx (Next.js App Router)
import { headers } from "next/headers";
import { getServerCakeBootstrapFromHeaders } from "@shiftbloom-studio/birthday-cake-loading/server";
export default async function RootLayout({ children }) {
const bootstrap = getServerCakeBootstrapFromHeaders(await headers());
return <Providers bootstrap={bootstrap}>{children}</Providers>;
}