Skip to content

feat(frontend): optimize client-side bundle size for Onboarding Progr…#1268

Merged
emdevelopa merged 1 commit into
emdevelopa:mainfrom
emmanuelStack654:feature/fe-optimize-client-side-bundle-size-for-onboarding-progress-tracker
Jul 26, 2026
Merged

feat(frontend): optimize client-side bundle size for Onboarding Progr…#1268
emdevelopa merged 1 commit into
emdevelopa:mainfrom
emmanuelStack654:feature/fe-optimize-client-side-bundle-size-for-onboarding-progress-tracker

Conversation

@emmanuelStack654

Copy link
Copy Markdown
Contributor

…ess Tracker

closes #1159

Reduces the client-side JavaScript footprint of the Onboarding Progress Tracker by lazy-loading framer-motion, replacing JS-driven animations with CSS-only Tailwind keyframes, fixing type drift between the reducer and hook layers, and streaming a zero-dependency shimmer skeleton while the tracker bundle loads. The result is a significantly lighter initial parse cost and a smoother perceived load experience.

Changes
New files

OnboardingTrackerSkeleton.tsx
— server-safe shimmer skeleton, no framer-motion, no hooks. Used as the dynamic() loading fallback and as a standalone Suspense placeholder.
Modified files

tailwind.config.js — 5 new keyframes + animation utilities for CSS-only onboarding animations.

OnboardingProgressTracker.tsx
— lazy framer-motion, CSS-only sub-components, native reduced-motion detection.

useOnboardingI18n.ts
— memoised return object to prevent reference churn.

onboarding-reducer.ts
— fixed type drift: SYNC_STEPS added to action union, createInitialOnboardingState arity corrected, selectProgressPercent reads from state directly.

useOnboardingProgress.ts
— aligned to corrected reducer API.
src/app/(public)/register/page.tsx — tracker lazy-loaded via dynamic() with skeleton fallback.
Optimisation details

  1. Lazy-loaded framer-motion

Before, motion, AnimatePresence, and useReducedMotion were statically imported, meaning framer-motion (~30 KB gzip) was always parsed before the page became interactive.

After, all five framer-motion exports used by the tracker (motion.div, motion.ol, motion.li, motion.svg, AnimatePresence) are loaded via dynamic(…, { ssr: false }). They are fetched only after mount and only when needed, not on initial page load.

  1. CSS-only StepIcon and StatusBadge

StepIcon previously used motion.span for the checkmark pop-in and spinner rotation. It now uses:

animate-onboarding-check-pop — CSS @Keyframes spring-style scale animation
animate-onboarding-spin — CSS @Keyframes linear rotation
StatusBadge previously used motion.span for a scale fade-in on every step row. It now uses a plain with transition-colors. This removes all framer-motion from the step-list render hot-path — the most frequently rendered section.

  1. CSS progress bar transition

The progress bar fill previously used a motion.div with variants and scaleX transforms. It now uses a plain

with transition-[width] duration-500 ease-out — a single CSS transition with no JS.

  1. Native reduced-motion detection

useReducedMotion from framer-motion was replaced with a matchMedia("(prefers-reduced-motion: reduce)") effect. This removes one more framer-motion import and reads the OS preference directly from the browser API.

  1. Memoised useOnboardingI18n return value

The hook previously returned a new object literal on every render, causing all memo()'d children to re-render via reference inequality. The return value is now wrapped in useMemo keyed on [t, ...callbacks] — the object is stable between renders and only recomputes on locale change.

  1. Reducer type drift fixed

Three issues were silently breaking the TypeScript contract between the reducer and the hook:

SYNC_STEPS was dispatched in useOnboardingProgress but absent from the OnboardingAction union — now added.
createInitialOnboardingState had its signature changed to (currentStep, loadingState) in a previous task but the hook called it with (currentStep, total, completed) — signature restored to the 3-arg form and totalSteps/completedSteps added to OnboardingState.
selectProgressPercent was changed to take (completedSteps, totalSteps) args but the hook called selectProgressPercent(state) — selector now reads from state directly, single-argument.
7. Lazy tracker in register page

OnboardingProgressTracker was statically imported in
page.tsx
, pulling the full tracker + framer-motion into the page's initial bundle. It is now loaded via dynamic(…, { ssr: false, loading: () => }) — the skeleton renders at paint time while the tracker bundle streams in the background.

@vercel

vercel Bot commented Jul 26, 2026

Copy link
Copy Markdown

@emmanuelStack654 is attempting to deploy a commit to the Emmanuel's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Jul 26, 2026

Copy link
Copy Markdown

@emmanuelStack654 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@emdevelopa
emdevelopa merged commit b5b75a2 into emdevelopa:main Jul 26, 2026
1 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Frontend] Optimize client-side bundle size for Onboarding Progress Tracker

2 participants