Skip to content

Birthday‑Cake Loading (BCL) is a capability‑adaptive progressive enhancement toolkit for React and Next.js. It ships a tiny baseline runtime, detects device/network/user‑preference signals, and lazily upgrades experiences only when the runtime has enough budget.

License

Notifications You must be signed in to change notification settings

shiftbloom-studio/birthday-cake-loading

🎂 Birthday-Cake Loading (BCL)

Capability-first progressive enhancement for React + Next.js.

Why BCL?

  • Fast time-to-content with baseline-first rendering.
  • 🧁 True progressive enhancement (upgrade only when the device can afford it).
  • Accessibility-first with reduced-motion/data respect built in.
  • 🌿 Respects user preferences (Save-Data, prefers-reduced-*).
  • 🪶 Tiny runtime and tree-shakeable exports.
  • Next.js-ready with a zero-config feel.

npm version npm downloads license GitHub stars CI

🚀 Quickstart

npm install @shiftbloom-studio/birthday-cake-loading
"use client";

import {
  CakeProvider,
  CakeWatch,
  CakeLayer,
  CakeUpgrade,
  useCakeFeatures
} from "@shiftbloom-studio/birthday-cake-loading";

const MotionStatus = () => {
  const { motion } = useCakeFeatures();
  return <p>Motion: {String(motion)}</p>;
};

export default function Page() {
  return (
    <CakeProvider
      config={{ watchtower: { enabled: true, sensitivity: "medium", targets: ["hero", "gallery"] } }}
    >
      <CakeWatch />
      <main>
        <CakeLayer minTier="rich" watchKey="hero" fallback={<div>Static hero</div>}>
          <div>Animated hero</div>
        </CakeLayer>

        <CakeUpgrade
          strategy="idle"
          minTier="rich"
          watchKey="gallery"
          loader={() => import("./rich-gallery")}
          fallback={<div>Static gallery</div>}
        />

        <MotionStatus />
      </main>
    </CakeProvider>
  );
}

✨ Key Features

Feature What you get Why it matters
Tiering (base → ultra) Signal-driven capability buckets Predictable, conservative upgrades
Feature flags motion, richImages, audio, etc. Easy gating without bespoke logic
SSR/Client Hints @shiftbloom-studio/birthday-cake-loading/server helpers Fast first paint, consistent tiering
CakeLayer + CakeUpgrade Render/lazy-load by tier Smooth progressive enhancement
Overrides + DevTools Session override + in-app panel QA and demos become trivial

🆚 Comparison

Approach Bundle impact Accessibility Progressive enhancement Server-first
BCL Tiny Built-in Automatic Yes
Manual feature flags Medium Manual Manual Optional
Device sniffing Medium Inconsistent Risky Optional
“Everything on” Large Often skipped None No

🧠 How tiering decides

flowchart TD
  A[Signals: data saver, memory, CPU, network] --> B{Save-Data or very low memory?}
  B -- yes --> C[base]
  B -- no --> D{Low memory/cores or constrained network?}
  D -- yes --> E[lite]
  D -- no --> F{High memory + high cores?}
  F -- yes --> G[ultra]
  F -- no --> H[rich]
Loading

📡 Signal flow

flowchart LR
  Headers[Client Hints / Headers] --> Server[server helpers]
  Server --> Provider[CakeProvider bootstrap]
  Provider --> Tiering[Tier + Features]
  Tiering --> UI[Layered UI]
Loading

🧩 Optional Signal Matrix (privacy-safe tuning)

For known device/browser quirks (ex: animation issues on certain mobile setups), BCL can apply an optional, coarse signal matrix that nudges tiers without invasive fingerprinting.

<CakeProvider
  config={{
    advanced: {
      signalMatrix: true,
      signalMatrixRules: [
        {
          id: "custom-low-memory-mobile",
          when: { userAgentMobile: true, maxDeviceMemoryGB: 3 },
          adjust: { maxTier: "lite" }
        }
      ]
    }
  }}
>
  {children}
</CakeProvider>

The built-in matrix uses only non-unique signals (reduced motion/data, coarse memory/CPU, mobile hint, and network class) and can be overridden by ID.

🔌 Server bootstrap (Next.js)

import { getServerCakeBootstrapFromHeaders } from "@shiftbloom-studio/birthday-cake-loading/server";

const bootstrap = getServerCakeBootstrapFromHeaders(headers());

🛰️ CakeWatchtower (runtime jank guard)

CakeWatch is an opt-in component that listens for frame drops + long tasks. When jank is detected, it can temporarily downgrade specific rich layers to their static fallbacks (no layout flicker by default thanks to opacity swaps).

import { CakeProvider, CakeWatch, CakeLayer } from "@shiftbloom-studio/birthday-cake-loading";

export default function App() {
  return (
    <CakeProvider config={{ watchtower: { enabled: true, sensitivity: "medium" } }}>
      <CakeWatch />
      <CakeLayer watchKey="particles" fallback={<div>Static background</div>}>
        <div>Particles + motion</div>
      </CakeLayer>
    </CakeProvider>
  );
}
// app/layout.tsx
import { headers } from "next/headers";
import { getServerCakeBootstrapFromHeaders } from "@shiftbloom-studio/birthday-cake-loading/server";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  const bootstrap = getServerCakeBootstrapFromHeaders(headers());
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

🧪 Testing

npm test

📜 License

Apache-2.0

About

Birthday‑Cake Loading (BCL) is a capability‑adaptive progressive enhancement toolkit for React and Next.js. It ships a tiny baseline runtime, detects device/network/user‑preference signals, and lazily upgrades experiences only when the runtime has enough budget.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

No packages published

Contributors 7