How to Add a Calendar to a Nuxt.js App (SSR-Safe, No Plugin Headaches)
By SimpleCalendarJS Team
You need to add a calendar to your Nuxt app. You search "Nuxt calendar," and the results split into two camps: date pickers that can't show events on a week grid, and full event calendars that immediately break Nuxt's SSR with window is not defined. The calendar itself is a solved problem — the hard part in Nuxt is getting a client-side library to survive server rendering without a maze of plugin workarounds. Here's a clear breakdown of your options and a working implementation you can drop into any Nuxt project today.
The Nuxt calendar problem
Nuxt renders every component on the server by default. This is the feature that makes Nuxt fast — pages arrive as fully rendered HTML before JavaScript even loads. But every calendar library uses browser APIs: DOM measurements, window, document, date/time calculations that behave differently in Node.js. Drop one into a Nuxt page and you get one of two outcomes: a hard crash (window is not defined) or a hydration mismatch where the server HTML doesn't match what Vue produces on the client.
Nuxt provides three escape hatches for client-only code: the <ClientOnly> wrapper component, .client.vue single-file components, and client-side plugins (files named *.client.ts in the plugins/ directory). Each adds complexity, and the right choice depends on how the calendar library handles imports. FullCalendar, for example, has a documented Nuxt SSR issue that requires a dedicated client plugin to resolve.
The Nuxt calendar landscape in 2026
The Nuxt ecosystem has calendar options at every level, but they solve fundamentally different problems.
Date pickers (Nuxt UI's <UCalendar>, v-calendar via @samk-dev/nuxt-vcalendar) let users select a date or date range. They render a month grid and return a value. If you need a date input for a form, these are the right tool — v-calendar has over 200,000 weekly npm downloads and the Nuxt module has 24,000+ downloads with zero-config auto-imports.
Event calendars (FullCalendar + @fullcalendar/vue3, vue-cal) show events on a month/week/day grid with time slots, navigation, and click handlers. This is what most developers mean when they say "add a calendar to my Nuxt app."
The problem is that every popular event calendar option has Nuxt-specific trade-offs:
| Library | Gzipped Size | SSR Compatible | Extra Steps in Nuxt |
|---|---|---|---|
| Nuxt UI UCalendar | ~40 KB (with deps) | Yes | Date picker only — no event views |
| FullCalendar + Vue 3 | ~43 KB minimum | No — needs plugin | Client plugin + 3+ packages |
| vue-cal | ~8 KB | Partial | <ClientOnly> wrapper needed |
| SimpleCalendarJS | ~14 KB | <ClientOnly> only | No plugin, 1 package |
Nuxt UI's UCalendar is SSR-safe because it's designed for Nuxt — but it's a date picker built on @internationalized/date, not an event calendar. It has no week view, no day view, and no way to display scheduled events on a time grid. The Nuxt UI GitHub issue #1137 requesting a full calendar component confirms this gap.
FullCalendar requires @fullcalendar/core, @fullcalendar/vue3, and at least one view plugin before anything renders — and in Nuxt you also need a client-side plugin file to prevent SSR crashes. That's four files before a single event appears on screen.
Option 1: The Nuxt UI path (date picker only)
If you only need date selection — not event scheduling — Nuxt UI's <UCalendar> is the path of least resistance:
<template> <UCalendar v-model="selectedDate" /> </template> <script setup> const selectedDate = ref(); </script>
This works because UCalendar is built for Nuxt. It's SSR-safe, auto-imported, and reactive. But it cannot display events on a week or day grid, show time slots, or handle event click interactions. If you need any of those, you need an event calendar.
Option 2: The FullCalendar path
FullCalendar is the most popular JavaScript event calendar at roughly 680,000 weekly npm downloads across its packages. In Nuxt, the setup requires a client-side plugin to avoid SSR crashes:
npm install @fullcalendar/core @fullcalendar/vue3 @fullcalendar/daygrid
// plugins/fullcalendar.client.ts import FullCalendar from '@fullcalendar/vue3'; export default defineNuxtPlugin((nuxtApp) => { nuxtApp.vueApp.component('FullCalendar', FullCalendar); });
// components/EventCalendar.vue <template> <ClientOnly> <FullCalendar :options="calendarOptions" /> </ClientOnly> </template> <script setup> import dayGridPlugin from '@fullcalendar/daygrid'; const calendarOptions = { plugins: [dayGridPlugin], initialView: 'dayGridMonth', events: [ { title: 'Team Standup', date: '2026-09-15' }, ], }; </script>
Note the trade-offs:
- Three npm packages and a client plugin file before a single event renders
- The
.client.tsplugin pattern is Nuxt-specific knowledge that doesn't transfer to other frameworks - Total bundle cost: ~43 KB gzipped minimum, growing with each additional view plugin (timegrid, list, interaction)
- Advanced features like resource scheduling require a paid premium license starting at $480 per developer
Option 3: The vanilla JS path (recommended)
A vanilla JavaScript calendar initialises inside onMounted, which only runs on the client. The server render produces an empty <div>. The client render produces the same empty <div>, then onMounted fires and the calendar mounts. No mismatch, no client plugin needed — just <ClientOnly> for safety.
Here's how to add a calendar to a Nuxt app using SimpleCalendarJS — ~14 KB gzipped, zero dependencies:
npm install simple-calendar-js
// components/EventCalendar.vue <template> <div ref="calendarEl" /> </template> <script setup> import { ref, onMounted, onBeforeUnmount } from 'vue'; import SimpleCalendarJs from 'simple-calendar-js'; import 'simple-calendar-js/dist/simple-calendar-js.min.css'; const calendarEl = ref(null); let calendar = null; onMounted(() => { calendar = new SimpleCalendarJs(calendarEl.value, { defaultView: 'month', locale: 'en-US', enabledViews: ['month', 'week', 'day'], fetchEvents: async (start, end) => { const res = await fetch( `/api/events?from=${start.toISOString()}&to=${end.toISOString()}` ); return res.json(); }, onEventClick: (event) => console.log('Event clicked:', event), onSlotClick: (date) => console.log('Slot clicked:', date), }); }); onBeforeUnmount(() => { calendar?.destroy(); }); </script>
// pages/calendar.vue <template> <div> <h1>Team Calendar</h1> <ClientOnly> <EventCalendar /> <template #fallback> <div style="height: 600px; display: flex; align-items: center; justify-content: center;"> Loading calendar... </div> </template> </ClientOnly> </div> </template>
That's it. One component, one <ClientOnly> wrapper. No plugin file. No adapter package. No three-package install.
What this gives you
- Month, week, and day views — toggle with the built-in toolbar or call
calendar.setView('week')programmatically - Async event fetching — the
fetchEventscallback fires with the visible date range on every navigation, so you only load what's on screen - Click handlers —
onEventClickfor existing events,onSlotClickfor empty time slots (wire it to a "create event" modal) - 34+ locales built in — pass
locale: 'pt-BR'orlocale: 'ja-JP'and the calendar renders in that language - Automatic cleanup —
destroy()inonBeforeUnmountprevents memory leaks when the component unmounts
Nuxt server routes + calendar: the full-stack pattern
Nuxt's built-in server routes let you co-locate your API alongside your front end. Pair them with SimpleCalendarJS's fetchEvents callback for a clean full-stack calendar:
// server/api/events.get.ts export default defineEventHandler(async (event) => { const query = getQuery(event); const from = query.from as string; const to = query.to as string; // Replace with your database query const events = await $fetch('https://your-api.com/events', { query: { from, to }, }); return events; });
The calendar component's fetchEvents calls /api/events — which Nuxt routes to the server handler. No separate backend, no CORS configuration, no environment variable juggling for API URLs.
Theming the calendar in Nuxt
SimpleCalendarJS uses CSS custom properties. This works identically whether you're using Nuxt UI's design tokens, Tailwind, UnoCSS, or a global stylesheet:
.uc-calendar { --cal-primary: #00dc82; --cal-primary-dark: #003b2e; --cal-today-bg: #e6faf2; --cal-font-size: 14px; }
Four lines and your calendar matches Nuxt's signature green. No deeply nested selector overrides, no !important flags.
For dark mode with Nuxt's built-in useColorMode():
.dark .uc-calendar { --cal-bg: #18181b; --cal-text: #e4e4e7; --cal-border: #27272a; --cal-today-bg: #27272a; }
Bundle size: what you're shipping to the browser
In Nuxt, every kilobyte inside a <ClientOnly> boundary is JavaScript that ships to the browser and directly impacts Largest Contentful Paint (LCP) and Total Blocking Time (TBT) — both Google ranking signals.
| Setup | Gzipped Size | npm Packages | Client Plugin Required |
|---|---|---|---|
| FullCalendar + Vue 3 + daygrid | ~43 KB | 3+ | Yes |
| Nuxt UI UCalendar (date picker only) | ~40 KB (with deps) | 0 (built-in) | No |
| vue-cal (event calendar) | ~8 KB | 1 | No |
| SimpleCalendarJS (event calendar) | ~14 KB | 1 | No |
vue-cal is the lightest Vue-native event calendar at ~8 KB — but it only works in Vue projects, v4 is no longer actively maintained, and the v5 rewrite has a different API surface. SimpleCalendarJS delivers month, week, and day event views at ~14 KB with zero framework coupling — the same code works if you later migrate to React, Svelte, or plain HTML.
When to use a Nuxt-specific calendar instead
There are legitimate reasons to choose a Nuxt-native or Vue-specific calendar:
- Date selection only: If you need a date input for a form (not an event calendar), Nuxt UI's
<UCalendar>is SSR-safe, auto-imported, and integrates with<UInputDate>for form bindings. - Reactive props and v-model: If your calendar's view, date, and events must be driven entirely by Vue's reactivity system with Pinia integration, a Vue component handles this natively.
- Drag-and-drop rescheduling: FullCalendar has mature drag-and-drop support for moving and resizing events on the grid.
- Nuxt module ecosystem: The
@samk-dev/nuxt-vcalendarmodule provides auto-imports and zero-config v-calendar setup if you want the convenience ofnuxt.config.tsregistration.
For the majority of Nuxt apps that need to display events on a calendar and let users interact with them, a vanilla JS approach is simpler, lighter, and avoids the SSR compatibility dance that plagues every other option.
Summary
- Nuxt's SSR breaks most calendar libraries — you get
window is not definedor hydration mismatches without<ClientOnly>, client plugins, or both - Nuxt UI's UCalendar is SSR-safe but it's a date picker, not an event calendar — no week view, no day view, no scheduled events
- FullCalendar requires 3+ npm packages plus a
.client.tsplugin file in Nuxt before anything renders - A vanilla JS calendar initialised in onMounted never runs on the server, so a
<ClientOnly>wrapper is all you need — no plugin file, no adapter package - SimpleCalendarJS ships a full event calendar (month, week, day views, async event fetching, click handlers, 34+ locales) in ~14 KB gzipped with zero dependencies and clean Nuxt SSR compatibility
- Pair it with Nuxt server routes for a full-stack calendar with no separate backend or CORS configuration
Sources & Further Reading
Research & References
- FullCalendar in Nuxt 3 — nuxt/nuxt GitHub #10586
- Nuxt UI Calendar component request — nuxt/ui GitHub #1137
- UCalendar rendering issues — nuxt/ui GitHub #3044
- FullCalendar Vue Component documentation — fullcalendar.io
- Nuxt ClientOnly component — nuxt.com
- Common Problems With The Nuxt Client-Only Component — Josh Deltener
- @samk-dev/nuxt-vcalendar — Nuxt Modules
- Nuxt UI Calendar component documentation — ui.nuxt.com
- vue-cal GitHub repository — antoniandre/vue-cal
- Load Third-Party Scripts in Nuxt: useHead vs useScript — Vue School
- Nuxt In 2026: The Vue Full-Stack Framework — DEV Community
Image Credits
- Cover: Codes On Screen — Pexels
All images free to use under the Pexels License.
Frequently Asked Questions
What is the best calendar library for Nuxt?
It depends on what you need. Nuxt UI's UCalendar is a date picker — it lets users select dates but has no week or day event views. For event scheduling with month, week, and day grids, FullCalendar with @fullcalendar/vue3 works but requires a client-only plugin and 43+ KB gzipped across multiple packages. SimpleCalendarJS (~14 KB, zero dependencies) provides the same core views and works in Nuxt with a simple onMounted pattern and no plugin configuration.
How do I add a calendar to a Nuxt 4 app?
Install a calendar library via npm, create a Vue component, and initialise the calendar inside the onMounted lifecycle hook. For vanilla JS libraries like SimpleCalendarJS, use a template ref for the DOM container and onMounted for setup. Wrap the component in Nuxt's built-in <ClientOnly> tag when importing it into a page to prevent SSR mismatches.
Why do calendar libraries break in Nuxt with SSR?
Nuxt renders every component on the server by default. Calendar libraries depend on browser APIs like window, document, and DOM measurements that don't exist in Node.js. When the server tries to render a calendar component, it either throws an error or produces HTML that doesn't match what the client generates — causing hydration mismatches.
What is the ClientOnly component in Nuxt?
ClientOnly is a built-in Nuxt component that prevents its children from rendering on the server. It's the standard way to integrate browser-only libraries. You can also provide a #fallback slot to show placeholder content during SSR. Note that component scripts still execute on the server — if a library throws on import, you may need a .client.vue file or a client-side plugin instead.
Do I need a Nuxt module for a calendar?
No. Nuxt modules like @samk-dev/nuxt-vcalendar add convenience (auto-imports, zero config), but they wrap date picker libraries like v-calendar — not event calendars. For event scheduling, a vanilla JavaScript library initialised in onMounted is simpler and avoids the overhead of a dedicated Nuxt module.
Can I use a vanilla JavaScript calendar in Nuxt?
Yes. The pattern is a template ref for the container, onMounted for initialisation, and onBeforeUnmount for cleanup — the same approach Vue's documentation recommends for any imperative DOM library. Wrap the component with <ClientOnly> and the calendar never touches the server render.
Add a calendar to your app today
Free for personal projects. $49/year or $199 lifetime per commercial project.
