Skip to main content

Lesson 8: Performance

Benchmarks, bundle size comparisons, and optimization strategies.

Bundle size

We replaced react-day-picker with @gentleduck/calendar. Comparison against the major React calendar libraries:

Bundle size — all competitors

LibraryBundle (gzipped)Improvement
@gentleduck/calendar4.9 KBWinner
react-calendar15.0 KB3.1x larger
react-day-picker v920.0 KB4.1x larger
react-datepicker32.0 KB6.5x larger
react-aria DatePicker45.0 KB9.2x larger

Dependencies

LibraryRuntime Dependencies
@gentleduck/calendar0
react-calendar0
react-day-picker1 (date-fns)
react-datepicker3
react-aria8

Render performance

Core function benchmarks (average of 2000 iterations):

OperationTime
buildCalendarMonth~8 us
buildMultiMonth(3)~22 us
buildMultiMonth(12)~78 us
applySelection~11 us

Generate benchmarks locally:

cd packages/duck-calendar && bun run benchmark
cd packages/duck-calendar && bun run benchmark

Package quality

ToolResult
publintAll good
attw (are-the-types-wrong)Types correct for node10, node16, bundler
ESMPure ESM, "type": "module"

Optimizations

These are the changes that took the core bundle from 7.0 KB down to 4.9 KB gzipped (75% smaller than react-day-picker):

  • Intl.DateTimeFormat caching — formatters cached per locale+options, avoiding expensive re-instantiation.
  • React.memo on day cells — prevents re-rendering 42 unchanged cells on selection change.
  • Stable keyboard callbackconfigRef pattern, zero recreation cost.
  • Pre-computed time field orders — 4 static arrays replace Array.filter() on every keystroke.
  • Frozen selection constants — one UNSELECTED object shared across unselected days.
  • Dev JSX elimination — production build uses createElement directly, no jsxDEV metadata.

Why it's fast

  • Pure functions — grid building and selection allocate no closures and hold no subscriptions.
  • No date library dependencyNativeAdapter uses native Date and Intl.DateTimeFormat.
  • Tree-shakeable — import only what you need; if it's just buildCalendarMonth, the rest goes.
  • Minimal React overhead — hooks use useMemo to avoid rebuilding grids on every render.
  • No CSS runtime — no CSS-in-JS overhead, no stylesheet injection.

Optimization tips