Why FullCalendar Is Slowing Down Your App (And What to Do About It)
By SimpleCalendarJS Team
Your app's performance dashboard looks fine — until you scroll down to the page with the calendar. FullCalendar is the most popular JavaScript calendar library, with over 19,000 GitHub stars and 1 million+ weekly npm downloads. But popularity doesn't exempt it from shipping a heavy JavaScript payload, and that payload has a measurable cost on your Core Web Vitals.
The bundle tax you didn't budget for
Every FullCalendar setup starts with @fullcalendar/core. A minimal month-view configuration with @fullcalendar/daygrid already costs roughly 43 KB gzipped. Most real-world apps don't stop there:
| Package combination | Approximate gzipped size |
|---|---|
core + daygrid | ~43 KB |
+ timegrid | ~55 KB |
+ interaction (drag & drop) | ~65 KB |
+ list + @fullcalendar/react | ~80–100 KB |
Full standard bundle (fullcalendar) | ~100 KB |
For context, Google's performance guidance recommends keeping total JavaScript per page under 300 KB gzipped to maintain good Core Web Vitals. A single calendar component consuming a third of that budget is significant — especially on pages where the calendar isn't even the primary feature.
FullCalendar v6 made this worse by embedding Preact as its internal rendering engine. The @fullcalendar/common package saw a 1000%+ size increase on Bundlephobia compared to v5. The plugin architecture means you can tree-shake unused views, but the core cost is fixed and non-negotiable.
Runtime rendering: death by a thousand re-renders
Bundle size is only the first problem. FullCalendar's runtime behaviour creates a second performance cliff once the library is loaded.
GitHub issue #3003 documents a fundamental design problem: dragging a single event triggers eventDestroy and eventAfterRender for every event in the view, not just the one being moved. If your week view has 200 events and a user drags one, FullCalendar destroys and re-renders all 200. Any custom JavaScript attached via event render hooks runs 200 times.
The GitHub issue tracker tells a consistent story:
- Issue #4395 — Page becomes non-responsive with large event sets. Browsers display "stop this script" warnings.
- Issue #2524 — Performance regression between v2.0.0 (1,500 events in under a second) and v2.1.0 (same data, 3–4 seconds).
removeEventSourcereported as "extremely slow." - Issue #5673 — Resource timeline view becomes unresponsive with 5,000 rows. Virtual rendering was proposed but milestoned for a future release.
- Issue #345 — The Angular wrapper triggers long-running script warnings with roughly 100 events in week view.
- Issue #5304 — RRule expansion freezes the calendar on year views with hundreds of recurring event definitions.
- Issue #4507 — Drag and resize interactions become sluggish proportionally to total event count.
These aren't edge cases from 2018. Many remain open or were only partially addressed in subsequent releases.
What this costs you in Core Web Vitals
Google ranks pages using three Core Web Vitals metrics. FullCalendar's performance profile directly affects two of them:
Largest Contentful Paint (LCP) measures when the largest visible element finishes rendering. The threshold for a "good" score is under 2.5 seconds. FullCalendar's JavaScript must be downloaded, parsed, and executed before the calendar paints — and on mobile connections, 80–100 KB of gzipped JavaScript takes meaningful time to transfer and process. Every millisecond spent parsing calendar code is a millisecond subtracted from your LCP budget.
Interaction to Next Paint (INP) replaced First Input Delay in 2024 as the responsiveness metric. INP measures the latency of all user interactions throughout the page lifecycle, not just the first one. The threshold for "good" is under 200ms. When a user clicks a date or drags an event and FullCalendar re-renders the entire view in response, that processing time contributes directly to INP. With 200+ events, a single drag can block the main thread for hundreds of milliseconds.
The combination is punishing: a heavy bundle degrades LCP on load, and expensive re-renders degrade INP during use.
Measuring the damage in your app
Before making changes, quantify the problem. Run a Lighthouse audit on the page containing your FullCalendar instance and note:
- Total Blocking Time (TBT) — proxy for INP in lab testing. Look for FullCalendar-related scripts in the "Reduce JavaScript execution time" audit.
- JavaScript bundle size — use your bundler's analyzer (
webpack-bundle-analyzer,rollup-plugin-visualizer, or@next/bundle-analyzer) to see exactly how many KB FullCalendar contributes. - Main thread activity — open Chrome DevTools Performance tab, interact with the calendar, and look for long tasks (yellow bars exceeding 50ms).
If FullCalendar is consuming more than 20% of your total JavaScript budget and your calendar doesn't require resource timelines or complex RRULE recurrence, you're over-paying for what you're using.
The fix: drop the weight
SimpleCalendarJS ships at ~14 KB gzipped with zero dependencies. That's roughly one-third the size of FullCalendar's minimum setup — and it includes month, week, and day views, event fetching, click handlers, drag-and-drop, and event resizing out of the box.
import SimpleCalendarJs from 'simple-calendar-js'; import 'simple-calendar-js/simple-calendar-js.css'; const calendar = new SimpleCalendarJs('#calendar', { fetchEvents: async (start, end) => { const res = await fetch(`/api/events?start=${start.toISOString()}&end=${end.toISOString()}`); return res.json(); }, onEventClick: (event) => openEventModal(event), enableDragAndDrop: true, enableResize: true, });
No plugins to install. No framework adapters. No Preact hidden inside the bundle. The same code works in React, Vue, Svelte, Angular, Astro, or a plain HTML page.
Performance comparison
| Metric | FullCalendar (typical setup) | SimpleCalendarJS |
|---|---|---|
| Bundle size (gzipped) | 43–100+ KB | ~14 KB |
| Dependencies | Preact (internal), 3–6 npm packages | Zero |
| Re-render on event drag | All events in view | Affected event only |
| Framework adapters needed | Yes (@fullcalendar/react, etc.) | No — universal API |
| Virtual rendering | Not yet (milestoned future) | Lightweight DOM |
| Pricing | Free (standard) / $480+ per dev (premium) | Free personal / license req. commercial |
Migrating: what changes, what stays
If your FullCalendar usage is limited to displaying events and handling clicks (the majority of implementations), migration is straightforward:
// Before: FullCalendar import { Calendar } from '@fullcalendar/core'; import dayGridPlugin from '@fullcalendar/daygrid'; import interactionPlugin from '@fullcalendar/interaction'; const calendar = new Calendar(document.getElementById('calendar'), { plugins: [dayGridPlugin, interactionPlugin], events: '/api/events', eventClick: (info) => handleClick(info.event), }); calendar.render(); // After: SimpleCalendarJS import SimpleCalendarJs from 'simple-calendar-js'; import 'simple-calendar-js/simple-calendar-js.css'; const calendar = new SimpleCalendarJs('#calendar', { fetchEvents: async (start, end) => { const res = await fetch(`/api/events?start=${start.toISOString()}&end=${end.toISOString()}`); return res.json(); }, onEventClick: (event) => handleClick(event), });
The API surface is deliberately simpler. You pass a fetchEvents function that returns an array of event objects, and SimpleCalendarJS handles the rest — view rendering, navigation, locale formatting, and CSS theming via variables.
When to stay with FullCalendar
FullCalendar is the right tool when your requirements include:
- Resource scheduling — named resources on a horizontal timeline (room booking, staff rosters)
- RRULE recurrence — complex recurring event patterns with exceptions
- Enterprise timeline views — multi-week, multi-resource drag-and-resize workflows
- iCalendar feed ingestion — direct parsing of
.icsfiles from external calendars
If none of those describe your use case, you're paying a performance tax for features you don't use.
Summary
- FullCalendar ships 43–100+ KB gzipped depending on plugins — a significant fraction of your JavaScript budget on any page.
- FullCalendar v6's internal Preact dependency caused a 1000%+ bundle increase for the common package versus v5.
- Runtime re-rendering is not scoped — dragging one event can trigger a full-view re-render, degrading INP with large event sets.
- Multiple open GitHub issues document performance degradation at 100–5,000 events across different views and framework wrappers.
- SimpleCalendarJS delivers month, week, and day views at ~14 KB gzipped with zero dependencies, scoped re-renders, and no framework lock-in.
Sources & Further Reading
Research & References
- Performance — Page Not Responding for huge number of events — GitHub #4395
- Performance decrease in 2.x — GitHub #2524
- Optimize event re-rendering — GitHub #3003
- Improve resource timeline performance with virtual rendering — GitHub #5673
- RRule slow when many events in year view — GitHub #5304
- Resize and drag event slow when there are too many events — GitHub #4507
- FullCalendar Angular — terrible performance issue with lots of events — GitHub #345
- [v6.0.0-beta.1] @fullcalendar/common bundle size increased by +1000% — GitHub #7029
- Core Web Vitals 2026: New Thresholds and Tuning — Studio Meyer
- 2025 In Review: What's New In Web Performance — DebugBear
Image Credits
- Cover: Close-up of vibrant HTML code on screen — Pexels
All images free to use under the Pexels License.
Frequently Asked Questions
Why is FullCalendar so slow?
FullCalendar's performance issues stem from three factors: a large JavaScript bundle (43–80+ KB gzipped depending on plugins), a rendering model that re-renders every event in the view on any change (GitHub issue #3003), and the lack of virtual rendering for large datasets (issue #5673). On mobile devices, this combination can push Interaction to Next Paint (INP) well above Google's 200ms threshold.
How big is FullCalendar's bundle size?
A minimal FullCalendar setup with just @fullcalendar/core and @fullcalendar/daygrid ships approximately 43 KB gzipped. Adding timegrid, interaction, list, and a framework adapter pushes the total to 80–100+ KB gzipped. FullCalendar v6 also bundles Preact internally, which contributed to a significant size increase from v5 (documented in GitHub issue #7029).
Does FullCalendar affect Core Web Vitals?
Yes. FullCalendar's JavaScript payload increases both Largest Contentful Paint (LCP) and Interaction to Next Paint (INP). The browser must download, parse, and execute the bundle before the calendar renders, directly delaying LCP. Event interactions that trigger full view re-renders contribute to poor INP scores, especially with hundreds of events.
How many events can FullCalendar handle before it slows down?
Performance degradation becomes noticeable at around 100–500 events depending on the view type. GitHub issue #4395 documents the page becoming unresponsive with large event sets. The Angular wrapper (issue #345) shows severe slowdowns with about 100 events in week view. Resource timeline view (issue #5673) becomes unusable with 5,000+ resources.
What is the best lightweight alternative to FullCalendar?
SimpleCalendarJS weighs ~14 KB gzipped with zero dependencies — roughly one-third the size of a minimal FullCalendar setup. It supports month, week, and day views, event fetching, click handlers, drag-and-drop, event resizing, and 34+ locales in a single package. It's free for personal and open-source use, with commercial licenses available at $49/year or $199 lifetime per project.
Can I lazy-load FullCalendar to improve performance?
Yes, dynamic imports (React.lazy, import()) can defer FullCalendar's bundle until the calendar is needed. This helps LCP on pages where the calendar isn't above the fold. However, lazy loading doesn't solve FullCalendar's runtime rendering performance — once loaded, interaction slowdowns with large event sets remain.
Add a calendar to your app today
Free for personal projects. $49/year or $199 lifetime per commercial project.
